Skip to content

Commit a4f8ec3

Browse files
authored
Add shouldForceLeadingZeros leading zeros prop to DatePicker (#4671)
1 parent 1d1cb8b commit a4f8ec3

File tree

10 files changed

+64
-5
lines changed

10 files changed

+64
-5
lines changed

packages/@react-aria/datepicker/src/useDatePicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
124124
placeholderValue: props.placeholderValue,
125125
hideTimeZone: props.hideTimeZone,
126126
hourCycle: props.hourCycle,
127+
shouldForceLeadingZeros: props.shouldForceLeadingZeros,
127128
granularity: props.granularity,
128129
isDisabled: props.isDisabled,
129130
isReadOnly: props.isReadOnly,

packages/@react-aria/datepicker/src/useDateRangePicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
9898
hideTimeZone: props.hideTimeZone,
9999
hourCycle: props.hourCycle,
100100
granularity: props.granularity,
101+
shouldForceLeadingZeros: props.shouldForceLeadingZeros,
101102
isDisabled: props.isDisabled,
102103
isReadOnly: props.isReadOnly,
103104
isRequired: props.isRequired,

packages/@react-spectrum/datepicker/stories/DateField.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export default {
109109
hideTimeZone: {
110110
control: 'boolean'
111111
},
112+
shouldForceLeadingZeros: {
113+
control: 'boolean'
114+
},
112115
isDisabled: {
113116
control: 'boolean'
114117
},

packages/@react-spectrum/datepicker/stories/DatePicker.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ export default {
121121
hideTimeZone: {
122122
control: 'boolean'
123123
},
124+
shouldForceLeadingZeros: {
125+
control: 'boolean'
126+
},
124127
isDisabled: {
125128
control: 'boolean'
126129
},

packages/@react-spectrum/datepicker/stories/DateRangePicker.stories.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ HourCycle24.story = {
7878
name: 'hourCycle: 24'
7979
};
8080

81+
export const ForceLeadingZeros = () => render({defaultValue: {start: new CalendarDate(2020, 2, 3), end: new CalendarDate(2020, 5, 4)}, shouldForceLeadingZeros: true});
82+
83+
ForceLeadingZeros.story = {
84+
name: 'shouldForceLeadingZeros'
85+
};
86+
8187
export const IsDisabled = () => render({isDisabled: true, value: {start: new CalendarDate(2020, 2, 3), end: new CalendarDate(2020, 5, 4)}});
8288

8389
IsDisabled.story = {

packages/@react-spectrum/datepicker/stories/TimeField.stories.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ HideTimeZone.story = {
8383
name: 'hideTimeZone'
8484
};
8585

86+
export const ForceLeadingZeros = () => render({defaultValue: parseTime('08:00'), shouldForceLeadingZeros: true});
87+
88+
ForceLeadingZeros.story = {
89+
name: 'shouldForceLeadingZeros'
90+
};
91+
8692
export const IsDisabled = () => render({isDisabled: true, value: new Time(2, 35)});
8793

8894
IsDisabled.story = {

packages/@react-spectrum/datepicker/test/DatePickerBase.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,23 @@ describe('DatePickerBase', function () {
215215
let button = getAllByRole('button')[0];
216216
expect(button).toHaveAttribute('disabled');
217217
});
218+
219+
it.each`
220+
Name | Component | props
221+
${'DatePicker'} | ${DatePicker} | ${{defaultValue: new CalendarDate(2019, 7, 5)}}
222+
${'DateRangePicker'} | ${DateRangePicker} | ${{defaultValue: {start: new CalendarDate(2019, 7, 5), end: new CalendarDate(2019, 7, 8)}}}
223+
`('$Name should support shouldForceLeadingZeros', ({Component, props}) => {
224+
let {getAllByRole} = render(<Component label="Date" {...props} shouldForceLeadingZeros />);
225+
226+
let segments = getAllByRole('spinbutton');
227+
for (let segment of segments) {
228+
if (segment.getAttribute('data-testid') !== 'year') {
229+
// ignore placeholder text.
230+
let textContent = [...segment.childNodes].map(el => el.nodeType === 3 ? el.textContent : '').join('');
231+
expect(textContent.startsWith('0')).toBeTruthy();
232+
}
233+
}
234+
});
218235
});
219236

220237
describe('calendar popover', function () {

packages/@react-stately/datepicker/src/useDateFieldState.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
183183
timeZone: defaultTimeZone,
184184
hideTimeZone,
185185
hourCycle: props.hourCycle,
186-
showEra
187-
}), [props.maxGranularity, granularity, props.hourCycle, defaultTimeZone, hideTimeZone, showEra]);
186+
showEra,
187+
shouldForceLeadingZeros: props.shouldForceLeadingZeros
188+
}), [props.maxGranularity, granularity, props.hourCycle, props.shouldForceLeadingZeros, defaultTimeZone, hideTimeZone, showEra]);
188189
let opts = useMemo(() => getFormatOptions({}, formatOpts), [formatOpts]);
189190

190191
let dateFormatter = useMemo(() => new DateFormatter(locale, opts), [locale, opts]);

packages/@react-stately/datepicker/src/utils.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ interface FormatterOptions {
2828
granularity?: DatePickerProps<any>['granularity'],
2929
maxGranularity?: 'year' | 'month' | DatePickerProps<any>['granularity'],
3030
hourCycle?: 12 | 24,
31-
showEra?: boolean
31+
showEra?: boolean,
32+
shouldForceLeadingZeros?: boolean
3233
}
3334

3435
const DEFAULT_FIELD_OPTIONS: FieldOptions = {
@@ -40,11 +41,21 @@ const DEFAULT_FIELD_OPTIONS: FieldOptions = {
4041
second: '2-digit'
4142
};
4243

44+
const TWO_DIGIT_FIELD_OPTIONS: FieldOptions = {
45+
year: 'numeric',
46+
month: '2-digit',
47+
day: '2-digit',
48+
hour: '2-digit',
49+
minute: '2-digit',
50+
second: '2-digit'
51+
};
52+
4353
export function getFormatOptions(
4454
fieldOptions: FieldOptions,
4555
options: FormatterOptions
4656
): Intl.DateTimeFormatOptions {
47-
fieldOptions = {...DEFAULT_FIELD_OPTIONS, ...fieldOptions};
57+
let defaultFieldOptions = options.shouldForceLeadingZeros ? TWO_DIGIT_FIELD_OPTIONS : DEFAULT_FIELD_OPTIONS;
58+
fieldOptions = {...defaultFieldOptions, ...fieldOptions};
4859
let granularity = options.granularity || 'minute';
4960
let keys = Object.keys(fieldOptions);
5061
let startIdx = keys.indexOf(options.maxGranularity ?? 'year');

packages/@react-types/datepicker/src/index.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ interface DateFieldBase<T extends DateValue> extends InputBase, Validation, Focu
5252
* Whether to hide the time zone abbreviation.
5353
* @default false
5454
*/
55-
hideTimeZone?: boolean
55+
hideTimeZone?: boolean,
56+
/**
57+
* Whether to always show leading zeros in the month, day, and hour fields.
58+
* By default, this is determined by the user's locale.
59+
*/
60+
shouldForceLeadingZeros?: boolean
5661
}
5762

5863
interface AriaDateFieldBaseProps<T extends DateValue> extends DateFieldBase<T>, AriaLabelingProps, DOMProps {}
@@ -129,6 +134,11 @@ export interface TimePickerProps<T extends TimeValue> extends InputBase, Validat
129134
granularity?: 'hour' | 'minute' | 'second',
130135
/** Whether to hide the time zone abbreviation. */
131136
hideTimeZone?: boolean,
137+
/**
138+
* Whether to always show leading zeros in the hour field.
139+
* By default, this is determined by the user's locale.
140+
*/
141+
shouldForceLeadingZeros?: boolean,
132142
/**
133143
* A placeholder time that influences the format of the placeholder shown when no value is selected.
134144
* Defaults to 12:00 AM or 00:00 depending on the hour cycle.

0 commit comments

Comments
 (0)