Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions pages/date-range-picker/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface DateRangePickerPageSettings {
disabledDates?: DisabledDate;
showDisabledReason?: boolean;
hasValue?: boolean;
secondaryGrid?: DateRangePickerProps.SecondaryGrid;
}

const defaultSettings: Required<DateRangePickerPageSettings> = {
Expand All @@ -45,6 +46,7 @@ const defaultSettings: Required<DateRangePickerPageSettings> = {
disabledDates: 'none',
showDisabledReason: true,
hasValue: true,
secondaryGrid: 'previous',
};

export function useDateRangePickerSettings(
Expand Down Expand Up @@ -89,6 +91,7 @@ export function useDateRangePickerSettings(
const disabledDates = urlParams.disabledDates ?? def('disabledDates');
const showDisabledReason = parseBoolean(def('showDisabledReason'), urlParams.showDisabledReason);
const hasValue = parseBoolean(def('hasValue'), urlParams.hasValue);
const secondaryGrid = urlParams.secondaryGrid ?? def('secondaryGrid');
const settings: Required<DateRangePickerPageSettings> = {
dateOnly,
monthOnly,
Expand All @@ -105,6 +108,7 @@ export function useDateRangePickerSettings(
disabledDates,
showDisabledReason,
hasValue,
secondaryGrid,
};
const setSettings = (settings: DateRangePickerPageSettings) => setUrlParams(settings);

Expand Down Expand Up @@ -212,6 +216,7 @@ export function useDateRangePickerSettings(
placeholder,
i18nStrings,
locale: 'en-GB',
secondaryGrid,
};

return { props, settings, setSettings };
Expand Down Expand Up @@ -256,6 +261,7 @@ export function Settings({
disabledDates,
showDisabledReason,
hasValue,
secondaryGrid,
},
setSettings,
}: {
Expand All @@ -274,6 +280,7 @@ export function Settings({
const dateFormatOptions = [{ value: 'iso' }, { value: 'slashed' }, { value: 'long-localized' }];
const inputDateFormat = [{ value: 'iso' }, { value: 'slashed' }];
const timeFormatOptions = [{ value: 'hh:mm:ss' }, { value: 'hh:mm' }, { value: 'hh' }];
const secondaryGridOptions = [{ value: 'previous' }, { value: 'next' }];
return (
<SpaceBetween size="m" direction="horizontal">
<FormField label="Range selector mode">
Expand Down Expand Up @@ -332,6 +339,16 @@ export function Settings({
/>
</FormField>

<FormField label="Secondary grid">
<Select
options={secondaryGridOptions}
selectedOption={secondaryGridOptions.find(o => o.value === secondaryGrid) ?? null}
onChange={({ detail }) =>
setSettings({ secondaryGrid: detail.selectedOption.value as DateRangePickerProps.SecondaryGrid })
}
/>
</FormField>

<SpaceBetween direction="horizontal" size="s">
<Checkbox checked={hasValue} onChange={({ detail }) => setSettings({ hasValue: detail.checked })}>
Has initial value
Expand Down
42 changes: 42 additions & 0 deletions pages/date-range-picker/start-month.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react';

import { Box, DateRangePicker, FormField, Link } from '~components';

import { SimplePage } from '../app/templates';
import ScreenshotArea from '../utils/screenshot-area';
import { Settings, useDateRangePickerSettings } from './common';

export default function StartMonthScenario() {
const { props, settings, setSettings } = useDateRangePickerSettings({
value: null,
secondaryGrid: 'next',
hasValue: false,
});
return (
<SimplePage
title="Date range picker: start month"
settings={<Settings settings={settings} setSettings={setSettings} />}
>
<Link id="focusable-before">Focusable element before</Link>
<br />
<br />

<ScreenshotArea gutters={false}>
<FormField label="Date Range Picker field">
<DateRangePicker {...props} />
</FormField>

<br />
<br />
<div style={{ blockSize: 800 }}>
<b>
Raw value: <Box variant="pre">{JSON.stringify(props.value, undefined, 2)}</Box>
</b>
</div>
</ScreenshotArea>
</SimplePage>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11396,6 +11396,28 @@ allows the user to clear the selected value.",
"optional": true,
"type": "boolean",
},
{
"description": "Determines which month is displayed first (on the left) when the calendar
opens with two grids visible.

* \`previous\` (default) – the left grid shows the previous month and the
right grid shows the current month.
* \`current\` – the left grid shows the current month and the right grid
shows the next month.

Has no effect on single-grid (mobile) layout.",
"inlineType": {
"name": "DateRangePickerProps.StartMonth",
"type": "union",
"values": [
"current",
"previous",
],
},
"name": "startMonth",
"optional": true,
"type": "string",
},
{
"description": "Starting day of the week. [0-6] maps to [Sunday-Saturday].
By default the starting day of the week is defined by the locale,
Expand Down
38 changes: 34 additions & 4 deletions src/date-range-picker/calendar/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,26 @@ describe('findMonthToDisplay', () => {
const result = findMonthToDisplay(value, true); // isSingleGrid doesn't matter in this case
expect(result).toEqual(createDate('2023-07-01'));
});
test('should return current month when both start and end dates are null', () => {
test('should return current month when both start and end dates are null for single grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findMonthToDisplay(value, true); // isSingleGrid doesn't matter in this case
const result = findMonthToDisplay(value, true);
expect(result).toEqual(createDate('2023-06-01')); // Based on the mocked current date
});
test('should return current month (default previous-and-current) when both dates are null for double grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findMonthToDisplay(value, false);
expect(result).toEqual(createDate('2023-06-01')); // Default: current month as baseDate (right grid)
});
test('should return current month with secondaryGrid="previous" when both dates are null for double grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findMonthToDisplay(value, false, 'previous');
expect(result).toEqual(createDate('2023-06-01'));
});
test('should return next month with secondaryGrid="next" when both dates are null for double grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findMonthToDisplay(value, false, 'next');
expect(result).toEqual(createDate('2023-07-01')); // Next month as baseDate so current month appears on the left
});
test('should handle leap years correctly', () => {
jest.setSystemTime(createDate('2024-02-15').getTime());
const value = { start: { date: '2024-02-29', time: '00:00' }, end: { date: '', time: '' } };
Expand Down Expand Up @@ -151,11 +166,26 @@ describe('findYearToDisplay', () => {
const result = findYearToDisplay(value, true); // isSingleGrid doesn't matter in this case
expect(result).toEqual(createDate('2024-01-01'));
});
test('should return current year when both start and end dates are empty', () => {
test('should return current year when both start and end dates are empty for single grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findYearToDisplay(value, true); // isSingleGrid doesn't matter in this case
const result = findYearToDisplay(value, true);
expect(result).toEqual(createDate('2023-01-01')); // Based on the mocked current date
});
test('should return current year (default previous-and-current) when both dates are empty for double grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findYearToDisplay(value, false);
expect(result).toEqual(createDate('2023-01-01')); // Default: current year as baseDate (right grid)
});
test('should return current year with secondaryGrid="previous" when both dates are empty for double grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findYearToDisplay(value, false, 'previous');
expect(result).toEqual(createDate('2023-01-01'));
});
test('should return next year with secondaryGrid="next" when both dates are empty for double grid', () => {
const value = { start: { date: '', time: '' }, end: { date: '', time: '' } };
const result = findYearToDisplay(value, false, 'next');
expect(result).toEqual(createDate('2024-01-01')); // Next year as baseDate so current year appears on the left
});
test('should handle leap years correctly', () => {
const value = { start: { date: '2024-02-29', time: '00:00' }, end: { date: '', time: '' } };
const result = findYearToDisplay(value, true);
Expand Down
3 changes: 2 additions & 1 deletion src/date-range-picker/calendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function DateRangePickerCalendar({
dateInputFormat,
customAbsoluteRangeControl,
granularity = 'day',
secondaryGrid,
}: DateRangePickerCalendarProps) {
const isSingleGrid = useMobile();
const isMonthPicker = granularity === 'month';
Expand All @@ -66,7 +67,7 @@ export default function DateRangePickerCalendar({
const addPage = isMonthPicker ? addYears : addMonths;
const startOfPage = isMonthPicker ? startOfYear : startOfMonth;
const findItemToFocus = isMonthPicker ? findMonthToFocus : findDateToFocus;
const [currentPage, setCurrentPage] = useState(() => findPageToDisplay(value, isSingleGrid));
const [currentPage, setCurrentPage] = useState(() => findPageToDisplay(value, isSingleGrid, secondaryGrid));
const [focusedDate, setFocusedDate] = useState<Date | null>(() => {
if (value.start.date) {
const startDate = parseDate(value.start.date);
Expand Down
1 change: 1 addition & 0 deletions src/date-range-picker/calendar/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface DateRangePickerCalendarProps
| 'customAbsoluteRangeControl'
| 'isDateEnabled'
| 'dateDisabledReason'
| 'secondaryGrid'
>,
'absoluteFormat' | 'timeInputFormat'
> {
Expand Down
22 changes: 18 additions & 4 deletions src/date-range-picker/calendar/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ export function findMonthToFocus(
return null;
}

export function findMonthToDisplay(value: DateRangePickerProps.PendingAbsoluteValue, isSingleGrid: boolean) {
export function findMonthToDisplay(
value: DateRangePickerProps.PendingAbsoluteValue,
isSingleGrid: boolean,
secondaryGrid: DateRangePickerProps.SecondaryGrid | undefined = 'previous'
) {
if (value.start.date) {
const startDate = parseDate(value.start.date);
if (isSingleGrid) {
Expand All @@ -54,10 +58,17 @@ export function findMonthToDisplay(value: DateRangePickerProps.PendingAbsoluteVa
if (value.end.date) {
return startOfMonth(parseDate(value.end.date));
}
return startOfMonth(Date.now());
if (isSingleGrid || secondaryGrid !== 'next') {
return startOfMonth(Date.now());
}
return startOfMonth(addMonths(Date.now(), 1));
}

export function findYearToDisplay(value: DateRangePickerProps.PendingAbsoluteValue, isSingleGrid: boolean) {
export function findYearToDisplay(
value: DateRangePickerProps.PendingAbsoluteValue,
isSingleGrid: boolean,
secondaryGrid: DateRangePickerProps.SecondaryGrid | undefined = 'previous'
) {
if (value.start.date) {
const startDate = parseDate(value.start.date);
if (isSingleGrid) {
Expand All @@ -68,7 +79,10 @@ export function findYearToDisplay(value: DateRangePickerProps.PendingAbsoluteVal
if (value.end.date) {
return startOfYear(parseDate(value.end.date));
}
return startOfYear(Date.now());
if (isSingleGrid || secondaryGrid !== 'next') {
return startOfYear(Date.now());
}
return startOfYear(addYears(Date.now(), 1));
}

export const generateI18NFallbackKey = (isMonthPicker: boolean, isDateOnly: boolean) => {
Expand Down
3 changes: 3 additions & 0 deletions src/date-range-picker/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ interface DateRangePickerDropdownProps
isSingleGrid: boolean;
customAbsoluteRangeControl: DateRangePickerProps.AbsoluteRangeControl | undefined;
renderRelativeRangeContent: DateRangePickerProps.RelativeRangeControl | undefined;
secondaryGrid: DateRangePickerProps.SecondaryGrid | undefined;
}

export function DateRangePickerDropdown({
Expand Down Expand Up @@ -91,6 +92,7 @@ export function DateRangePickerDropdown({
customRelativeRangeUnits,
renderRelativeRangeContent,
granularity = 'day',
secondaryGrid,
}: DateRangePickerDropdownProps) {
const i18n = useInternalI18n('date-range-picker');
const isMonthPicker = granularity === 'month';
Expand Down Expand Up @@ -216,6 +218,7 @@ export function DateRangePickerDropdown({
dateInputFormat={dateInputFormat}
customAbsoluteRangeControl={customAbsoluteRangeControl}
granularity={granularity}
secondaryGrid={secondaryGrid}
/>
)}

Expand Down
2 changes: 2 additions & 0 deletions src/date-range-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const DateRangePicker = React.forwardRef(
customRelativeRangeUnits,
renderRelativeRangeContent,
granularity = 'day',
secondaryGrid,
...rest
}: DateRangePickerProps,
ref: Ref<DateRangePickerProps.Ref>
Expand Down Expand Up @@ -349,6 +350,7 @@ const DateRangePicker = React.forwardRef(
customRelativeRangeUnits={customRelativeRangeUnits}
renderRelativeRangeContent={renderRelativeRangeContent}
granularity={granularity}
secondaryGrid={secondaryGrid}
/>
)}
</ResetContextsForModal>
Expand Down
18 changes: 18 additions & 0 deletions src/date-range-picker/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ export interface DateRangePickerBaseProps {
*/
timeInputFormat?: DateRangePickerProps.TimeInputFormat;

/**
* Determines which period is displayed in the secondary (right) grid
* when the calendar opens with two grids visible.
*
* * `previous` (default) – the secondary grid shows the current period,
* and the primary (left) grid shows the previous period.
* * `next` – the secondary grid shows the next period,
* and the primary (left) grid shows the current period.
*
* For day granularity, a "period" is a month. For month granularity, a "period" is a year.
*
* When a value is selected, the calendar navigates to the selected date instead.
* Has no effect on single-grid (mobile) layout.
*/
secondaryGrid?: DateRangePickerProps.SecondaryGrid;

/**
* Fired whenever a user changes the component's value.
* The event `detail` contains the current value of the field.
Expand Down Expand Up @@ -298,6 +314,8 @@ export namespace DateRangePickerProps {
setSelectedRange: (value: RelativeValue) => void
) => React.ReactNode;

export type SecondaryGrid = 'previous' | 'next';

export type RangeSelectorMode = 'default' | 'absolute-only' | 'relative-only';

export interface Ref {
Expand Down
Loading