Skip to content

Commit 84b619d

Browse files
committed
Extract PlainDateSegments into component
1 parent 4d8df9f commit 84b619d

File tree

4 files changed

+151
-77
lines changed

4 files changed

+151
-77
lines changed

packages/circuit-ui/components/DateInput/DateInput.tsx

Lines changed: 26 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ import { applyMultipleRefs } from '../../util/refs.js';
5353
import { changeInputValue } from '../../util/input-value.js';
5454

5555
import { Dialog } from './components/Dialog.js';
56-
import { DateSegment } from './components/DateSegment.js';
56+
import { PlainDateSegments } from './components/PlainDateSegments.js';
5757
import { usePlainDateState } from './hooks/usePlainDateState.js';
5858
import { useSegmentFocus } from './hooks/useSegmentFocus.js';
59-
import { getCalendarButtonLabel, getDateSegments } from './DateInputService.js';
59+
import { getCalendarButtonLabel, getDateParts } from './DateInputService.js';
6060
import classes from './DateInput.module.css';
6161

6262
export interface DateInputProps
@@ -292,7 +292,7 @@ export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
292292

293293
const dialogStyles = isMobile ? mobileStyles : floatingStyles;
294294

295-
const segments = getDateSegments(locale);
295+
const parts = getDateParts(locale);
296296
const calendarButtonLabel = getCalendarButtonLabel(
297297
openCalendarButtonLabel,
298298
state.date,
@@ -389,68 +389,20 @@ export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
389389
readOnly && classes.readonly,
390390
)}
391391
>
392-
{segments.map((segment, index) => {
393-
const segmentProps = {
394-
required,
395-
invalid,
396-
disabled,
397-
readOnly,
398-
focus,
399-
// Only the first segment should be associated with the validation hint to reduce verbosity.
400-
'aria-describedby': index === 0 ? descriptionIds : undefined,
401-
};
402-
switch (segment.type) {
403-
case 'year':
404-
return (
405-
<DateSegment
406-
key={segment.type}
407-
aria-label={yearInputLabel}
408-
autoComplete={
409-
autoComplete === 'bday' ? 'bday-year' : undefined
410-
}
411-
{...segmentProps}
412-
{...state.props.year}
413-
/>
414-
);
415-
case 'month':
416-
return (
417-
<DateSegment
418-
key={segment.type}
419-
aria-label={monthInputLabel}
420-
autoComplete={
421-
autoComplete === 'bday' ? 'bday-month' : undefined
422-
}
423-
{...segmentProps}
424-
{...state.props.month}
425-
/>
426-
);
427-
case 'day':
428-
return (
429-
<DateSegment
430-
key={segment.type}
431-
aria-label={dayInputLabel}
432-
autoComplete={
433-
autoComplete === 'bday' ? 'bday-day' : undefined
434-
}
435-
{...segmentProps}
436-
{...state.props.day}
437-
/>
438-
);
439-
case 'literal':
440-
return (
441-
<div
442-
// biome-ignore lint/suspicious/noArrayIndexKey: The order of the literals is static
443-
key={segment.type + index}
444-
className={classes.literal}
445-
aria-hidden="true"
446-
>
447-
{segment.value}
448-
</div>
449-
);
450-
default:
451-
return null;
452-
}
453-
})}
392+
<PlainDateSegments
393+
parts={parts}
394+
state={state}
395+
focus={focus}
396+
yearInputLabel={yearInputLabel}
397+
monthInputLabel={monthInputLabel}
398+
dayInputLabel={dayInputLabel}
399+
aria-describedby={descriptionIds}
400+
required={required}
401+
invalid={invalid}
402+
disabled={disabled}
403+
readOnly={readOnly}
404+
autoComplete={autoComplete}
405+
/>
454406
</div>
455407
<IconButton
456408
icon={CalendarIcon}
@@ -519,13 +471,15 @@ export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
519471
{clearDateButtonLabel}
520472
</Button>
521473
)}
522-
<Button
523-
variant="primary"
524-
onClick={handleApply}
525-
className={classes.apply}
526-
>
527-
{applyDateButtonLabel}
528-
</Button>
474+
{isMobile && (
475+
<Button
476+
variant="primary"
477+
onClick={handleApply}
478+
className={classes.apply}
479+
>
480+
{applyDateButtonLabel}
481+
</Button>
482+
)}
529483
</div>
530484
)}
531485
</div>

packages/circuit-ui/components/DateInput/DateInputService.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616
import { describe, expect, it } from 'vitest';
1717
import { Temporal } from 'temporal-polyfill';
1818

19-
import { getCalendarButtonLabel, getDateSegments } from './DateInputService.js';
19+
import { getCalendarButtonLabel, getDateParts } from './DateInputService.js';
2020

2121
describe('DateInputService', () => {
22-
describe('getDateSegments', () => {
22+
describe('getDateParts', () => {
2323
it.each([
2424
// locale, year, month, day
2525
['en-US', [4, 0, 2]],
2626
['de-DE', [4, 2, 0]],
2727
['pt-BR', [4, 2, 0]],
2828
])('should order the segments for the %s locale', (locale, indices) => {
29-
const actual = getDateSegments(locale);
29+
const actual = getDateParts(locale);
3030
const year = actual.findIndex(({ type }) => type === 'year');
3131
const month = actual.findIndex(({ type }) => type === 'month');
3232
const day = actual.findIndex(({ type }) => type === 'day');
@@ -39,7 +39,7 @@ describe('DateInputService', () => {
3939
['de-DE', '.'],
4040
['pt-BR', '/'],
4141
])('should return the literal for the %s locale', (locale, literal) => {
42-
const actual = getDateSegments(locale);
42+
const actual = getDateParts(locale);
4343
const literalSegment = actual.find(({ type }) => type === 'literal');
4444
expect(literalSegment?.value).toBe(literal);
4545
});

packages/circuit-ui/components/DateInput/DateInputService.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,26 @@ import type { Locale } from '../../util/i18n.js';
2020

2121
const TEST_VALUE = new Temporal.PlainDate(2024, 3, 8);
2222

23-
export function getDateSegments(locale?: Locale) {
23+
export type DatePart =
24+
| { type: 'literal'; value: string }
25+
| {
26+
type:
27+
| 'day'
28+
| 'dayPeriod'
29+
| 'era'
30+
| 'hour'
31+
| 'minute'
32+
| 'month'
33+
| 'second'
34+
| 'timeZoneName'
35+
| 'weekday'
36+
| 'year'
37+
| 'unknown'
38+
| 'date';
39+
value?: never;
40+
};
41+
42+
export function getDateParts(locale?: Locale): DatePart[] {
2443
const parts = formatDateTimeToParts(TEST_VALUE, locale);
2544
return parts.map(({ type, value }) =>
2645
type === 'literal' ? { type, value } : { type },
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright 2024, SumUp Ltd.
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import type { DatePart } from '../DateInputService.js';
17+
import type { PlainDateState } from '../hooks/usePlainDateState.js';
18+
import classes from '../DateInput.module.css';
19+
20+
import { DateSegment, type DateSegmentProps } from './DateSegment.js';
21+
22+
export interface PlainDateSegmentsProps
23+
extends Pick<
24+
DateSegmentProps,
25+
| 'focus'
26+
| 'required'
27+
| 'invalid'
28+
| 'disabled'
29+
| 'readOnly'
30+
| 'aria-describedby'
31+
> {
32+
parts: DatePart[];
33+
state: PlainDateState;
34+
yearInputLabel: string;
35+
monthInputLabel: string;
36+
dayInputLabel: string;
37+
autoComplete?: 'bday';
38+
}
39+
40+
export function PlainDateSegments({
41+
parts,
42+
state,
43+
yearInputLabel,
44+
monthInputLabel,
45+
dayInputLabel,
46+
'aria-describedby': descriptionId,
47+
autoComplete,
48+
...props
49+
}: PlainDateSegmentsProps) {
50+
return parts.map((part, index) => {
51+
switch (part.type) {
52+
case 'year':
53+
return (
54+
<DateSegment
55+
key={part.type}
56+
aria-label={yearInputLabel}
57+
// Only associate the first segment with the validation hint to reduce verbosity
58+
aria-describedby={index === 0 ? descriptionId : undefined}
59+
autoComplete={autoComplete === 'bday' ? 'bday-year' : undefined}
60+
{...props}
61+
{...state.props.year}
62+
/>
63+
);
64+
case 'month':
65+
return (
66+
<DateSegment
67+
key={part.type}
68+
aria-label={monthInputLabel}
69+
aria-describedby={index === 0 ? descriptionId : undefined}
70+
autoComplete={autoComplete === 'bday' ? 'bday-month' : undefined}
71+
{...props}
72+
{...state.props.month}
73+
/>
74+
);
75+
case 'day':
76+
return (
77+
<DateSegment
78+
key={part.type}
79+
aria-label={dayInputLabel}
80+
aria-describedby={index === 0 ? descriptionId : undefined}
81+
autoComplete={autoComplete === 'bday' ? 'bday-day' : undefined}
82+
{...props}
83+
{...state.props.day}
84+
/>
85+
);
86+
case 'literal':
87+
return (
88+
<div
89+
// biome-ignore lint/suspicious/noArrayIndexKey: The order of the literals is static
90+
key={part.type + index}
91+
className={classes.literal}
92+
aria-hidden="true"
93+
>
94+
{part.value}
95+
</div>
96+
);
97+
default:
98+
return null;
99+
}
100+
});
101+
}

0 commit comments

Comments
 (0)