Skip to content

Commit 682dc02

Browse files
feat(DateInput, DateRangeInput): support readOnly prop (#8649)
* feat(DateInput, DateRangeInput): add props allowClearButton and readOnly * docs: add examples with readOnly states * fix: fix storybook argTypes * fix: revert prop allowClearButton * fix(DateInput,DateRangeInput): fix clear button show when readOnly
1 parent 028fe98 commit 682dc02

File tree

9 files changed

+121
-10
lines changed

9 files changed

+121
-10
lines changed

packages/vkui/src/components/DateInput/DateInput.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const story: Meta<DateInputProps> = {
1515
parameters: createStoryParameters('DateInput', CanvasFullLayout, DisableCartesianParam),
1616
args: { onChange: fn() },
1717
argTypes: {
18+
readOnly: {
19+
control: { type: 'boolean' },
20+
},
1821
value: {
1922
control: { type: 'date' },
2023
},

packages/vkui/src/components/DateInput/DateInput.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,41 @@ describe('DateInput', () => {
4242
</React.Fragment>
4343
));
4444

45+
it('check correct readonly state', async () => {
46+
jest.useFakeTimers();
47+
const onChange = jest.fn();
48+
render(
49+
<DateInput
50+
value={date}
51+
onChange={onChange}
52+
changeMonthLabel=""
53+
changeYearLabel=""
54+
changeDayLabel=""
55+
changeHoursLabel=""
56+
changeMinutesLabel=""
57+
readOnly={true}
58+
{...testIds}
59+
/>,
60+
);
61+
const inputLikes = getInputsLike();
62+
inputLikes.forEach((inputLike) => {
63+
expect(inputLike).toHaveAttribute('aria-readonly', 'true');
64+
});
65+
66+
const [dates, months, years] = inputLikes;
67+
68+
await userEvent.type(dates, '30');
69+
await userEvent.type(months, '06');
70+
await userEvent.type(years, '2023');
71+
72+
const normalizedDate = convertInputsToNumbers(inputLikes);
73+
expect(normalizedDate).toEqual([31, 7, 2024]);
74+
75+
expect(onChange).toHaveBeenCalledTimes(0);
76+
77+
expect(screen.queryByTestId(testIds.clearButtonTestId)).toBeNull();
78+
});
79+
4580
it('should be correct input value', () => {
4681
const onChange = jest.fn();
4782
render(

packages/vkui/src/components/DateInput/DateInput.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ export const DateInput = ({
258258
onPrevMonth,
259259
prevMonthIcon,
260260
nextMonthIcon,
261-
disableCalendar = false,
262261
renderDayContent,
263262
onCalendarOpenChanged,
264263
calendarTestsProps,
@@ -275,6 +274,8 @@ export const DateInput = ({
275274
timezone,
276275
restoreFocus,
277276
disableFocusTrap,
277+
readOnly,
278+
'disableCalendar': disableCalendarProp = false,
278279
'aria-label': ariaLabel = '',
279280
...props
280281
}: DateInputProps): React.ReactNode => {
@@ -284,6 +285,8 @@ export const DateInput = ({
284285
const hoursRef = React.useRef<HTMLSpanElement>(null);
285286
const minutesRef = React.useRef<HTMLSpanElement>(null);
286287

288+
const disableCalendar = readOnly ? true : disableCalendarProp;
289+
287290
const { value, updateValue, setInternalValue, getLastUpdatedValue, clearValue } =
288291
useDateInputValue({
289292
value: valueProp,
@@ -342,7 +345,7 @@ export const DateInput = ({
342345
maxElement,
343346
refs,
344347
autoFocus,
345-
disabled,
348+
disabled: disabled || readOnly,
346349
elementsConfig,
347350
onClear: clearValue,
348351
onInternalValueChange,
@@ -441,7 +444,7 @@ export const DateInput = ({
441444
<Icon20CalendarOutline />
442445
</IconButton>
443446
) : null}
444-
{value ? (
447+
{value && !readOnly ? (
445448
<IconButton
446449
hoverMode="opacity"
447450
label={clearFieldLabel}
@@ -466,7 +469,7 @@ export const DateInput = ({
466469
Component="input"
467470
readOnly
468471
aria-hidden
469-
tabIndex={-1}
472+
tabIndex={readOnly ? 0 : -1}
470473
name={name}
471474
value={value ? format(value, enableTime ? "dd.MM.yyyy'T'HH:mm" : 'dd.MM.yyyy') : ''}
472475
onFocus={handleFieldEnter}
@@ -489,6 +492,7 @@ export const DateInput = ({
489492
onKeyDown={handleKeyDown}
490493
onElementSelect={setFocusedElement}
491494
label={changeDayLabel}
495+
readOnly={readOnly}
492496
data-testid={dayFieldTestId}
493497
/>
494498
<InputLikeDivider>.</InputLikeDivider>
@@ -501,6 +505,7 @@ export const DateInput = ({
501505
index={1}
502506
onElementSelect={setFocusedElement}
503507
onKeyDown={handleKeyDown}
508+
readOnly={readOnly}
504509
label={changeMonthLabel}
505510
data-testid={monthFieldTestId}
506511
/>
@@ -513,6 +518,7 @@ export const DateInput = ({
513518
getRootRef={yearsRef}
514519
index={2}
515520
onElementSelect={setFocusedElement}
521+
readOnly={readOnly}
516522
label={changeYearLabel}
517523
onKeyDown={handleKeyDown}
518524
data-testid={yearFieldTestId}
@@ -528,6 +534,7 @@ export const DateInput = ({
528534
getRootRef={hoursRef}
529535
index={3}
530536
onElementSelect={setFocusedElement}
537+
readOnly={readOnly}
531538
label={changeHoursLabel}
532539
onKeyDown={handleKeyDown}
533540
data-testid={hourFieldTestId}
@@ -541,6 +548,7 @@ export const DateInput = ({
541548
getRootRef={minutesRef}
542549
index={4}
543550
onElementSelect={setFocusedElement}
551+
readOnly={readOnly}
544552
label={changeMinutesLabel}
545553
onKeyDown={handleKeyDown}
546554
data-testid={minuteFieldTestId}

packages/vkui/src/components/DateInput/Readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const Example = () => {
7474
const [showNeighboringMonth, setShowNeighboringMonth] = useState(false);
7575
const [disableCalendar, setDisableCalendar] = useState(false);
7676
const [accessible, setAccessible] = useState(false);
77+
const [readOnly, setReadOnly] = useState(false);
7778
const [locale, setLocale] = useState('ru');
7879

7980
return (
@@ -121,6 +122,11 @@ const Example = () => {
121122
Включено
122123
</Checkbox>
123124
</FormItem>
125+
<FormItem top="Readonly режим">
126+
<Checkbox checked={readOnly} onChange={(e) => setReadOnly(e.target.checked)}>
127+
Включено
128+
</Checkbox>
129+
</FormItem>
124130
<FormItem top="Локаль">
125131
<Select
126132
style={{ width: 100 }}
@@ -162,6 +168,7 @@ const Example = () => {
162168
showNeighboringMonth={showNeighboringMonth}
163169
disableCalendar={disableCalendar}
164170
accessible={accessible}
171+
readOnly={readOnly}
165172
/>
166173
</LocaleProvider>
167174
</Flex>

packages/vkui/src/components/DateRangeInput/DateRangeInput.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const story: Meta<StoryDateRangeInputProps> = {
1515
component: DateRangeInput,
1616
parameters: createStoryParameters('DateRangeInput', CanvasFullLayout, DisableCartesianParam),
1717
argTypes: {
18+
readOnly: {
19+
control: { type: 'boolean' },
20+
},
1821
value: {
1922
description: 'Используйте startDate и endDate для задания периода',
2023
control: false,

packages/vkui/src/components/DateRangeInput/DateRangeInput.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,42 @@ describe('DateRangeInput', () => {
6565
expect(normalizedDate).toEqual([20, 7, 2024, 31, 7, 2024]);
6666
});
6767

68+
it('check correct readonly state', async () => {
69+
jest.useFakeTimers();
70+
render(
71+
<DateRangeInput
72+
value={[startDate, endDate]}
73+
changeStartDayLabel=""
74+
changeStartMonthLabel=""
75+
changeStartYearLabel=""
76+
changeEndDayLabel=""
77+
changeEndMonthLabel=""
78+
changeEndYearLabel=""
79+
readOnly={true}
80+
{...testsProps}
81+
/>,
82+
);
83+
const inputLikes = getInputsLike();
84+
const [startDates, startMonths, startYears, endDates, endMonths, endYears] = inputLikes;
85+
86+
inputLikes.forEach((inputLike) => {
87+
expect(inputLike).toHaveAttribute('aria-readonly', 'true');
88+
});
89+
90+
await userEvent.type(startDates, '10');
91+
await userEvent.type(startMonths, '04');
92+
await userEvent.type(startYears, '2023');
93+
94+
await userEvent.type(endDates, '15');
95+
await userEvent.type(endMonths, '06');
96+
await userEvent.type(endYears, '2024');
97+
98+
const normalizedDate = convertInputsToNumbers(inputLikes);
99+
expect(normalizedDate).toEqual([20, 7, 2024, 31, 7, 2024]);
100+
101+
expect(screen.queryByTestId(testsProps.clearButtonTestId)).toBeNull();
102+
});
103+
68104
it('should correct update value when typing text in input', async () => {
69105
jest.useFakeTimers();
70106
const onChange = jest.fn();

packages/vkui/src/components/DateRangeInput/DateRangeInput.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ export const DateRangeInput = ({
251251
'aria-label': ariaLabel = '',
252252
prevMonthIcon,
253253
nextMonthIcon,
254-
disableCalendar = false,
255254
onCalendarOpenChanged,
256255
renderDayContent,
257256
calendarTestsProps,
@@ -261,6 +260,8 @@ export const DateRangeInput = ({
261260
showCalendarButtonTestId,
262261
id,
263262
accessible,
263+
readOnly,
264+
'disableCalendar': disableCalendarProp = false,
264265
...props
265266
}: DateRangeInputProps): React.ReactNode => {
266267
const daysStartRef = React.useRef<HTMLSpanElement>(null);
@@ -270,6 +271,8 @@ export const DateRangeInput = ({
270271
const monthsEndRef = React.useRef<HTMLSpanElement>(null);
271272
const yearsEndRef = React.useRef<HTMLSpanElement>(null);
272273

274+
const disableCalendar = readOnly ? true : disableCalendarProp;
275+
273276
const _onChange = React.useCallback(
274277
(newValue: DateRangeType | null | undefined) => onChange?.(newValue || undefined),
275278
[onChange],
@@ -349,7 +352,7 @@ export const DateRangeInput = ({
349352
maxElement: 5,
350353
refs,
351354
autoFocus,
352-
disabled,
355+
disabled: disabled || readOnly,
353356
elementsConfig,
354357
onClear,
355358
onInternalValueChange,
@@ -434,7 +437,7 @@ export const DateRangeInput = ({
434437
<Icon20CalendarOutline />
435438
</IconButton>
436439
) : null}
437-
{value ? (
440+
{value && !readOnly ? (
438441
<IconButton
439442
hoverMode="opacity"
440443
label={clearFieldLabel}
@@ -459,8 +462,8 @@ export const DateRangeInput = ({
459462
Component="input"
460463
readOnly
461464
aria-hidden
462-
tabIndex={-1}
463465
name={name}
466+
tabIndex={readOnly ? 0 : -1}
464467
value={
465468
value
466469
? `${value[0] ? format(value[0], 'dd.MM.yyyy') : ''} - ${
@@ -475,10 +478,11 @@ export const DateRangeInput = ({
475478
value={internalValue[0]}
476479
minValue={1}
477480
maxValue={31}
478-
onKeyDown={handleKeyDown}
481+
onKeyDown={readOnly ? undefined : handleKeyDown}
479482
length={2}
480483
getRootRef={daysStartRef}
481484
index={0}
485+
readOnly={readOnly}
482486
onElementSelect={setFocusedElement}
483487
label={changeStartDayLabel}
484488
data-testid={startDateTestsProps?.day}
@@ -493,6 +497,7 @@ export const DateRangeInput = ({
493497
getRootRef={monthsStartRef}
494498
index={1}
495499
onElementSelect={setFocusedElement}
500+
readOnly={readOnly}
496501
label={changeStartMonthLabel}
497502
data-testid={startDateTestsProps?.month}
498503
/>
@@ -506,6 +511,7 @@ export const DateRangeInput = ({
506511
getRootRef={yearsStartRef}
507512
index={2}
508513
onElementSelect={setFocusedElement}
514+
readOnly={readOnly}
509515
label={changeStartYearLabel}
510516
data-testid={startDateTestsProps?.year}
511517
/>
@@ -519,6 +525,7 @@ export const DateRangeInput = ({
519525
getRootRef={daysEndRef}
520526
index={3}
521527
onElementSelect={setFocusedElement}
528+
readOnly={readOnly}
522529
label={changeEndDayLabel}
523530
data-testid={endDateTestsProps?.day}
524531
/>
@@ -532,6 +539,7 @@ export const DateRangeInput = ({
532539
getRootRef={monthsEndRef}
533540
index={4}
534541
onElementSelect={setFocusedElement}
542+
readOnly={readOnly}
535543
label={changeEndMonthLabel}
536544
data-testid={endDateTestsProps?.month}
537545
/>
@@ -545,6 +553,7 @@ export const DateRangeInput = ({
545553
getRootRef={yearsEndRef}
546554
index={5}
547555
onElementSelect={setFocusedElement}
556+
readOnly={readOnly}
548557
label={changeEndYearLabel}
549558
data-testid={endDateTestsProps?.year}
550559
/>

packages/vkui/src/components/DateRangeInput/Readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const Example = () => {
8484
const [closeOnChange, setCloseOnChange] = useState(true);
8585
const [disableCalendar, setDisableCalendar] = useState(false);
8686
const [accessible, setAccessible] = useState(false);
87+
const [readOnly, setReadOnly] = useState(false);
8788
const [locale, setLocale] = useState('ru');
8889

8990
return (
@@ -118,6 +119,11 @@ const Example = () => {
118119
Включено
119120
</Checkbox>
120121
</FormItem>
122+
<FormItem top="Readonly режим">
123+
<Checkbox checked={readOnly} onChange={(e) => setReadOnly(e.target.checked)}>
124+
Включено
125+
</Checkbox>
126+
</FormItem>
121127
<FormItem top="Локаль">
122128
<Select
123129
style={{ width: 100 }}
@@ -157,6 +163,7 @@ const Example = () => {
157163
disablePickers={disablePickers}
158164
disableCalendar={disableCalendar}
159165
accessible={accessible}
166+
readOnly={readOnly}
160167
/>
161168
</LocaleProvider>
162169
</Flex>

packages/vkui/src/components/InputLike/InputLike.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export const InputLike = ({
3737
onElementSelect,
3838
onFocus,
3939
label,
40+
readOnly,
41+
onKeyDown,
4042
...restProps
4143
}: InputLikeProps) => {
4244
const handleElementSelect = React.useCallback(
@@ -51,8 +53,9 @@ export const InputLike = ({
5153
<RootComponent
5254
Component="span"
5355
baseClassName={value?.length === length ? styles.host : undefined}
54-
tabIndex={0}
56+
tabIndex={readOnly ? -1 : 0}
5557
onFocus={callMultiple(onFocus, handleElementSelect)}
58+
onKeyDown={readOnly ? undefined : onKeyDown}
5659
{...restProps}
5760
>
5861
{label && <VisuallyHidden>{label}</VisuallyHidden>}

0 commit comments

Comments
 (0)