Skip to content

Commit 45a8e95

Browse files
authored
feat: Adds support for custom formats in date input (#3686)
1 parent da6e199 commit 45a8e95

File tree

17 files changed

+566
-75
lines changed

17 files changed

+566
-75
lines changed

pages/date-input/common.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { DateInputProps } from '~components';
5+
6+
export const getPlaceholder = ({
7+
granularity,
8+
inputFormat,
9+
format,
10+
}: {
11+
granularity: DateInputProps.Granularity;
12+
inputFormat: undefined | DateInputProps.InputFormat;
13+
format: undefined | DateInputProps.Format;
14+
}) => {
15+
const isIso = inputFormat === 'iso' || (inputFormat === undefined && format === 'iso');
16+
const separator = isIso ? '-' : '/';
17+
return `YYYY${separator}${granularity === 'month' ? 'MM' : 'MM' + separator + 'DD'}`;
18+
};
19+
20+
export const locales = [
21+
'ar',
22+
'de',
23+
'en-GB',
24+
'en-US',
25+
'es',
26+
'fr',
27+
'he',
28+
'id',
29+
'it',
30+
'ja',
31+
'ko',
32+
'ms',
33+
'pt-BR',
34+
'th',
35+
'tr',
36+
'vi',
37+
'zh-CN',
38+
'zh-TW',
39+
];
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
5+
import Box from '~components/box';
6+
import DateInput, { DateInputProps } from '~components/date-input';
7+
8+
import createPermutations from '../utils/permutations';
9+
import PermutationsView from '../utils/permutations-view';
10+
import ScreenshotArea from '../utils/screenshot-area';
11+
import { locales } from './common';
12+
13+
const permutationsFormats = createPermutations<DateInputProps>([
14+
{
15+
value: ['2020-01-02'],
16+
ariaLabel: ['Some label'],
17+
format: ['iso', 'slashed', 'long-localized'],
18+
granularity: ['day', 'month'],
19+
},
20+
]);
21+
22+
const permutationsLongLocalizedLocales = createPermutations<DateInputProps>([
23+
{
24+
value: ['2020-01-02'],
25+
ariaLabel: ['Some label'],
26+
format: ['long-localized'],
27+
granularity: ['day', 'month'],
28+
locale: locales,
29+
},
30+
]);
31+
32+
export default function DateInputPermutations() {
33+
return (
34+
<Box padding="l">
35+
<h1>Date Input permutations - formats</h1>
36+
<ScreenshotArea>
37+
<PermutationsView
38+
permutations={permutationsFormats}
39+
render={permutation => <DateInput {...permutation} onChange={() => {}} />}
40+
/>
41+
42+
<br />
43+
<hr />
44+
<br />
45+
46+
<PermutationsView
47+
permutations={permutationsLongLocalizedLocales}
48+
render={permutation => <DateInput {...permutation} onChange={() => {}} />}
49+
/>
50+
</ScreenshotArea>
51+
</Box>
52+
);
53+
}

pages/date-input/permutations.page.tsx renamed to pages/date-input/permutations-states.page.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,11 @@ const permutations = createPermutations<DateInputProps>([
2323
export default function DateInputPermutations() {
2424
return (
2525
<Box padding="l">
26-
<h1>Date Input permutations</h1>
27-
<ScreenshotArea disableAnimations={true}>
26+
<h1>Date Input permutations - states</h1>
27+
<ScreenshotArea>
2828
<PermutationsView
2929
permutations={permutations}
30-
render={permutation => (
31-
<DateInput
32-
onChange={() => {
33-
/*empty handler to suppress react controlled property warning*/
34-
}}
35-
{...permutation}
36-
/>
37-
)}
30+
render={permutation => <DateInput {...permutation} onChange={() => {}} />}
3831
/>
3932
</ScreenshotArea>
4033
</Box>

pages/date-input/simple.page.tsx

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,33 @@
22
// SPDX-License-Identifier: Apache-2.0
33
import React, { useContext, useEffect, useState } from 'react';
44

5-
import { Box, Checkbox, SpaceBetween } from '~components';
5+
import { Box, Select, SelectProps, SpaceBetween, Toggle } from '~components';
66
import DateInput, { DateInputProps } from '~components/date-input';
77

88
import { AppContextType } from '../app/app-context';
99
import AppContext from '../app/app-context';
10+
import { getPlaceholder, locales } from './common';
1011

11-
export type DateInputDemoContext = React.Context<
12+
export type PageContext = React.Context<
1213
AppContextType<{
13-
monthOnly?: boolean;
14+
granularity?: DateInputProps.Granularity;
15+
readOnly?: boolean;
16+
disabled?: boolean;
17+
inputFormat?: DateInputProps.InputFormat;
18+
format?: DateInputProps.Format;
19+
locale?: string;
1420
hasValue?: boolean;
1521
}>
1622
>;
1723

24+
const inputFormatOptions: SelectProps.Option[] = [{ value: 'iso' }, { value: 'slashed' }];
25+
26+
const formatOptions: SelectProps.Option[] = [...inputFormatOptions, { value: 'long-localized' }];
27+
28+
const localeOptions: SelectProps.Option[] = locales.map(locale => ({ value: locale }));
29+
1830
export default function DateInputScenario() {
19-
const { urlParams, setUrlParams } = useContext(AppContext as DateInputDemoContext);
31+
const { urlParams, setUrlParams } = useContext(AppContext as PageContext);
2032
const initValue = '2025-02-14';
2133
const hasValue = urlParams.hasValue ?? false;
2234
const [value, setValue] = useState<DateInputProps['value']>('');
@@ -33,21 +45,81 @@ export default function DateInputScenario() {
3345
<Box padding="l">
3446
<SpaceBetween direction="vertical" size="m">
3547
<h1>Date input</h1>
36-
<SpaceBetween direction="horizontal" size="s">
37-
<Checkbox checked={hasValue} onChange={({ detail }) => setUrlParams({ hasValue: detail.checked })}>
38-
Has initial value
39-
</Checkbox>
48+
49+
<SpaceBetween size="m">
50+
<SpaceBetween direction="horizontal" size="s">
51+
<Toggle
52+
checked={urlParams.granularity === 'month'}
53+
onChange={({ detail }) => setUrlParams({ granularity: detail.checked ? 'month' : 'day' })}
54+
>
55+
Month only
56+
</Toggle>
57+
<Toggle
58+
checked={!!urlParams.readOnly}
59+
onChange={({ detail }) => setUrlParams({ readOnly: detail.checked })}
60+
>
61+
Read-only
62+
</Toggle>
63+
<Toggle
64+
checked={!!urlParams.disabled}
65+
onChange={({ detail }) => setUrlParams({ disabled: detail.checked })}
66+
>
67+
Disabled
68+
</Toggle>
69+
<Toggle checked={hasValue} onChange={({ detail }) => setUrlParams({ hasValue: detail.checked })}>
70+
Has initial value
71+
</Toggle>
72+
</SpaceBetween>
73+
74+
<SpaceBetween direction="horizontal" size="s">
75+
<div style={{ minWidth: 200 }}>
76+
<Select
77+
inlineLabelText="Input format"
78+
options={inputFormatOptions}
79+
selectedOption={inputFormatOptions.find(o => o.value === urlParams.inputFormat) ?? null}
80+
onChange={({ detail }) =>
81+
setUrlParams({ inputFormat: detail.selectedOption.value as DateInputProps.InputFormat })
82+
}
83+
/>
84+
</div>
85+
86+
<div style={{ minWidth: 200 }}>
87+
<Select
88+
inlineLabelText="Format"
89+
options={formatOptions}
90+
selectedOption={formatOptions.find(o => o.value === urlParams.format) ?? null}
91+
onChange={({ detail }) =>
92+
setUrlParams({ format: detail.selectedOption.value as DateInputProps.Format })
93+
}
94+
/>
95+
</div>
96+
97+
<div style={{ minWidth: 200 }}>
98+
<Select
99+
inlineLabelText="Locale"
100+
options={localeOptions}
101+
selectedOption={localeOptions.find(o => o.value === urlParams.locale) ?? null}
102+
onChange={({ detail }) => setUrlParams({ locale: detail.selectedOption.value })}
103+
/>
104+
</div>
105+
</SpaceBetween>
40106
</SpaceBetween>
107+
41108
<DateInput
42109
className="testing-date-input"
43110
name="date"
44111
ariaLabel="Enter the date in YYYY/MM/DD"
45-
placeholder="YYYY/MM/DD"
46-
onChange={e => setValue(e.detail.value)}
112+
placeholder={getPlaceholder({
113+
granularity: urlParams.granularity ?? 'day',
114+
inputFormat: urlParams.inputFormat,
115+
format: urlParams.format,
116+
})}
47117
value={value}
118+
onChange={e => setValue(e.detail.value)}
119+
{...urlParams}
48120
/>
49-
<b>Raw value</b>
50-
<pre>{JSON.stringify(value, undefined, 2)}</pre>
121+
122+
<Box variant="code">Raw value (iso): {JSON.stringify(value, undefined, 2)}</Box>
51123
</SpaceBetween>
52124
</Box>
53125
);

pages/date-range-picker/absolute-format.localization.page.tsx

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, { useContext, useState } from 'react';
55
import { Box, DateRangePicker, DateRangePickerProps, FormField, Grid, SpaceBetween } from '~components';
66

77
import AppContext from '../app/app-context';
8+
import { locales } from '../date-input/common';
89
import {
910
applyDisabledReason,
1011
checkIfDisabled,
@@ -16,27 +17,6 @@ import {
1617
isValid,
1718
} from './common';
1819

19-
const locales = [
20-
'ar',
21-
'de',
22-
'en-GB',
23-
'en-US',
24-
'es',
25-
'fr',
26-
'he',
27-
'id',
28-
'it',
29-
'ja',
30-
'ko',
31-
'ms',
32-
'pt-BR',
33-
'th',
34-
'tr',
35-
'vi',
36-
'zh-CN',
37-
'zh-TW',
38-
];
39-
4020
const rtlLocales = new Set(['ar', 'he']);
4121

4222
const initialRange = {

src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9183,6 +9183,39 @@ receive focus.",
91839183
"optional": true,
91849184
"type": "boolean",
91859185
},
9186+
{
9187+
"description": "The format as it is displayed. It can take the following values:
9188+
* \`iso\`: ISO 8601 format without time, e.g.: 2024-01-30 (or 2024-01).
9189+
* \`long-localized\`: a more human-readable, localized format, e.g.: January 30, 2024 (or January, 2024).
9190+
* \`slashed\`: similar to ISO 8601 but with '/' in place of '-'. e.g.: 2024/01/30 (or 2024/01).",
9191+
"inlineType": {
9192+
"name": "DateFormat",
9193+
"type": "union",
9194+
"values": [
9195+
"iso",
9196+
"long-localized",
9197+
"slashed",
9198+
],
9199+
},
9200+
"name": "format",
9201+
"optional": true,
9202+
"type": "string",
9203+
},
9204+
{
9205+
"description": "Specifies the granularity at which users will be able to select a date.
9206+
Defaults to \`day\`.",
9207+
"inlineType": {
9208+
"name": "DateGranularity",
9209+
"type": "union",
9210+
"values": [
9211+
"day",
9212+
"month",
9213+
],
9214+
},
9215+
"name": "granularity",
9216+
"optional": true,
9217+
"type": "string",
9218+
},
91869219
{
91879220
"deprecatedTag": "The usage of the \`id\` attribute is reserved for internal use cases. For testing and other use cases,
91889221
use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes). If you must
@@ -9192,6 +9225,23 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
91929225
"optional": true,
91939226
"type": "string",
91949227
},
9228+
{
9229+
"description": "Specifies the date format to use when the format is 'long-localized' and editing the date.
9230+
The format of the input as it is being interacted with. It can take the following values:
9231+
* \`iso\`: ISO 8601 format without time, e.g.: 2024-01-30 (or 2024-01).
9232+
* \`slashed\`: similar to ISO 8601 but with '/' in place of '-'. e.g.: 2024/01/30 (or 2024/01).",
9233+
"inlineType": {
9234+
"name": "EditableDateFormat",
9235+
"type": "union",
9236+
"values": [
9237+
"iso",
9238+
"slashed",
9239+
],
9240+
},
9241+
"name": "inputFormat",
9242+
"optional": true,
9243+
"type": "string",
9244+
},
91959245
{
91969246
"description": "Overrides the invalidation state. Usually the invalid state
91979247
comes from the parent \`FormField\`component,
@@ -9202,6 +9252,14 @@ single form field.",
92029252
"optional": true,
92039253
"type": "boolean",
92049254
},
9255+
{
9256+
"description": "Specifies the locale to use to render month names and determine the starting day of the week.
9257+
If you don't provide this, the locale is determined by the page and browser locales.
9258+
Supported values and formats are listed in the [JavaScript Intl API specification](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_identification_and_negotiation).",
9259+
"name": "locale",
9260+
"optional": true,
9261+
"type": "string",
9262+
},
92059263
{
92069264
"description": "Specifies the name of the control used in HTML forms.",
92079265
"name": "name",

src/date-input/__integ__/date-input.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class DateInputPage extends BasePageObject {
3636

3737
async type(text: string) {
3838
// `await this.keys(text);` doesn't work as it key presses too quickly and doesn't
39-
// allow the seperator to be appended so the cursor position gets messed up.
39+
// allow the separator to be appended so the cursor position gets messed up.
4040
for (let k = 0; k < text.length; k++) {
4141
await this.keys(text[k]);
4242
}

0 commit comments

Comments
 (0)