Skip to content

Commit

Permalink
feat: Add human-readable format option to Date range picker display (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jperals authored Feb 16, 2024
1 parent 2af7864 commit 2c12a6b
Show file tree
Hide file tree
Showing 15 changed files with 639 additions and 52 deletions.
126 changes: 126 additions & 0 deletions pages/date-range-picker/absolute-format.localization.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useState } from 'react';
import { Box, DateRangePicker, DateRangePickerProps, SpaceBetween, Grid } from '~components';
import { i18nStrings, isValid } from './common';
import AppContext, { AppContextType } from '../app/app-context';

const locales = [
'ar',
'de',
'en-GB',
'en-US',
'es',
'fr',
'he',
'id',
'it',
'ja',
'ko',
'ms',
'pt-BR',
'th',
'tr',
'vi',
'zh-CN',
'zh-TW',
];

const rtlLocales = new Set(['ar', 'he']);

type DemoContext = React.Context<
AppContextType<{
absoluteFormat?: DateRangePickerProps.AbsoluteFormat;
dateOnly?: boolean;
hideTimeOffset?: boolean;
timeOffset?: number;
}>
>;

const initialRange = {
startDate: '2024-12-09T00:00:00+01:00',
endDate: '2024-12-31T23:59:59+01:00',
};

export default function DateRangePickerScenario() {
const { urlParams, setUrlParams } = useContext(AppContext as DemoContext);

const [value, setValue] = useState<DateRangePickerProps['value']>({
type: 'absolute',
startDate: urlParams.dateOnly ? initialRange.startDate.slice(0, 10) : initialRange.startDate,
endDate: urlParams.dateOnly ? initialRange.endDate.slice(0, 10) : initialRange.endDate,
});

return (
<Box padding="s">
<SpaceBetween direction="vertical" size="m">
<h1>Absolute date range picker with custom absolute format</h1>
<SpaceBetween direction="horizontal" size="xxl">
<label>
Format{' '}
<select
value={urlParams.absoluteFormat}
onChange={event =>
setUrlParams({
absoluteFormat: event.currentTarget.value as DateRangePickerProps.AbsoluteFormat,
})
}
>
<option value="">(Default)</option>
<option value="long-localized">Localized</option>
</select>
</label>
<label>
<input
type="checkbox"
checked={urlParams.dateOnly}
onChange={event => setUrlParams({ dateOnly: !!event.target.checked })}
/>{' '}
Date only
</label>
<label>
Time offset from UTC in minutes{' '}
<input
type="number"
value={urlParams.timeOffset}
onChange={event => {
const value = parseInt(event.currentTarget.value);
setUrlParams({ timeOffset: isNaN(value) ? 0 : value });
}}
/>
</label>
<label>
<input
type="checkbox"
checked={urlParams.hideTimeOffset}
onChange={event => setUrlParams({ hideTimeOffset: !!event.target.checked })}
/>{' '}
Hide time offset
</label>
</SpaceBetween>
<hr />
{locales.map(locale => (
<div key={`pickers-${locale}`} dir={rtlLocales.has(locale) ? 'rtl' : 'ltr'}>
<Grid gridDefinition={[{ colspan: 1 }, { colspan: 11 }]}>
<div style={{ textAlign: 'right' }}>{locale}</div>
<DateRangePicker
value={value}
locale={locale}
i18nStrings={i18nStrings}
placeholder={'Filter by a date and time range'}
onChange={e => setValue(e.detail.value)}
relativeOptions={[]}
isValidRange={isValid}
rangeSelectorMode={'absolute-only'}
getTimeOffset={urlParams.timeOffset === undefined ? undefined : () => urlParams.timeOffset!}
absoluteFormat={urlParams.absoluteFormat}
dateOnly={urlParams.dateOnly}
hideTimeOffset={urlParams.hideTimeOffset}
/>
</Grid>
</div>
))}
</SpaceBetween>
</Box>
);
}
67 changes: 67 additions & 0 deletions pages/date-range-picker/absolute-format.permutations.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { Box, DateRangePicker, DateRangePickerProps, SpaceBetween } from '~components';
import { i18nStrings, isValid } from './common';
import ScreenshotArea from '../utils/screenshot-area';
import PermutationsView from '../utils/permutations-view';
import createPermutations from '../utils/permutations';

const permutations = createPermutations<
Pick<DateRangePickerProps, 'absoluteFormat' | 'dateOnly' | 'hideTimeOffset' | 'value'>
>([
{
absoluteFormat: ['iso', 'long-localized'],
dateOnly: [true],
value: [
{
type: 'absolute',
startDate: '2024-12-30',
endDate: '2024-12-31',
},
],
},
{
absoluteFormat: ['iso', 'long-localized'],
dateOnly: [false],
hideTimeOffset: [true, false],
value: [
{
type: 'absolute',
startDate: '2024-12-30T00:00:00+01:00',
endDate: '2024-12-31T23:59:59+01:00',
},
],
},
]);

export default function DateRangePickerPermutations() {
return (
<Box padding="s">
<SpaceBetween direction="vertical" size="m">
<h1>Absolute date range picker with custom absolute format</h1>
<hr />
<ScreenshotArea>
<PermutationsView
permutations={permutations}
render={permutation => (
<DateRangePicker
value={permutation.value}
absoluteFormat={permutation.absoluteFormat}
dateOnly={permutation.dateOnly}
hideTimeOffset={permutation.hideTimeOffset}
locale="en-US"
i18nStrings={i18nStrings}
placeholder={'Filter by a date and time range'}
relativeOptions={[]}
isValidRange={isValid}
rangeSelectorMode={'absolute-only'}
getTimeOffset={() => 60}
/>
)}
/>
</ScreenshotArea>
</SpaceBetween>
</Box>
);
}
28 changes: 28 additions & 0 deletions src/__tests__/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6009,6 +6009,27 @@ The event \`detail\` contains the current value of the field.",
],
"name": "DateRangePicker",
"properties": Array [
Object {
"defaultValue": "'iso'",
"description": "Specifies the time format to use for displaying the absolute time range.
It can take the following values:
* \`iso\`: ISO 8601 format, e.g.: 2024-01-30T13:32:32+01:00 (or 2024-01-30 when \`dateOnly\` is true)
* \`long-localized\`: a more human-readable, localized format, e.g.: January 30, 2024, 13:32:32 (UTC+1) (or January 30, 2024 when \`dateOnly\` is true)

Defaults to \`iso\`.
",
"inlineType": Object {
"name": "DateRangePickerProps.AbsoluteFormat",
"type": "union",
"values": Array [
"iso",
"long-localized",
],
},
"name": "absoluteFormat",
"optional": true,
"type": "string",
},
Object {
"description": "Adds \`aria-describedby\` to the component. If you're using this component within a form field,
don't set this property because the form field component automatically sets it.
Expand Down Expand Up @@ -6132,6 +6153,13 @@ Default: the user's current time offset as provided by the browser.
"optional": true,
"type": "DateRangePickerProps.GetTimeOffsetFunction",
},
Object {
"description": "Specifies whether to hide the time offset in the displayed absolute time range.
Defaults to \`false\`.",
"name": "hideTimeOffset",
"optional": true,
"type": "boolean",
},
Object {
"description": "An object containing all the necessary localized strings required by the component.",
"i18nTag": true,
Expand Down
56 changes: 45 additions & 11 deletions src/date-range-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,23 @@ import ResetContextsForModal from '../internal/context/reset-contexts-for-modal.

export { DateRangePickerProps };

function renderDateRange(
range: null | DateRangePickerProps.Value,
placeholder: string,
formatRelativeRange: DateRangePickerProps.I18nStrings['formatRelativeRange'],
timeOffset: { startDate?: number; endDate?: number }
) {
function renderDateRange({
locale,
range,
placeholder = '',
formatRelativeRange,
absoluteFormat,
hideTimeOffset,
timeOffset,
}: {
locale?: string;
range: null | DateRangePickerProps.Value;
placeholder?: string;
formatRelativeRange: DateRangePickerProps.I18nStrings['formatRelativeRange'];
absoluteFormat: DateRangePickerProps.AbsoluteFormat;
hideTimeOffset?: boolean;
timeOffset: { startDate?: number; endDate?: number };
}) {
if (!range) {
return (
<span className={styles['label-text']} aria-disabled={true}>
Expand All @@ -52,7 +63,16 @@ function renderDateRange(
range.type === 'relative' ? (
formatRelativeRange?.(range) ?? ''
) : (
<BreakSpaces text={formatDateRange(range.startDate, range.endDate, timeOffset)} />
<BreakSpaces
text={formatDateRange({
startDate: range.startDate,
endDate: range.endDate,
timeOffset,
hideTimeOffset,
format: absoluteFormat,
locale,
})}
/>
);

return (
Expand Down Expand Up @@ -107,6 +127,8 @@ const DateRangePicker = React.forwardRef(
expandToViewport = false,
rangeSelectorMode = 'default',
customAbsoluteRangeControl,
absoluteFormat = 'iso',
hideTimeOffset,
...rest
}: DateRangePickerProps,
ref: Ref<DateRangePickerProps.Ref>
Expand Down Expand Up @@ -229,6 +251,16 @@ const DateRangePicker = React.forwardRef(
}
}

const formattedDate: string | JSX.Element = renderDateRange({
locale: normalizedLocale,
range: value,
placeholder,
formatRelativeRange,
absoluteFormat,
hideTimeOffset,
timeOffset: normalizedTimeOffset,
});

const trigger = (
<div className={styles['trigger-wrapper']}>
<ButtonTrigger
Expand All @@ -255,9 +287,7 @@ const DateRangePicker = React.forwardRef(
<span className={styles['icon-wrapper']}>
<InternalIcon name="calendar" variant={disabled || readOnly ? 'disabled' : 'normal'} />
</span>
<span id={triggerContentId}>
{renderDateRange(value, placeholder ?? '', formatRelativeRange, normalizedTimeOffset)}
</span>
<span id={triggerContentId}>{formattedDate}</span>
</span>
</ButtonTrigger>
</div>
Expand All @@ -269,7 +299,11 @@ const DateRangePicker = React.forwardRef(
<div
{...baseProps}
ref={mergedRef}
className={clsx(baseProps.className, styles.root)}
className={clsx(
baseProps.className,
styles.root,
absoluteFormat === 'long-localized' && !dateOnly && styles.wide
)}
onKeyDown={onWrapperKeyDownHandler}
>
<Dropdown
Expand Down
19 changes: 19 additions & 0 deletions src/date-range-picker/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ export interface DateRangePickerProps
* Specifies an additional control displayed in the dropdown, located below the range calendar.
*/
customAbsoluteRangeControl?: DateRangePickerProps.AbsoluteRangeControl;

/**
* Specifies the time format to use for displaying the absolute time range.
*
* It can take the following values:
* * `iso`: ISO 8601 format, e.g.: 2024-01-30T13:32:32+01:00 (or 2024-01-30 when `dateOnly` is true)
* * `long-localized`: a more human-readable, localized format, e.g.: January 30, 2024, 13:32:32 (UTC+1) (or January 30, 2024 when `dateOnly` is true)
*
* Defaults to `iso`.
*/
absoluteFormat?: DateRangePickerProps.AbsoluteFormat;

/**
* Specifies whether to hide the time offset in the displayed absolute time range.
* Defaults to `false`.
*/
hideTimeOffset?: boolean;
}

export namespace DateRangePickerProps {
Expand Down Expand Up @@ -391,6 +408,8 @@ export namespace DateRangePickerProps {
*/
renderSelectedAbsoluteRangeAriaLive?: (startDate: string, endDate: string) => string;
}

export type AbsoluteFormat = 'iso' | 'long-localized';
}

export type DayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
Expand Down
7 changes: 6 additions & 1 deletion src/date-range-picker/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ $calendar-header-color: awsui.$color-text-body-default;

.root {
@include styles.styles-reset;
max-inline-size: 32em;
&:not(.wide) {
max-inline-size: 32em;
}
&.wide {
max-inline-size: 39em;
}
}

.focus-lock {
Expand Down
Loading

0 comments on commit 2c12a6b

Please sign in to comment.