gravity-ui / date-components

MIT License
7 stars 3 forks source link

Component for selecting a time interval #77

Open Estasie opened 2 months ago

Estasie commented 2 months ago

We already have RangeDatePicker and RelativeRangeDatePicker components, so we can create a set of new auxiliary components. For example, if I want to pick a time now I have to open time picker6 click the date, select to value and click Apply button. To solve this problem I suggest:

  1. Make a shortcuts component with a buttons set (should be customizable, depending on user's needs and also provide default shortcuts). The Controls will have a RelativeRangeDatePicker under the hood and operate with absolute and relative data types. Example of usage: <Controls from={from} to={to} onUpdate={onUpdate} // ... />
    1. And my favourite one and a very convenient thing to use - timeline ruler. Visually, it is a time band that allows you to navigate through time intervals, shorten and distance them in order to explore segments of different sizes. For example, for the last day, week, a couple of minutes, a certain time interval. I suggest making this component initially independent and working with an absolute dates. But provide a hook for a possible combination of Controls and Ruler (make them work together). Simple example of Ruler usage:
      <Timeline
              from={fromMillis}
              to={toMillis}
              onUpdate={onUpdateMillis}
              // ...
      />

      Pretty simple and same as controls.

An example of common usage of the components (remembering hook):

import {ThinTimelineRuler, ThinTimelineControls, useThinTimelineRuler, TimelineProps} from '...';

export function MyComponent() {
    const [{from, to}, onUpdate] = React.useState<TimelineProps>(/* ... */);

    const {
        fromMillis,
        toMillis,
        onUpdateMillis,
    } = useTimeline({from, to, onUpdate});

    // ...

    return (
        <div>
            <Controls
                from={from}
                to={to}
                onUpdate={onUpdate}
                // ...
            />
            <Timeline
                from={frommillis}
                to={toMillis}
                onUpdate={onUpdateMillis}
                // ...
            />
        </div>
    );
}

We can also provide some usable hooks and etc. Open to discuss details and API!

Estasie commented 2 months ago

Interface for ThinTimelineRuler

interface Period {
    /** Selected period start (millis from epoch) */
    start: number;
    /** Selected period end (millis from epoch) */
    end: number;
}
interface HasHandlesProp {
    /**
     * If `false` handles are not shown and cannot be dragged.
     * Selection window will have bordered style
     */
    hasHandles?: boolean;
}
interface UncontrolledThinTimelineRulerProps extends Period, HasHandlesProp {
    /** Gets invoked whenever selected period gets updated */
    onUpdate?: (value: Period) => void;
    /** If display red line denoting current instant */
    displayNow?: boolean;
    /** If render '-' and '+' buttons */
    hasZoomButtons?: boolean;
    /** Position of the '-' and '+' zoom buttons */
    zoomButtonsPosition?: 'left' | 'right';
    /** Use this to customize rendering of selected period */
    renderSelection?: (opts: RenderSelectionOptions) => React.ReactNode;
    /** Use this to render additional SVG nodes on the ruler */
    renderAdditionalSvg?: (opts: RenderSelectionOptions) => React.ReactNode;
    /** Use this to allow custom rendering of time ticks */
    formatTime?: (time: DateTime) => string;

    /**
     * This prop can be used to specify the point of maximal interest in the
     * selected period. 0 means the earliest point, 1 means the latest, 0.5 can
     * be used to mark the middle of the selected period.
     *
     * When zoom buttons are pressed, this point in the selected period will
     * remain fixed.
     */
    pointOfMaxInterest?: number;

    /** Min ruler viewport length (ms, default is 1m) */
    viewportMinLength?: number;
    /** Max ruler viewport length (ms, default is 20y) */
    viewportMaxLength?: number;

    /** Fixed ruler viewport start point (ms) */
    viewportStart?: number;
    /** Fixed ruler viewport end point (ms) */
    viewportEnd?: number;

    /** If disabled, prevents timeline drag. Default: true */
    isTimelineDragEnabled?: boolean;

    /**
     * Specify this if you want to override the timeZone used when parsing or formatting a date and time value.
     * If no timeZone is set, the default timeZone for the current user is used.
     */
    timeZone?: string;

    className?: string;
}

Interface for Controls:

interface ThinTimelineControlsProps {
    value: RelativeRangeDatePickerProps['value'];

    /** Gets called whenever selected period, stickToNow, or period name are updated */
    onUpdate: (value: RelativeRangeDatePickerProps['value']) => void;

    /** Presets for toolbar at the top as duration strings (e.g. '1m', '15d', '1y') */
    toolbarPresets?: string[];
    /** Presets for date input popup as duration strings (e.g. '1m', '15d', '1y') */
    relativeRangesPalette?: string[];
    /** Enables/disables toolbar custom button that is used to input arbitrary duration string */
    hasCustomPresetInput?: boolean;

    /** Indicates whether to add a 'Now' block */
    withNow?: boolean;
    /** Indicates whether to show popup presets */
    withPresets?: boolean;
    /** Indicates whether to add a range date picker block */
    withRangeDatePicker?: boolean;

    /** Set one of proposed date and time formats in Datepicker [Available formats](https://day.js.org/docs/en/display/format) */
    format?: string;
    /** Indicates whether to add a refresh block */
    withRefresh?: boolean;
    /** Selected refresh interval value (milliseconds, 0 = off) */
    refreshInterval?: number;
    /** Available refresh interval options (milliseconds, 0 = off) */
    availableRefreshIntervals?: number[];
    alwaysShowAsAbsolute?: boolean;
    /** Gets invoked whenever refresh interval is updated */
    onRefreshIntervalUpdate?: (value: number) => void;
    /** Gets invoked when refresh button is clicked */
    onRefreshClick?: () => void;
    /**
     * If `true`, selected refresh interval is rendered as human-readable string;
     * otherwise shorthand duration string is used (e.g. '30s')
     */
    humanReadableActiveRefreshInterval?: boolean;
    /**
     * Allows to specify a default refresh interval. If 'Now' button gets clicked,
     * this interval will be automatically enabled.
     * If not provided, the first non-zero value from `availableRefreshIntervals`
     * will be taken.
     */
    defaultRefreshInterval?: number;

    className?: string;

    /**
     * Custom content to render before all toolbar items.
     * Can be utilized to reuse the same flex container as the rest of the toolbar.
     */
    prepend?: React.ReactNode;
    /** Same as `prepend`, but after all toolbar items. */
    append?: React.ReactNode;

    /**
     * This prop can be used to specify the point of maximal interest in the
     * selected period. 0 means the earliest point, 1 means the latest, 0.5 can
     * be used to mark the middle of the selected period.
     *
     * When 'Now' button is pressed, this point is set to match current datetime.
     */
    pointOfMaxInterest?: number;

    /**
     * Maximum time limit for suggest values
     * By default, suggest includes values up to '2y'
     * With this prop you can set max suggest value
     */
    suggestLimit?: string;

    /**
     * Specify this if you want to override the timeZone used when parsing or formatting a date and time value.
     * If no timeZone is set, the default timeZone for the current user is used.
     */
    timeZone?: string;
}
korvin89 commented 2 months ago

cc @ValeraS