Skip to content

Commit

Permalink
feat(RelativeRangeDatePicker): add renderControl props (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
zamkovskaya authored Sep 18, 2024
1 parent 41c4b27 commit d389eea
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,6 @@ $block: '.#{variables.$ns}relative-range-date-picker';

display: inline-flex;

&__input {
caret-color: transparent;

&_mobile {
pointer-events: none;
}
}

&__mobile-trigger {
--_--g-date-mobile-trigger-clear-width: 0px;
--_--g-date-mobile-trigger-errors-width: 0px;
--_--g-date-mobile-trigger-button-width: 24px;

position: absolute;
inset: 0;
inset-inline-end: calc(
var(--g-spacing-2) + var(--_--g-date-mobile-trigger-button-width) +
var(--_--g-date-mobile-trigger-clear-width) +
var(--_--g-date-mobile-trigger-errors-width)
);

opacity: 0;

&_size_s {
--_--g-date-mobile-trigger-button-width: 20px;
}
&_size_l {
--_--g-date-mobile-trigger-button-width: 28px;
}
&_size_xl {
--_--g-date-mobile-trigger-button-width: 36px;
}

&_has-clear {
--_--g-date-mobile-trigger-clear-width: calc(
var(--_--g-date-mobile-trigger-button-width) + 2px
);
}
&_has-errors {
--_--g-date-mobile-trigger-errors-width: calc(
var(--_--g-date-mobile-trigger-button-width) + 2px
);
}
}

&__value-label {
display: flex;

Expand Down
167 changes: 25 additions & 142 deletions src/components/RelativeRangeDatePicker/RelativeRangeDatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,21 @@

import React from 'react';

import type {DateTime} from '@gravity-ui/date-utils';
import {Calendar as CalendarIcon} from '@gravity-ui/icons';
import {
Button,
Icon,
TextInput,
useControlledState,
useFocusWithin,
useMobile,
} from '@gravity-ui/uikit';
import {useControlledState, useFocusWithin, useMobile} from '@gravity-ui/uikit';

import {block} from '../../utils/cn';
import {HiddenInput} from '../HiddenInput/HiddenInput';
import type {Value} from '../RelativeDatePicker';
import type {
DomProps,
FocusableProps,
InputBase,
InputDOMProps,
RangeValue,
StyleProps,
TextInputProps,
Validation,
} from '../types';
import {getButtonSizeForInput} from '../utils/getButtonSizeForInput';

import {Control} from './components/Control/Control';
import {PickerDialog} from './components/PickerDialog/PickerDialog';
import type {Preset} from './components/Presets/defaultPresets';
import type {PresetTab} from './components/Presets/utils';
import {useRelativeRangeDatePickerState} from './hooks/useRelativeRangeDatePickerState';
import type {RelativeRangeDatePickerStateOptions} from './hooks/useRelativeRangeDatePickerState';
import {i18n} from './i18n';
import {getDefaultTitle} from './utils';
import type {RelativeRangeDatePickerProps} from './types';

import './RelativeRangeDatePicker.scss';

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

export interface RelativeRangeDatePickerProps
extends RelativeRangeDatePickerStateOptions,
DomProps,
InputBase,
InputDOMProps,
TextInputProps,
Validation,
FocusableProps,
StyleProps {
/** Format of the date when rendered in the input. [Available formats](https://day.js.org/docs/en/display/format) */
format?: string;
/** A placeholder date that controls the default values of each segment when the user first interacts with them. Defaults to today's date at midnight. */
placeholderValue?: DateTime;
/** Apply changes with button */
withApplyButton?: boolean;
/** Show time zone selector */
withZonesList?: boolean;
/** Show relative range presets */
withPresets?: boolean;
/** Custom preset tabs */
presetTabs?: PresetTab[];
/** Custom docs for presets, if empty array docs will be hidden */
docs?: Preset[];
/** Show selected relative values as absolute dates */
alwaysShowAsAbsolute?: boolean;
/** */
getRangeTitle?: (value: RangeValue<Value | null> | null, timeZone: string) => string;
/** Sets the CSS className for the popup element. */
popupClassName?: string;
/** Handler that is called when the popup's open state changes. */
onOpenChange?: (open: boolean) => void;
}

export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
const state = useRelativeRangeDatePickerState(props);

Expand All @@ -99,58 +43,27 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
},
});

const {alwaysShowAsAbsolute, presetTabs, getRangeTitle} = props;
const format = props.format || 'L';
const text = React.useMemo(
() =>
typeof getRangeTitle === 'function'
? getRangeTitle(state.value, state.timeZone)
: getDefaultTitle({
value: state.value,
timeZone: state.timeZone,
alwaysShowAsAbsolute: alwaysShowAsAbsolute,
format,
presets: presetTabs?.flatMap(({presets}) => presets),
}),
[alwaysShowAsAbsolute, format, getRangeTitle, presetTabs, state.timeZone, state.value],
);

const validationState = props.validationState || (state.isInvalid ? 'invalid' : undefined);
const errorMessage = props.errorMessage ?? state.errors.join('\n');

return (
<div
ref={anchorRef}
{...focusWithinProps}
className={b(null, props.className)}
style={props.style}
>
<TextInput
id={props.id}
autoFocus={props.autoFocus}
controlRef={inputRef}
value={text}
placeholder={props.placeholder}
onUpdate={(v) => {
if (!props.readOnly && !v) {
state.setValue(null, 'default');
<Control
props={props}
state={state}
open={open}
isMobile={isMobile}
ref={inputRef}
onClick={() => {
if (props.disabled) {
return;
}
if (!open) {
setIsActive(true);
setOpen(true);
}
}}
controlProps={{
role: 'combobox',
'aria-expanded': open,
disabled: isMobile,
readOnly: props.readOnly,
className: b('input', {mobile: isMobile}),
onClick: () => {
if (props.disabled) {
return;
}
if (!open) {
setIsActive(true);
setOpen(true);
}
},
}}
onKeyDown={(e) => {
if (props.disabled) {
Expand All @@ -161,52 +74,22 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
setOpen(true);
}
}}
onClickCalendar={() => {
setIsActive(true);
setOpen(!open);
}}
onFocus={() => {
if (!isActive) {
setIsActive(true);
setOpen(true);
}
}}
validationState={validationState}
errorMessage={errorMessage}
errorPlacement={props.errorPlacement}
pin={props.pin}
size={props.size}
label={props.label}
hasClear={props.hasClear}
disabled={props.disabled}
endContent={
<Button
view="flat-secondary"
size={getButtonSizeForInput(props.size)}
disabled={props.disabled}
extraProps={{
'aria-haspopup': 'dialog',
'aria-expanded': open,
'aria-label': i18n('Range date picker'),
}}
onClick={() => {
setIsActive(true);
setOpen(!open);
}}
>
<Icon data={CalendarIcon} />
</Button>
}
onUpdate={(v: string) => {
if (!props.readOnly && !v) {
state.setValue(null, 'default');
}
}}
/>
{isMobile ? (
<button
className={b('mobile-trigger', {
'has-clear': Boolean(props.hasClear && state.value),
'has-errors': state.isInvalid && props.errorPlacement === 'inside',
size: props.size,
})}
onClick={() => {
setIsActive(true);
setOpen(true);
}}
/>
) : null}
<HiddenInput
name={props.name}
form={props.form}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {Meta, StoryObj} from '@storybook/react';
import {timeZoneControl} from '../../../demo/utils/zones';
import type {Value} from '../../RelativeDatePicker';
import {RelativeRangeDatePicker} from '../RelativeRangeDatePicker';
import type {RelativeRangeDatePickerProps} from '../RelativeRangeDatePicker';
import type {RelativeRangeDatePickerProps} from '../types';

const meta: Meta<typeof RelativeRangeDatePicker> = {
title: 'Components/RelativeRangeDatePicker',
Expand Down Expand Up @@ -123,3 +123,25 @@ export const InsideDialog: StoryObj<
},
},
};

export const CustomControl: StoryObj<RelativeRangeDatePickerProps> = {
...Default,
args: {
...Default.args,
style: undefined,
},
render: (props) => {
return (
<RelativeRangeDatePicker
{...props}
renderControl={({title, triggerProps, ref}) => {
return (
<Button ref={ref} {...triggerProps} extraProps={triggerProps}>
{title || 'Not selected'}
</Button>
);
}}
/>
);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@use '../../../variables.scss';

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

#{$block} {
&__input {
caret-color: transparent;

&_mobile {
pointer-events: none;
}
}

&__mobile-trigger {
--_--g-date-mobile-trigger-clear-width: 0px;
--_--g-date-mobile-trigger-errors-width: 0px;
--_--g-date-mobile-trigger-button-width: 24px;

position: absolute;
inset: 0;
inset-inline-end: calc(
var(--g-spacing-2) + var(--_--g-date-mobile-trigger-button-width) +
var(--_--g-date-mobile-trigger-clear-width) +
var(--_--g-date-mobile-trigger-errors-width)
);

opacity: 0;

&_size_s {
--_--g-date-mobile-trigger-button-width: 20px;
}
&_size_l {
--_--g-date-mobile-trigger-button-width: 28px;
}
&_size_xl {
--_--g-date-mobile-trigger-button-width: 36px;
}

&_has-clear {
--_--g-date-mobile-trigger-clear-width: calc(
var(--_--g-date-mobile-trigger-button-width) + 2px
);
}
&_has-errors {
--_--g-date-mobile-trigger-errors-width: calc(
var(--_--g-date-mobile-trigger-button-width) + 2px
);
}
}
}
Loading

0 comments on commit d389eea

Please sign in to comment.