22
33import React from 'react' ;
44
5- import type { DateTime } from '@gravity-ui/date-utils' ;
6- import { Calendar as CalendarIcon } from '@gravity-ui/icons' ;
7- import {
8- Button ,
9- Icon ,
10- TextInput ,
11- useControlledState ,
12- useFocusWithin ,
13- useMobile ,
14- } from '@gravity-ui/uikit' ;
5+ import { useControlledState , useFocusWithin , useMobile } from '@gravity-ui/uikit' ;
156
167import { block } from '../../utils/cn' ;
178import { HiddenInput } from '../HiddenInput/HiddenInput' ;
189import type { Value } from '../RelativeDatePicker' ;
19- import type {
20- DomProps ,
21- FocusableProps ,
22- InputBase ,
23- InputDOMProps ,
24- RangeValue ,
25- StyleProps ,
26- TextInputProps ,
27- Validation ,
28- } from '../types' ;
29- import { getButtonSizeForInput } from '../utils/getButtonSizeForInput' ;
3010
11+ import { Control } from './components/Control/Control' ;
3112import { PickerDialog } from './components/PickerDialog/PickerDialog' ;
32- import type { Preset } from './components/Presets/defaultPresets' ;
33- import type { PresetTab } from './components/Presets/utils' ;
3413import { useRelativeRangeDatePickerState } from './hooks/useRelativeRangeDatePickerState' ;
35- import type { RelativeRangeDatePickerStateOptions } from './hooks/useRelativeRangeDatePickerState' ;
36- import { i18n } from './i18n' ;
37- import { getDefaultTitle } from './utils' ;
14+ import type { RelativeRangeDatePickerProps } from './types' ;
3815
3916import './RelativeRangeDatePicker.scss' ;
4017
4118const b = block ( 'relative-range-date-picker' ) ;
4219
43- export interface RelativeRangeDatePickerProps
44- extends RelativeRangeDatePickerStateOptions ,
45- DomProps ,
46- InputBase ,
47- InputDOMProps ,
48- TextInputProps ,
49- Validation ,
50- FocusableProps ,
51- StyleProps {
52- /** Format of the date when rendered in the input. [Available formats](https://day.js.org/docs/en/display/format) */
53- format ?: string ;
54- /** 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. */
55- placeholderValue ?: DateTime ;
56- /** Apply changes with button */
57- withApplyButton ?: boolean ;
58- /** Show time zone selector */
59- withZonesList ?: boolean ;
60- /** Show relative range presets */
61- withPresets ?: boolean ;
62- /** Custom preset tabs */
63- presetTabs ?: PresetTab [ ] ;
64- /** Custom docs for presets, if empty array docs will be hidden */
65- docs ?: Preset [ ] ;
66- /** Show selected relative values as absolute dates */
67- alwaysShowAsAbsolute ?: boolean ;
68- /** */
69- getRangeTitle ?: ( value : RangeValue < Value | null > | null , timeZone : string ) => string ;
70- /** Sets the CSS className for the popup element. */
71- popupClassName ?: string ;
72- /** Handler that is called when the popup's open state changes. */
73- onOpenChange ?: ( open : boolean ) => void ;
74- }
75-
7620export function RelativeRangeDatePicker ( props : RelativeRangeDatePickerProps ) {
7721 const state = useRelativeRangeDatePickerState ( props ) ;
7822
@@ -99,58 +43,27 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
9943 } ,
10044 } ) ;
10145
102- const { alwaysShowAsAbsolute, presetTabs, getRangeTitle} = props ;
103- const format = props . format || 'L' ;
104- const text = React . useMemo (
105- ( ) =>
106- typeof getRangeTitle === 'function'
107- ? getRangeTitle ( state . value , state . timeZone )
108- : getDefaultTitle ( {
109- value : state . value ,
110- timeZone : state . timeZone ,
111- alwaysShowAsAbsolute : alwaysShowAsAbsolute ,
112- format,
113- presets : presetTabs ?. flatMap ( ( { presets} ) => presets ) ,
114- } ) ,
115- [ alwaysShowAsAbsolute , format , getRangeTitle , presetTabs , state . timeZone , state . value ] ,
116- ) ;
117-
118- const validationState = props . validationState || ( state . isInvalid ? 'invalid' : undefined ) ;
119- const errorMessage = props . errorMessage ?? state . errors . join ( '\n' ) ;
120-
12146 return (
12247 < div
12348 ref = { anchorRef }
12449 { ...focusWithinProps }
12550 className = { b ( null , props . className ) }
12651 style = { props . style }
12752 >
128- < TextInput
129- id = { props . id }
130- autoFocus = { props . autoFocus }
131- controlRef = { inputRef }
132- value = { text }
133- placeholder = { props . placeholder }
134- onUpdate = { ( v ) => {
135- if ( ! props . readOnly && ! v ) {
136- state . setValue ( null , 'default' ) ;
53+ < Control
54+ props = { props }
55+ state = { state }
56+ open = { open }
57+ isMobile = { isMobile }
58+ ref = { inputRef }
59+ onClick = { ( ) => {
60+ if ( props . disabled ) {
61+ return ;
62+ }
63+ if ( ! open ) {
64+ setIsActive ( true ) ;
65+ setOpen ( true ) ;
13766 }
138- } }
139- controlProps = { {
140- role : 'combobox' ,
141- 'aria-expanded' : open ,
142- disabled : isMobile ,
143- readOnly : props . readOnly ,
144- className : b ( 'input' , { mobile : isMobile } ) ,
145- onClick : ( ) => {
146- if ( props . disabled ) {
147- return ;
148- }
149- if ( ! open ) {
150- setIsActive ( true ) ;
151- setOpen ( true ) ;
152- }
153- } ,
15467 } }
15568 onKeyDown = { ( e ) => {
15669 if ( props . disabled ) {
@@ -161,52 +74,22 @@ export function RelativeRangeDatePicker(props: RelativeRangeDatePickerProps) {
16174 setOpen ( true ) ;
16275 }
16376 } }
77+ onClickCalendar = { ( ) => {
78+ setIsActive ( true ) ;
79+ setOpen ( ! open ) ;
80+ } }
16481 onFocus = { ( ) => {
16582 if ( ! isActive ) {
16683 setIsActive ( true ) ;
16784 setOpen ( true ) ;
16885 }
16986 } }
170- validationState = { validationState }
171- errorMessage = { errorMessage }
172- errorPlacement = { props . errorPlacement }
173- pin = { props . pin }
174- size = { props . size }
175- label = { props . label }
176- hasClear = { props . hasClear }
177- disabled = { props . disabled }
178- endContent = {
179- < Button
180- view = "flat-secondary"
181- size = { getButtonSizeForInput ( props . size ) }
182- disabled = { props . disabled }
183- extraProps = { {
184- 'aria-haspopup' : 'dialog' ,
185- 'aria-expanded' : open ,
186- 'aria-label' : i18n ( 'Range date picker' ) ,
187- } }
188- onClick = { ( ) => {
189- setIsActive ( true ) ;
190- setOpen ( ! open ) ;
191- } }
192- >
193- < Icon data = { CalendarIcon } />
194- </ Button >
195- }
87+ onUpdate = { ( v : string ) => {
88+ if ( ! props . readOnly && ! v ) {
89+ state . setValue ( null , 'default' ) ;
90+ }
91+ } }
19692 />
197- { isMobile ? (
198- < button
199- className = { b ( 'mobile-trigger' , {
200- 'has-clear' : Boolean ( props . hasClear && state . value ) ,
201- 'has-errors' : state . isInvalid && props . errorPlacement === 'inside' ,
202- size : props . size ,
203- } ) }
204- onClick = { ( ) => {
205- setIsActive ( true ) ;
206- setOpen ( true ) ;
207- } }
208- />
209- ) : null }
21093 < HiddenInput
21194 name = { props . name }
21295 form = { props . form }
0 commit comments