Skip to content

feat(RelativeRangeDatePicker): added interactive tooltip and header #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
docs={props.docs}
withApplyButton={props.withApplyButton}
withZonesList={props.withZonesList}
withHeader={props.withHeader}
/>
</div>
);
Expand Down
36 changes: 36 additions & 0 deletions src/components/RelativeRangeDatePicker/__tests__/form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,42 @@ describe('RelativeRangeDatePicker: form', () => {
]);
});

it('should submit docs preset after selection', async () => {
let value;
const onSubmit = jest.fn((e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
value = [...formData.entries()];
});
render(
<form data-qa="form" onSubmit={onSubmit}>
<RelativeRangeDatePicker name="date-field" withHeader />
<button type="submit" data-qa="submit">
submit
</button>
</form>,
);
await userEvent.tab();
await userEvent.tab();
await userEvent.keyboard('{Enter}');

await userEvent.click(screen.getByRole('button', {name: 'now - 5m'}));
await userEvent.click(screen.getAllByRole('button', {name: 'now'})[0]);

expect(screen.getByText('Last 5 minutes')).toBeVisible();

await userEvent.click(screen.getByTestId('submit'));

expect(onSubmit).toHaveBeenCalledTimes(1);
expect(value).toEqual([
['date-field', 'relative'],
['date-field', 'now - 5m'],
['date-field', 'relative'],
['date-field', 'now'],
['date-field', 'default'],
]);
});

it('supports form reset', async () => {
function Test() {
const [value, setValue] = React.useState<RangeValue<Value | null> | null>({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
@use '@gravity-ui/uikit/styles/mixins';
@use '../../../variables';

$block: '.#{variables.$ns}relative-range-date-picker-presets-doc';
$block: '.#{variables.$ns}relative-range-date-picker-doc';

#{$block} {
padding: var(--g-spacing-4);

&__button {
--g-button-background-color-hover: transparent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,12 @@ import type {TableColumnConfig} from '@gravity-ui/uikit';

import {block} from '../../../../utils/cn';
import {getButtonSizeForInput} from '../../../utils/getButtonSizeForInput';
import type {Preset} from '../Presets/defaultPresets';
import {i18n} from '../Presets/i18n';

import type {Preset} from './defaultPresets';
import {i18n} from './i18n';
import './PickerDoc.scss';

import './PresetsDoc.scss';

const b = block('relative-range-date-picker-presets-doc');

const columns: TableColumnConfig<Preset>[] = [
{
id: 'title',
name: () => {
return i18n('Range');
},
},
{
id: 'from',
name: () => {
return i18n('From');
},
},
{
id: 'to',
name: () => {
return i18n('To');
},
},
];
const b = block('relative-range-date-picker-doc');

const data: Preset[] = [
{
Expand Down Expand Up @@ -75,25 +53,67 @@ const data: Preset[] = [
},
];

interface PresetsExamplesProps {
size?: 's' | 'm' | 'l' | 'xl';
interface DocContentProps extends Omit<PresetsDocProps, 'docs' | 'className'> {
docs: Preset[];
}
function PresetsExamples({size, docs}: PresetsExamplesProps) {
return <Table columns={columns} data={docs} className={b('table', {size})} />;

function DocContent({size, docs, onStartUpdate, onEndUpdate}: DocContentProps) {
const isMobile = useMobile();

const columns: TableColumnConfig<Preset>[] = React.useMemo(
() => [
{
id: 'title',
name: () => {
return i18n('Range');
},
},
{
id: 'from',
name: () => {
return i18n('From');
},
template: (item) => (
<Button
size={isMobile ? 'l' : getButtonSizeForInput(size)}
onClick={() => onStartUpdate(item.from)}
>
{item.from}
</Button>
),
},
{
id: 'to',
name: () => {
return i18n('To');
},
template: (item) => (
<Button
size={isMobile ? 'l' : getButtonSizeForInput(size)}
onClick={() => onEndUpdate(item.to)}
>
{item.to}
</Button>
),
},
],
[isMobile, onEndUpdate, onStartUpdate, size],
);

return <Table columns={columns} data={docs} className={b('table', {size})} wordWrap />;
}

interface DesktopDocProps {
className?: string;
size?: 's' | 'm' | 'l' | 'xl';
interface DesktopDocProps extends Omit<PresetsDocProps, 'docs'> {
docs: Preset[];
}
function DesktopDoc({className, size, docs}: DesktopDocProps) {

function DesktopDoc({className, size, ...props}: DesktopDocProps) {
return (
<Popover
className={b(null)}
hasArrow={false}
content={<PresetsExamples size={size} docs={docs} />}
placement={['right-start', 'left-start']}
content={<DocContent size={size} {...props} />}
>
<Button
className={b('button', className)}
Expand All @@ -106,12 +126,11 @@ function DesktopDoc({className, size, docs}: DesktopDocProps) {
);
}

interface MobileDocProps {
className?: string;
size?: 's' | 'm' | 'l' | 'xl';
interface MobileDocProps extends Omit<PresetsDocProps, 'docs'> {
docs: Preset[];
}
function MobileDoc({className, size, docs}: MobileDocProps) {

function MobileDoc({className, ...props}: MobileDocProps) {
const [open, setOpen] = React.useState(false);
return (
<div className={b(null, className)}>
Expand All @@ -126,7 +145,7 @@ function MobileDoc({className, size, docs}: MobileDocProps) {
<Icon data={CircleQuestion} />
</Button>
<Sheet visible={open} onClose={() => setOpen(false)}>
<PresetsExamples size={size} docs={docs} />
<DocContent {...props} />
</Sheet>
</div>
);
Expand All @@ -136,18 +155,20 @@ interface PresetsDocProps {
className?: string;
size?: 's' | 'm' | 'l' | 'xl';
docs?: Preset[];
onStartUpdate: (start: string) => void;
onEndUpdate: (end: string) => void;
}

export function PresetsDoc({className, size, docs = data}: PresetsDocProps) {
export function PickerDoc({docs = data, ...props}: PresetsDocProps) {
const isMobile = useMobile();

if (!Array.isArray(docs) || docs.length === 0) {
return null;
}

if (isMobile) {
return <MobileDoc className={className} size={size} docs={docs} />;
return <MobileDoc {...props} docs={docs} />;
}

return <DesktopDoc className={className} size={size} docs={docs} />;
return <DesktopDoc {...props} docs={docs} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ $block: '.#{variables.$ns}relative-range-date-picker-form';
}
}

&__header {
display: flex;
align-items: center;
gap: var(--g-spacing-1);

margin-block-end: var(--g-spacing-2);
}

&__apply {
margin-block-start: var(--g-spacing-2);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import type {DateTime} from '@gravity-ui/date-utils';
import {Button} from '@gravity-ui/uikit';
import {Button, Text} from '@gravity-ui/uikit';
import type {TextInputSize} from '@gravity-ui/uikit';

import {block} from '../../../../utils/cn';
Expand All @@ -14,6 +14,7 @@ import type {Preset} from '../Presets/defaultPresets';
import type {PresetTab} from '../Presets/utils';
import {Zones} from '../Zones/Zones';

import {PickerDoc} from './PickerDoc';
import {i18n} from './i18n';
import {useRelativeRangeDatePickerDialogState} from './useRelativeRangeDatePickerDialogState';

Expand All @@ -39,9 +40,11 @@ export interface PickerFormProps extends RelativeRangeDatePickerStateOptions, Do
withZonesList?: boolean;
/** Show relative range presets */
withPresets?: boolean;
/** Show header with docs tooltip */
withHeader?: boolean;
/** Custom preset tabs */
presetTabs?: PresetTab[];
/** Custom docs for presets, if empty array docs will be hidden */
/** Custom docs for picker, if empty array docs will be hidden */
docs?: Preset[];
}

Expand All @@ -62,9 +65,27 @@ export function PickerForm(
size: props.size,
errorPlacement: 'inside',
};
const {isDateUnavailable} = props;
const {isDateUnavailable, withHeader = false} = props;
return (
<div className={b({size: props.size}, props.className)} style={props.style}>
{withHeader && (
<div className={b('header')}>
<Text variant={props.size === 'xl' ? 'subheader-3' : 'subheader-2'}>
{i18n('Select the interval')}
</Text>
<PickerDoc
size={props.size}
docs={props.docs}
onStartUpdate={(start) => {
state.setStart({type: 'relative', value: start});
}}
onEndUpdate={(end) => {
state.setEnd({type: 'relative', value: end});
}}
/>
</div>
)}

<div className={b('pickers')}>
<RelativeDatePicker
{...fieldProps}
Expand Down Expand Up @@ -124,7 +145,6 @@ export function PickerForm(
}
}}
minValue={props.minValue}
docs={props.docs}
className={b('presets')}
/>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"\"From\" can't be after \"To\".": "\"From\" can't be after \"To\".",
"From": "From",
"To": "To",
"Apply": "Apply"
"Apply": "Apply",
"Select the interval": "Select the interval"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"\"From\" can't be after \"To\".": "Значение «От» не может быть позже чем «До».",
"From": "От",
"To": "Дo",
"Apply": "Применить"
"Apply": "Применить",
"Select the interval": "Выберите интервал"
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ $block: '.#{variables.$ns}relative-range-date-picker-presets';
outline: none;
}

&__doc {
margin-inline-start: auto;
}

&__content {
overflow: auto;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {List, Tab, TabList, TabPanel, TabProvider} from '@gravity-ui/uikit';

import {block} from '../../../../utils/cn';

import {PresetsDoc} from './PresetsDoc';
import type {Preset} from './defaultPresets';
import {filterPresetTabs, getDefaultPresetTabs} from './utils';
import type {PresetTab} from './utils';
Expand All @@ -23,7 +22,6 @@ export interface PresetProps {
minValue?: DateTime;
size?: 's' | 'm' | 'l' | 'xl';
presetTabs?: PresetTab[];
docs?: Preset[];
}
export function Presets({
className,
Expand All @@ -32,7 +30,6 @@ export function Presets({
withTime,
onChoosePreset,
presetTabs,
docs,
}: PresetProps) {
const tabs = React.useMemo(() => {
return filterPresetTabs(presetTabs ?? getDefaultPresetTabs({withTime}), {minValue});
Expand Down Expand Up @@ -64,7 +61,6 @@ export function Presets({
</Tab>
))}
</TabList>
<PresetsDoc className={b('doc')} size={size} docs={docs} />
</div>
<TabPanel className={b('content')} value={activeTabId}>
<PresetsList
Expand Down
4 changes: 3 additions & 1 deletion src/components/RelativeRangeDatePicker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ export interface RelativeRangeDatePickerProps
withZonesList?: boolean;
/** Show relative range presets */
withPresets?: boolean;
/** Show header with docs tooltip */
withHeader?: boolean;
/** Custom preset tabs */
presetTabs?: PresetTab[];
/** Custom docs for presets, if empty array docs will be hidden */
/** Custom docs for picker, if empty array docs will be hidden */
docs?: Preset[];
/** Show selected relative values as absolute dates */
alwaysShowAsAbsolute?: boolean;
Expand Down
Loading