All files / src/components/RangeDateSelection/components/Ticks Ticks.tsx

100% Statements 27/27
83.33% Branches 5/6
100% Functions 6/6
100% Lines 25/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 1141x     1x                                                           1x                             12x 12x 12x   8x                   12x 8x       8x 214x       12x 12x     12x   12x 10x 7x     3x   3x 26x 26x 26x   26x                         3x     12x              
import React from 'react';
 
import type {DateTime} from '@gravity-ui/date-utils';
import {dateTime} from '@gravity-ui/date-utils';
 
import {block} from '../../../../utils/cn';
import {getLerpCoeff} from '../../utils/span';
import {useViewportDimensions, useViewportInterval} from '../Ruler/Ruler';
 
import type {Geometry} from './utils';
import {
    calculateTickTimes,
    formatTick as defaultFormatTime,
    makeMiddleTicksGeometry,
    makeSlitTicksGeometry,
} from './utils';
 
import './Ticks.scss';
 
interface TicksProps {
    theme: 'normal' | 'dim';
    minTickWidth: number;
    maxTickWidth: number;
    hasLabels?: boolean;
 
    geometry: Geometry;
    className?: string;
    labelsClassName?: string;
 
    formatTime?: (time: DateTime) => string;
    timeZone?: string;
}
 
const b = block('timeline-ticks');
 
export {makeMiddleTicksGeometry, makeSlitTicksGeometry};
 
export function Ticks({
    theme,
    minTickWidth,
    maxTickWidth,
    hasLabels,
    geometry,
    className,
    labelsClassName,
    formatTime,
    timeZone,
}: TicksProps) {
    const viewport = useViewportDimensions();
    const interval = useViewportInterval();
    const tickTimes = React.useMemo(
        () =>
            calculateTickTimes({
                minTickWidth,
                maxTickWidth,
                viewportStart: interval.start,
                viewportEnd: interval.end,
                viewportWidth: viewport.width,
                timeZone,
            }),
        [maxTickWidth, minTickWidth, interval, viewport.width, timeZone],
    );
    const tickCoords = React.useMemo(() => {
        const timeToXCoeff = getLerpCoeff(
            {start: interval.start.valueOf(), end: interval.end.valueOf()},
            {start: 0, end: viewport.width},
        );
        return tickTimes.map(
            (t) => 0 + Math.round((t.valueOf() - interval.start.valueOf()) * timeToXCoeff),
        );
    }, [tickTimes, interval, viewport.width]);
 
    const ticksGeometry = React.useMemo(() => {
        return tickCoords.map(geometry).join('');
    }, [geometry, tickCoords]);
 
    const ticksPath = <path className={b({theme}, className)} d={ticksGeometry} />;
 
    const tickLabels = React.useMemo(() => {
        if (!hasLabels || tickCoords.length < 2) {
            return null;
        }
 
        const result: React.ReactElement[] = [];
 
        for (let i = 0; i < tickCoords.length; i++) {
            const x = tickCoords[i];
            const t = tickTimes[i];
            const dt = dateTime({input: t, timeZone});
 
            result.push(
                <text
                    key={i}
                    className={b('label', labelsClassName)}
                    dominantBaseline="middle"
                    x={x}
                    y="50%"
                    filter="url(#g-date-tt-ticklabelbg)"
                >
                    {formatTime ? formatTime(dt) : defaultFormatTime(dt)}
                </text>,
            );
        }
        return result;
    }, [hasLabels, tickCoords, tickTimes, timeZone, labelsClassName, formatTime]);
 
    return (
        <React.Fragment>
            {ticksPath}
            {tickLabels}
        </React.Fragment>
    );
}