Skip to content

Commit

Permalink
chore: Enhancing date-range-picker pages (#3107)
Browse files Browse the repository at this point in the history
  • Loading branch information
dpitcock authored Dec 10, 2024
1 parent 5acafb3 commit d6ee60f
Show file tree
Hide file tree
Showing 16 changed files with 690 additions and 185 deletions.
89 changes: 64 additions & 25 deletions pages/date-range-picker/absolute-format.localization.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ import React, { useContext, useState } from 'react';

import { Box, DateRangePicker, DateRangePickerProps, Grid, SpaceBetween } from '~components';

import AppContext, { AppContextType } from '../app/app-context';
import { i18nStrings, isValid } from './common';
import AppContext from '../app/app-context';
import {
applyDisabledReason,
checkIfDisabled,
DateRangePickerDemoContext,
dateRangePickerDemoDefaults,
DisabledDate,
generateI18nStrings,
generatePlaceholder,
isValid,
} from './common';

const locales = [
'ar',
Expand All @@ -30,27 +39,28 @@ const locales = [

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 { urlParams, setUrlParams } = useContext(AppContext as DateRangePickerDemoContext);
const dateOnly = urlParams.dateOnly ?? dateRangePickerDemoDefaults.dateOnly;
const monthOnly = false;
const disabledDates =
(urlParams.disabledDates as DisabledDate) ?? (dateRangePickerDemoDefaults.disabledDates as DisabledDate);
const withDisabledReason = urlParams.withDisabledReason ?? dateRangePickerDemoDefaults.withDisabledReason;
const absoluteFormat =
urlParams.absoluteFormat ?? (dateRangePickerDemoDefaults.absoluteFormat as DateRangePickerProps.AbsoluteFormat);
const hideTimeOffset = urlParams.hideTimeOffset ?? dateRangePickerDemoDefaults.hideTimeOffset;
const timeOffset = isNaN(parseInt(urlParams.timeOffset as string))
? dateRangePickerDemoDefaults.timeOffset
: parseInt(urlParams.timeOffset as string);
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,
startDate: dateOnly ? initialRange.startDate.slice(0, 10) : initialRange.startDate,
endDate: dateOnly ? initialRange.endDate.slice(0, 10) : initialRange.endDate,
});

return (
Expand All @@ -61,7 +71,7 @@ export default function DateRangePickerScenario() {
<label>
Format{' '}
<select
value={urlParams.absoluteFormat}
value={absoluteFormat}
onChange={event =>
setUrlParams({
absoluteFormat: event.currentTarget.value as DateRangePickerProps.AbsoluteFormat,
Expand All @@ -72,10 +82,37 @@ export default function DateRangePickerScenario() {
<option value="long-localized">Localized</option>
</select>
</label>
<label>
Disabled dates{' '}
<select
value={disabledDates}
onChange={event =>
setUrlParams({
disabledDates: event.currentTarget.value as DisabledDate,
})
}
>
<option value="none">None (Default)</option>
<option value="all">All</option>
<option value="only-even">Only even</option>
<option value="middle-of-page">Middle of {monthOnly ? 'year' : 'month'}</option>
<option value="end-of-page">End of {monthOnly ? 'year' : 'month'}</option>
<option value="start-of-page">Start of {monthOnly ? 'year' : 'month'}</option>
<option value="overlapping-pages">Overlapping {monthOnly ? 'years' : 'months'}</option>
</select>
</label>
<label>
<input
type="checkbox"
checked={withDisabledReason}
onChange={event => setUrlParams({ withDisabledReason: !!event.target.checked })}
/>{' '}
Disabled reasons
</label>
<label>
<input
type="checkbox"
checked={urlParams.dateOnly}
checked={dateOnly}
onChange={event => setUrlParams({ dateOnly: !!event.target.checked })}
/>{' '}
Date only
Expand All @@ -84,7 +121,7 @@ export default function DateRangePickerScenario() {
Time offset from UTC in minutes{' '}
<input
type="number"
value={urlParams.timeOffset}
value={timeOffset}
onChange={event => {
const value = parseInt(event.currentTarget.value);
setUrlParams({ timeOffset: isNaN(value) ? 0 : value });
Expand All @@ -94,7 +131,7 @@ export default function DateRangePickerScenario() {
<label>
<input
type="checkbox"
checked={urlParams.hideTimeOffset}
checked={hideTimeOffset}
onChange={event => setUrlParams({ hideTimeOffset: !!event.target.checked })}
/>{' '}
Hide time offset
Expand All @@ -108,16 +145,18 @@ export default function DateRangePickerScenario() {
<DateRangePicker
value={value}
locale={locale}
i18nStrings={i18nStrings}
placeholder={'Filter by a date and time range'}
i18nStrings={generateI18nStrings(dateOnly, monthOnly)}
placeholder={generatePlaceholder(dateOnly, monthOnly)}
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}
isDateEnabled={date => checkIfDisabled(date, disabledDates, monthOnly)}
dateDisabledReason={date => applyDisabledReason(withDisabledReason, date, disabledDates, monthOnly)}
getTimeOffset={timeOffset === undefined ? undefined : () => timeOffset!}
absoluteFormat={absoluteFormat}
dateOnly={dateOnly}
hideTimeOffset={hideTimeOffset}
/>
</Grid>
</div>
Expand Down
10 changes: 5 additions & 5 deletions pages/date-range-picker/absolute-format.permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { Box, DateRangePicker, DateRangePickerProps, SpaceBetween } from '~compo
import createPermutations from '../utils/permutations';
import PermutationsView from '../utils/permutations-view';
import ScreenshotArea from '../utils/screenshot-area';
import { i18nStrings, isValid } from './common';
import { generateI18nStrings, generatePlaceholder, isValid } from './common';

const permutations = createPermutations<
Pick<DateRangePickerProps, 'absoluteFormat' | 'dateOnly' | 'hideTimeOffset' | 'value'>
>([
{
absoluteFormat: ['iso', 'long-localized'],
dateOnly: [true],
dateOnly: [true, false],
value: [
{
type: 'absolute',
Expand All @@ -25,7 +25,7 @@ const permutations = createPermutations<
},
{
absoluteFormat: ['iso', 'long-localized'],
dateOnly: [false],
dateOnly: [true, false],
hideTimeOffset: [true, false],
value: [
{
Expand Down Expand Up @@ -53,8 +53,8 @@ export default function DateRangePickerPermutations() {
dateOnly={permutation.dateOnly}
hideTimeOffset={permutation.hideTimeOffset}
locale="en-US"
i18nStrings={i18nStrings}
placeholder={'Filter by a date and time range'}
i18nStrings={generateI18nStrings(permutation.dateOnly || false, false)}
placeholder={generatePlaceholder(permutation.dateOnly, false)}
relativeOptions={[]}
isValidRange={isValid}
rangeSelectorMode={'absolute-only'}
Expand Down
5 changes: 4 additions & 1 deletion pages/date-range-picker/calendar-permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const intervals = [
['2021-08-03', '2021-08-09'],
['2021-05-10', '2021-05-31'],
['2021-05-10', '2021-05-30'],
['2021-08', '2023-08'],
['2021-08', '2021-08'],
['2021-05', '2022-05'],
];

const permutations = createPermutations<DateRangePickerCalendarProps>([
Expand All @@ -29,7 +32,7 @@ const permutations = createPermutations<DateRangePickerCalendarProps>([
setValue: [() => {}],
locale: ['en-GB'],
startOfWeek: [1],
isDateEnabled: [() => true],
isDateEnabled: [() => true, () => false],
onChange: [() => {}],
timeInputFormat: ['hh:mm:ss'] as const,
i18nStrings: [i18nStrings],
Expand Down
144 changes: 143 additions & 1 deletion pages/date-range-picker/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,117 @@
// SPDX-License-Identifier: Apache-2.0
import { DateRangePickerProps } from '~components/date-range-picker';

import { AppContextType } from '../app/app-context';
import { makeIsValidFunction } from './is-valid-range';

export type DateRangePickerDemoContext = React.Context<
AppContextType<{
monthOnly?: boolean;
dateOnly?: boolean;
showRelativeOptions?: boolean;
invalid?: boolean;
warning?: boolean;
rangeSelectorMode?: DateRangePickerProps.RangeSelectorMode;
absoluteFormat?: DateRangePickerProps.AbsoluteFormat;
hideTimeOffset?: boolean;
timeOffset?: number | string;
expandToViewport?: boolean;
disabledDates?: string;
withDisabledReason?: boolean;
}>
>;

export type DisabledDate =
| 'none'
| 'all'
| 'only-even'
| 'middle-of-page'
| 'end-of-page'
| 'start-of-page'
| 'overlapping-pages';

function isEnabledByOddness(date: Date, isMonthPicker: boolean): boolean {
return isMonthPicker ? (date.getMonth() + 1) % 2 !== 0 : date.getDate() % 2 !== 0;
}

export function checkIfDisabled(date: Date, disabledDate: DisabledDate, isMonthPicker: boolean): boolean {
const endOfMonthDays = [28, 29, 30, 31];
switch (disabledDate) {
case 'only-even':
return isEnabledByOddness(date, isMonthPicker);
case 'middle-of-page':
if (isMonthPicker) {
return ![5, 6].includes(date.getMonth());
}
return date.getDate() !== 15;
case 'end-of-page':
if (isMonthPicker) {
return date.getMonth() !== 11;
}
return !endOfMonthDays.includes(date.getDate());
case 'start-of-page':
if (isMonthPicker) {
return date.getMonth() > 0;
}
return date.getDate() > 1;
case 'overlapping-pages':
if (isMonthPicker) {
return ![11, 0].includes(date.getMonth());
}
return ![...endOfMonthDays, 1].includes(date.getDate());
case 'all':
return false;
case 'none':
default:
return true;
}
}

export const evenDisabledMsg = 'Option is not odd enough';
export function applyDisabledReason(
hasDisabledReason: boolean,
date: Date,
disabledDate: DisabledDate,
isMonthPicker: boolean
): string {
if (!hasDisabledReason || checkIfDisabled(date, disabledDate, isMonthPicker)) {
return '';
}

const pageType = isMonthPicker ? 'year' : 'month';
switch (disabledDate) {
case 'only-even':
return evenDisabledMsg;
case 'middle-of-page':
return `Middle of ${pageType} disabled`;
case 'end-of-page':
return `End of ${pageType} disabled`;
case 'start-of-page':
return `Start of ${pageType} disabled`;
case 'overlapping-pages':
return `End and start of ${pageType} disabled`;
case 'all':
return `Full ${pageType} disabled`;
default:
return 'Disabled';
}
}

export const dateRangePickerDemoDefaults = {
monthOnly: false,
dateOnly: false,
showRelativeOptions: true,
invalid: false,
warning: false,
rangeSelectorMode: 'default',
absoluteFormat: 'iso',
hideTimeOffset: false,
timeOffset: 0,
expandToViewport: false,
disabledDates: 'none',
withDisabledReason: true,
};

function formatRelativeRange(range: DateRangePickerProps.RelativeValue): string {
const unit = range.amount === 1 ? range.unit : `${range.unit}s`;
return `Previous ${range.amount} ${unit}`;
Expand Down Expand Up @@ -37,7 +146,13 @@ export const i18nStrings: DateRangePickerProps['i18nStrings'] = {
renderSelectedAbsoluteRangeAriaLive: (startDate, endDate) => `Range selected from ${startDate} to ${endDate}`,
};

export const i18nStringsDateOnly = { ...i18nStrings, dateTimeConstraintText: 'Range must be between 6 and 30 days.' };
export function generateI18nStrings(isDateOnly: boolean, isMonthOnly: boolean): DateRangePickerProps['i18nStrings'] {
return {
...i18nStrings,
...(isDateOnly ? { dateTimeConstraintText: 'Range must be between 6 and 30 days.' } : {}),
...(isMonthOnly ? { dateTimeConstraintText: 'For month use YYYY/MM.' } : {}),
};
}

export const relativeOptions = [
{ key: 'previous-5-minutes', amount: 5, unit: 'minute', type: 'relative' },
Expand All @@ -46,6 +161,30 @@ export const relativeOptions = [
{ key: 'previous-6-hours', amount: 6, unit: 'hour', type: 'relative' },
] as const;

export const dateOnlyRelativeOptions = [
{ key: 'previous-1-day', amount: 5, unit: 'day', type: 'relative' },
{ key: 'previous-7-days', amount: 7, unit: 'day', type: 'relative' },
{ key: 'previous-1-month', amount: 1, unit: 'month', type: 'relative' },
{ key: 'previous-6-months', amount: 6, unit: 'month', type: 'relative' },
] as const;

export const monthOnlyRelativeOptions = [
{ key: 'previous-1-month', amount: 1, unit: 'month', type: 'relative' },
{ key: 'previous-2-months', amount: 2, unit: 'month', type: 'relative' },
{ key: 'previous-3-months', amount: 3, unit: 'month', type: 'relative' },
{ key: 'previous-6-months', amount: 6, unit: 'month', type: 'relative' },
] as const;

export function generateRelativeOptions(dateOnly: boolean, monthOnly: boolean) {
if (monthOnly) {
return monthOnlyRelativeOptions;
}
if (dateOnly) {
return dateOnlyRelativeOptions;
}
return relativeOptions;
}

export const isValid = makeIsValidFunction({
durationBetweenOneAndTwenty: 'The amount part of the range needs to be between 1 and 20.',
durationMissing: 'You need to provide a duration.',
Expand All @@ -54,3 +193,6 @@ export const isValid = makeIsValidFunction({
startDateMissing: 'You need to provide a start date.',
endDateMissing: 'You need to provide an end date.',
});

export const generatePlaceholder = (dateOnly?: boolean, monthOnly?: boolean) =>
`Filter by ${monthOnly ? 'month' : 'date'} ${dateOnly ? '' : ' and time '}range`;
Loading

0 comments on commit d6ee60f

Please sign in to comment.