Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Month granularity for date-range-picker #3077

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c6e45a2
feat: Month granularity in date-range-picker
dpitcock Dec 6, 2024
a5cde6e
setting relative options to start with Last vs Previous
dpitcock Dec 11, 2024
be09560
fixing calendar util to focus month
dpitcock Dec 12, 2024
29e4739
fixing auto calendar update after startdate entered
dpitcock Dec 12, 2024
1b2ea82
creating test-classes dir for date-range-picker and updating test uti…
dpitcock Dec 16, 2024
63eb37e
updating snapshot after rebase
dpitcock Jan 6, 2025
e761cfe
fixing test and odd user select on calendar header button click
dpitcock Jan 6, 2025
b978990
expanding unit test covering for date-range-picker
dpitcock Jan 20, 2025
4b85962
updating i18n strings and tests
dpitcock Jan 20, 2025
0cda3e6
adding test to confirm i18n strings
dpitcock Jan 20, 2025
829b6ad
updating i18n to have different constraint texts
dpitcock Jan 21, 2025
f71f85f
updaing i18n strings to use 'i18nStrings.' prefix
dpitcock Jan 22, 2025
402401a
fixing test to also use 'i18nStrings.' prefix
dpitcock Jan 22, 2025
93cd494
changes after applying changes from running script in i18n package
dpitcock Jan 22, 2025
7e7d8c1
removing user-select styling addition
dpitcock Jan 23, 2025
15fea17
test updates
dpitcock Jan 23, 2025
5907acd
updating test utils
dpitcock Jan 23, 2025
daf0663
updating i18n string for relative range option desc
dpitcock Jan 23, 2025
f635cec
upating test
dpitcock Jan 23, 2025
2a100a4
fixing integ test
dpitcock Jan 23, 2025
0d8816e
implementing strategy pattern for grid item
dpitcock Jan 23, 2025
21bf62a
modifying strategy on grid
dpitcock Jan 23, 2025
02489b4
updaing relativeRangeSelectionMonthlyDescription
dpitcock Jan 24, 2025
8dd4a5b
updating Granularity type
dpitcock Jan 24, 2025
449875f
updating snapshot and Granularity type
dpitcock Jan 24, 2025
d5a41e1
updating after running i18n script
dpitcock Jan 24, 2025
8fc8bff
preparing for merge
dpitcock Jan 28, 2025
44f9394
preparing for merge2
dpitcock Jan 28, 2025
45e1569
fixes after merging in latest calendar with 6 rows
dpitcock Jan 29, 2025
2046db6
returning year calendar functions to not use selected in consideratio…
dpitcock Jan 29, 2025
959755a
removing commented out code
dpitcock Jan 29, 2025
bf6a96e
renaming to monthly-grid so file is tracked correctly
dpitcock Jan 29, 2025
f087079
trying to rename file so it is tracked correctly
dpitcock Jan 29, 2025
29838c3
fixing accessibility in page issues
dpitcock Jan 29, 2025
0b879f6
adding more labels to DRP pages, addressing comments
dpitcock Jan 29, 2025
cae5883
adding validation functions so examples work on par with playground e…
dpitcock Jan 29, 2025
56e2f76
adding month only custom absolute range to page
dpitcock Jan 30, 2025
2fd7684
wrapping pages in Box component
dpitcock Jan 30, 2025
dd7f5e9
seperate permutations pages for month and year calendars
dpitcock Jan 30, 2025
fb977d4
fixing .no-range styling issue, seperating permutations
dpitcock Jan 30, 2025
0970ec5
fixing disabled dates
dpitcock Jan 30, 2025
a7053d5
removed debug data-permutation in page
dpitcock Jan 30, 2025
115108d
chore: Cleaning up classes
dpitcock Jan 30, 2025
2f29638
chore: Cleaning up classes2
dpitcock Jan 30, 2025
bc106f8
chore: Cleaning up classes3
dpitcock Jan 30, 2025
91cd9c3
returning class to day-header
dpitcock Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { Box, DateRangePicker, DateRangePickerProps, SpaceBetween } from '~compo
import createPermutations from '../utils/permutations';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR-175152623 is changes needed to accommodate regression tests path change

import PermutationsView from '../utils/permutations-view';
import ScreenshotArea from '../utils/screenshot-area';
import { generateI18nStrings, generatePlaceholder, isValid } from './common';
import { generatePlaceholder, i18nStrings, isValid } from './common';

const permutations = createPermutations<
Pick<DateRangePickerProps, 'absoluteFormat' | 'dateOnly' | 'hideTimeOffset' | 'value'>
Pick<DateRangePickerProps, 'absoluteFormat' | 'dateOnly' | 'hideTimeOffset' | 'value' | 'granularity'>
>([
{
absoluteFormat: ['iso', 'long-localized'],
Expand Down Expand Up @@ -41,7 +41,7 @@ export default function DateRangePickerPermutations() {
return (
<Box padding="s">
<SpaceBetween direction="vertical" size="m">
<h1>Absolute date range picker with custom absolute format</h1>
<h1>Absolute date range picker month calendar with custom absolute format</h1>
<hr />
<ScreenshotArea>
<PermutationsView
Expand All @@ -51,12 +51,13 @@ export default function DateRangePickerPermutations() {
value={permutation.value}
absoluteFormat={permutation.absoluteFormat}
dateOnly={permutation.dateOnly}
granularity="day"
hideTimeOffset={permutation.hideTimeOffset}
locale="en-US"
i18nStrings={generateI18nStrings(permutation.dateOnly || false, false)}
i18nStrings={i18nStrings}
placeholder={generatePlaceholder(permutation.dateOnly, false)}
relativeOptions={[]}
isValidRange={isValid}
isValidRange={value => isValid('day')(value)}
rangeSelectorMode={'absolute-only'}
getTimeOffset={() => 60}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copy link
Member Author

@dpitcock dpitcock Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR-175152623 is changes needed to add regression tests path

// SPDX-License-Identifier: Apache-2.0
import React from 'react';

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

import createPermutations from '../utils/permutations';
import PermutationsView from '../utils/permutations-view';
import ScreenshotArea from '../utils/screenshot-area';
import { generatePlaceholder, i18nStrings, isValid } from './common';

const permutations = createPermutations<
Pick<DateRangePickerProps, 'absoluteFormat' | 'dateOnly' | 'hideTimeOffset' | 'value' | 'granularity'>
>([
{
absoluteFormat: ['iso', 'long-localized'],
value: [
{
type: 'absolute',
startDate: '2024-12',
endDate: '2025-01',
},
],
},
{
absoluteFormat: ['iso', 'long-localized'],
hideTimeOffset: [true, false],
value: [
{
type: 'absolute',
startDate: '2023-06',
endDate: '2024-02',
},
],
},
]);

export default function DateRangePickerPermutations() {
return (
<Box padding="s">
<SpaceBetween direction="vertical" size="m">
<h1>Absolute date range picker year calendar with custom absolute format</h1>
<hr />
<ScreenshotArea>
<PermutationsView
permutations={permutations}
render={permutation => (
<DateRangePicker
value={permutation.value}
absoluteFormat={permutation.absoluteFormat}
dateOnly={false}
granularity="month"
hideTimeOffset={permutation.hideTimeOffset}
locale="en-US"
i18nStrings={i18nStrings}
placeholder={generatePlaceholder(permutation.dateOnly, permutation.granularity === 'month')}
relativeOptions={[]}
isValidRange={value => isValid('month')(value)}
rangeSelectorMode={'absolute-only'}
getTimeOffset={() => 60}
/>
)}
/>
</ScreenshotArea>
</SpaceBetween>
</Box>
);
}
49 changes: 30 additions & 19 deletions pages/date-range-picker/absolute-format.localization.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useState } from 'react';

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

import AppContext from '../app/app-context';
import {
Expand All @@ -11,8 +11,8 @@ import {
DateRangePickerDemoContext,
dateRangePickerDemoDefaults,
DisabledDate,
generateI18nStrings,
generatePlaceholder,
i18nStrings,
isValid,
} from './common';

Expand Down Expand Up @@ -47,7 +47,7 @@ const initialRange = {
export default function DateRangePickerScenario() {
const { urlParams, setUrlParams } = useContext(AppContext as DateRangePickerDemoContext);
const dateOnly = urlParams.dateOnly ?? dateRangePickerDemoDefaults.dateOnly;
const monthOnly = false;
const monthOnly = urlParams.monthOnly ?? dateRangePickerDemoDefaults.monthOnly;
const disabledDates =
(urlParams.disabledDates as DisabledDate) ?? (dateRangePickerDemoDefaults.disabledDates as DisabledDate);
const withDisabledReason = urlParams.withDisabledReason ?? dateRangePickerDemoDefaults.withDisabledReason;
Expand Down Expand Up @@ -117,6 +117,14 @@ export default function DateRangePickerScenario() {
/>{' '}
Date only
</label>
<label>
<input
type="checkbox"
checked={monthOnly}
onChange={event => setUrlParams({ monthOnly: !!event.target.checked })}
/>{' '}
Month only
</label>
<label>
Time offset from UTC in minutes{' '}
<input
Expand All @@ -142,22 +150,25 @@ export default function DateRangePickerScenario() {
<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={generateI18nStrings(dateOnly, monthOnly)}
placeholder={generatePlaceholder(dateOnly, monthOnly)}
onChange={e => setValue(e.detail.value)}
relativeOptions={[]}
isValidRange={isValid}
rangeSelectorMode={'absolute-only'}
isDateEnabled={date => checkIfDisabled(date, disabledDates, monthOnly)}
dateDisabledReason={date => applyDisabledReason(withDisabledReason, date, disabledDates, monthOnly)}
getTimeOffset={timeOffset === undefined ? undefined : () => timeOffset!}
absoluteFormat={absoluteFormat}
dateOnly={dateOnly}
hideTimeOffset={hideTimeOffset}
/>
<FormField label="Date Range Picker field">
<DateRangePicker
value={value}
locale={locale}
i18nStrings={i18nStrings}
placeholder={generatePlaceholder(dateOnly, monthOnly)}
onChange={e => setValue(e.detail.value)}
relativeOptions={[]}
isValidRange={isValid(monthOnly ? 'month' : 'day')}
rangeSelectorMode={'absolute-only'}
isDateEnabled={date => checkIfDisabled(date, disabledDates, monthOnly)}
dateDisabledReason={date => applyDisabledReason(withDisabledReason, date, disabledDates, monthOnly)}
getTimeOffset={timeOffset === undefined ? undefined : () => timeOffset!}
absoluteFormat={absoluteFormat}
dateOnly={dateOnly}
granularity={monthOnly ? 'month' : 'day'}
hideTimeOffset={hideTimeOffset}
/>
</FormField>
</Grid>
</div>
))}
Expand Down
83 changes: 50 additions & 33 deletions pages/date-range-picker/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { DateRangePickerProps } from '~components/date-range-picker';

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

export type DateRangePickerDemoContext = React.Context<
AppContextType<{
Expand All @@ -19,6 +19,7 @@ export type DateRangePickerDemoContext = React.Context<
expandToViewport?: boolean;
disabledDates?: string;
withDisabledReason?: boolean;
hasValue?: boolean;
}>
>;

Expand Down Expand Up @@ -111,30 +112,39 @@ export const dateRangePickerDemoDefaults = {
expandToViewport: false,
disabledDates: 'none',
withDisabledReason: true,
hasValue: true,
};

function formatRelativeRange(range: DateRangePickerProps.RelativeValue): string {
const unit = range.amount === 1 ? range.unit : `${range.unit}s`;
return `Previous ${range.amount} ${unit}`;
return `Last ${range.amount} ${unit}`;
}

export const i18nStrings: DateRangePickerProps['i18nStrings'] = {
ariaLabel: 'Filter by a date and time range',
todayAriaLabel: 'Today',
nextMonthAriaLabel: 'Next month',
previousMonthAriaLabel: 'Previous month',
nextYearAriaLabel: 'Next year',
previousYearAriaLabel: 'Previous year',
currentMonthAriaLabel: 'This month',
customRelativeRangeDurationLabel: 'Duration',
customRelativeRangeDurationPlaceholder: 'Enter duration',
customRelativeRangeOptionLabel: 'Custom range',
customRelativeRangeOptionDescription: 'Set a custom range in the past',
customRelativeRangeUnitLabel: 'Unit of time',
formatRelativeRange: formatRelativeRange,
formatUnit: (unit, value) => (value === 1 ? unit : `${unit}s`),
dateConstraintText: 'Range must be between 6 and 30 days.',
dateTimeConstraintText: 'Range must be between 6 and 30 days. Use 24 hour format.',
monthConstraintText: 'For month use YYYY/MM.',
modeSelectionLabel: 'Date range mode',
relativeModeTitle: 'Relative range',
absoluteModeTitle: 'Absolute range',
relativeRangeSelectionHeading: 'Choose a range',
relativeRangeSelectionMonthlyDescription: 'Each month counts from the first day to the last day.',
startMonthLabel: 'Start month',
endMonthLabel: 'End month',
startDateLabel: 'Start date',
endDateLabel: 'End date',
startTimeLabel: 'Start time',
Expand All @@ -146,33 +156,25 @@ export const i18nStrings: DateRangePickerProps['i18nStrings'] = {
renderSelectedAbsoluteRangeAriaLive: (startDate, endDate) => `Range selected from ${startDate} to ${endDate}`,
};

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' },
{ key: 'previous-30-minutes', amount: 30, unit: 'minute', type: 'relative' },
{ key: 'previous-1-hour', amount: 1, unit: 'hour', type: 'relative' },
{ key: 'previous-6-hours', amount: 6, unit: 'hour', type: 'relative' },
{ key: 'last-5-minutes', amount: 5, unit: 'minute', type: 'relative' },
{ key: 'last-30-minutes', amount: 30, unit: 'minute', type: 'relative' },
{ key: 'last-1-hour', amount: 1, unit: 'hour', type: 'relative' },
{ key: 'last-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' },
{ key: 'last-1-day', amount: 5, unit: 'day', type: 'relative' },
{ key: 'last-7-days', amount: 7, unit: 'day', type: 'relative' },
{ key: 'last-1-month', amount: 1, unit: 'month', type: 'relative' },
{ key: 'last-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' },
{ key: 'last-1-month', amount: 1, unit: 'month', type: 'relative' },
{ key: 'last-2-months', amount: 2, unit: 'month', type: 'relative' },
{ key: 'last-3-months', amount: 3, unit: 'month', type: 'relative' },
{ key: 'last-6-months', amount: 6, unit: 'month', type: 'relative' },
] as const;

export function generateRelativeOptions(dateOnly: boolean, monthOnly: boolean) {
Expand All @@ -185,14 +187,29 @@ export function generateRelativeOptions(dateOnly: boolean, monthOnly: boolean) {
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.',
minimumStartDate: 'The range cannot start before 2018.',
noValueSelected: 'You need to select a range.',
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`;
export const isValid = (granularity: DateRangePickerProps.Granularity) => {
const errorMessages = {
durationBetweenOneAndTwenty: 'The amount part of the range needs to be between 1 and 20.',
durationMissing: 'You need to provide a duration.',
notLongEnough: 'The selected date range is too small. Select a range of one month or larger.',
minimumStartDate: 'The range cannot start before 2018.',
noValueSelected: 'You need to select a range.',
startDateMissing: 'You need to provide a start date.',
endDateMissing: 'You need to provide an end date.',
};

if (granularity === 'month') {
return makeIsMonthValidFunction(errorMessages);
}
return makeIsDateValidFunction(errorMessages);
};

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