Skip to content
Open
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
13 changes: 9 additions & 4 deletions src/calendar/grid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,24 @@ export interface GridProps {
renderDateAnnouncement: (date: Date, isOnCurrentDate: boolean) => string;
isSameDate: (date: Date, baseDate: Date) => boolean;
onGridKeyDownHandler: (event: React.KeyboardEvent<HTMLElement>) => void;
referrerId?: string;
}

interface GridCellProps extends TdHTMLAttributes<HTMLTableCellElement> {
disabledReason?: string;
referrerId?: string;
}

const GridCell = forwardRef((props: GridCellProps, focusedDateRef: React.Ref<HTMLTableCellElement>) => {
const { disabledReason, ...rest } = props;
const { disabledReason, referrerId, ...rest } = props;
const isDisabledWithReason = !!disabledReason;
const { targetProps, descriptionEl } = useHiddenDescription(disabledReason);
const ref = useRef<HTMLTableCellElement>(null);
const cellRef = useRef<HTMLTableCellElement>(null);
const [showTooltip, setShowTooltip] = useState(false);

return (
<td
ref={useMergeRefs(focusedDateRef, ref)}
ref={useMergeRefs(focusedDateRef, cellRef)}
{...(isDisabledWithReason ? targetProps : {})}
{...rest}
onFocus={() => (isDisabledWithReason ? setShowTooltip(true) : undefined)}
Expand All @@ -77,9 +79,10 @@ const GridCell = forwardRef((props: GridCellProps, focusedDateRef: React.Ref<HTM
{showTooltip && (
<Tooltip
className={styles['disabled-reason-tooltip']}
getTrack={() => ref.current}
getTrack={() => cellRef.current}
content={disabledReason!}
onEscape={() => setShowTooltip(false)}
referrerId={referrerId}
/>
)}
</>
Expand All @@ -105,6 +108,7 @@ export default function Grid({
renderDateAnnouncement,
isSameDate,
onGridKeyDownHandler,
referrerId,
}: GridProps) {
const focusedDateRef = useRef<HTMLTableCellElement>(null);

Expand Down Expand Up @@ -166,6 +170,7 @@ export default function Grid({
[styles['calendar-date-dense']]: denseGrid,
})}
disabledReason={isDisabledWithReason ? disabledReason : undefined}
referrerId={referrerId}
>
<span className={styles['date-inner']} aria-hidden="true">
{renderDate(date)}
Expand Down
6 changes: 5 additions & 1 deletion src/calendar/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { getBaseMonth } from './utils/navigation-month';

import styles from './styles.css.js';

type InternalCalendarProps = CalendarProps & InternalBaseComponentProps & { referrerId?: string };

export default function Calendar({
value,
locale = '',
Expand All @@ -39,11 +41,12 @@ export default function Calendar({
__internalRootRef,
i18nStrings,
granularity = 'day',
referrerId,
previousMonthAriaLabel,
nextMonthAriaLabel,
todayAriaLabel,
...rest
}: CalendarProps & InternalBaseComponentProps) {
}: InternalCalendarProps) {
checkControlled('Calendar', 'value', value, 'onChange', onChange);

const baseProps = getBaseProps(rest);
Expand Down Expand Up @@ -188,6 +191,7 @@ export default function Calendar({
renderDateAnnouncement={renderDateAnnouncement}
isSameDate={isSameDate}
onGridKeyDownHandler={onGridKeyDownHandler}
referrerId={referrerId}
/>
</div>
</div>
Expand Down
40 changes: 39 additions & 1 deletion src/date-picker/__tests__/date-picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import * as React from 'react';
import { render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';

import DatePicker, { DatePickerProps } from '../../../lib/components/date-picker';
import FormField from '../../../lib/components/form-field';
Expand Down Expand Up @@ -488,3 +488,41 @@ describe('i18n', () => {
);
});
});

describe('disabled reason behavior', () => {
test('clicking on disabled reason does not close the calendar', () => {
const { wrapper } = renderDatePicker({
...defaultProps,
isDateEnabled: () => false,
dateDisabledReason: () => 'Test',
});

wrapper.findOpenCalendarButton().click();
expect(wrapper.findCalendar()).not.toBe(null);

wrapper.findCalendar()!.findDateAt(1, 1).focus();
expect(wrapper.findCalendar()!.findDateAt(1, 1).findDisabledReason()!.getElement()).toHaveTextContent('Test');

wrapper.findCalendar()!.findDateAt(1, 1).findDisabledReason()!.click();
expect(wrapper.findCalendar()!.findDateAt(1, 1).findDisabledReason()).not.toBe(null);
expect(wrapper.findCalendar()).not.toBe(null);
});

test('disabled reason tooltip can be closed with Escape', () => {
const { wrapper } = renderDatePicker({
...defaultProps,
isDateEnabled: () => false,
dateDisabledReason: () => 'Test',
});

wrapper.findOpenCalendarButton().click();
expect(wrapper.findCalendar()).not.toBe(null);

wrapper.findCalendar()!.findDateAt(1, 1).focus();
expect(wrapper.findCalendar()!.findDateAt(1, 1).findDisabledReason()!.getElement()).toHaveTextContent('Test');

fireEvent.keyDown(wrapper.findCalendar()!.findDateAt(1, 1).getElement(), { key: 'Escape', code: 'Escape' });
expect(wrapper.findCalendar()).not.toBe(null);
expect(wrapper.findCalendar()!.findDateAt(1, 1).findDisabledReason()).toBe(null);
});
});
4 changes: 4 additions & 0 deletions src/date-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ const DatePicker = React.forwardRef(

baseProps.className = clsx(baseProps.className, styles.root, styles['date-picker-container']);

const referrerId = useUniqueId();

return (
<div {...baseProps} ref={mergedRef} onKeyDown={!disabled && !readOnly ? onWrapperKeyDownHandler : undefined}>
{disabled || readOnly ? (
Expand All @@ -195,6 +197,7 @@ const DatePicker = React.forwardRef(
expandToViewport={expandToViewport}
scrollable={false}
dropdownId={dropdownId}
triggerId={referrerId}
content={
isDropDownOpen ? (
<FocusLock className={styles['focus-lock']} autoFocus={true}>
Expand All @@ -220,6 +223,7 @@ const DatePicker = React.forwardRef(
nextMonthAriaLabel: i18nStrings?.nextMonthAriaLabel ?? nextMonthAriaLabel,
previousMonthAriaLabel: i18nStrings?.previousMonthAriaLabel ?? previousMonthAriaLabel,
}}
referrerId={referrerId}
/>
<InternalLiveRegion id={calendarDescriptionId} hidden={true} tagName="span">
{getBaseDateLabel({ date: baseDate, granularity, locale: normalizedLocale })}
Expand Down
44 changes: 43 additions & 1 deletion src/date-range-picker/__tests__/date-range-picker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import * as React from 'react';
import { render, waitFor } from '@testing-library/react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import Mockdate from 'mockdate';

import { warnOnce } from '@cloudscape-design/component-toolkit/internal';
Expand Down Expand Up @@ -585,3 +585,45 @@ describe('Date range picker', () => {
});
});
});

describe('disabled reason behavior', () => {
const findDate = (wrapper: DateRangePickerWrapper) => wrapper.findDropdown()!.findDateAt('left', 1, 1);

test('clicking on disabled reason does not close the calendar', () => {
const { wrapper } = renderDateRangePicker({
...defaultProps,
rangeSelectorMode: 'absolute-only',
isDateEnabled: () => false,
dateDisabledReason: () => 'Test',
});

wrapper.findTrigger().click();
expect(wrapper.findDropdown()).not.toBe(null);

findDate(wrapper).focus();
expect(findDate(wrapper).findDisabledReason()!.getElement()).toHaveTextContent('Test');

findDate(wrapper).findDisabledReason()!.click();
expect(findDate(wrapper).findDisabledReason()).not.toBe(null);
expect(wrapper.findDropdown()).not.toBe(null);
});

test('disabled reason tooltip can be closed with Escape', () => {
const { wrapper } = renderDateRangePicker({
...defaultProps,
rangeSelectorMode: 'absolute-only',
isDateEnabled: () => false,
dateDisabledReason: () => 'Test',
});

wrapper.findTrigger().click();
expect(wrapper.findDropdown()).not.toBe(null);

findDate(wrapper).focus();
expect(findDate(wrapper).findDisabledReason()!.getElement()).toHaveTextContent('Test');

fireEvent.keyDown(findDate(wrapper).getElement(), { key: 'Escape', code: 'Escape' });
expect(wrapper.findDropdown()).not.toBe(null);
expect(findDate(wrapper).findDisabledReason()).toBe(null);
});
});
4 changes: 3 additions & 1 deletion src/date-range-picker/calendar/grids/grid-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import testutilStyles from '../../test-classes/styles.css.js';

interface GridCellProps extends TdHTMLAttributes<HTMLTableCellElement> {
disabledReason?: string;
referrerId?: string;
}

export const GridCell = forwardRef((props: GridCellProps, focusedDateRef: React.Ref<HTMLTableCellElement>) => {
const { disabledReason, ...rest } = props;
const { disabledReason, referrerId, ...rest } = props;
const isDisabledWithReason = !!disabledReason;
const { targetProps, descriptionEl } = useHiddenDescription(disabledReason);
const ref = useRef<HTMLTableCellElement>(null);
Expand Down Expand Up @@ -73,6 +74,7 @@ export const GridCell = forwardRef((props: GridCellProps, focusedDateRef: React.
getTrack={() => ref.current}
content={disabledReason!}
onEscape={() => setShowTooltip(false)}
referrerId={referrerId}
/>
)}
</>
Expand Down
2 changes: 2 additions & 0 deletions src/date-range-picker/calendar/grids/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function Grid({
className,
startOfWeek: rawStartOfWeek = 0,
granularity = 'day',
referrerId,
}: GridProps) {
const baseDateTime = baseDate?.getTime();
const i18n = useInternalI18n('date-range-picker');
Expand Down Expand Up @@ -275,6 +276,7 @@ export function Grid({
aria-disabled={!isEnabled}
tabIndex={tabIndex}
disabledReason={isDisabledWithReason ? disabledReason : undefined}
referrerId={referrerId}
{...handlers}
>
<span className={styles[`${granularity}-inner`]} aria-hidden="true">
Expand Down
3 changes: 3 additions & 0 deletions src/date-range-picker/calendar/grids/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const Grids = ({
headingIdPrefix,
startOfWeek = 0,
granularity = 'day',
referrerId,
}: SelectGridProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const [gridHasFocus, setGridHasFocus] = useState(false);
Expand Down Expand Up @@ -196,6 +197,7 @@ export const Grids = ({
className={testutilStyles['first-grid']}
baseDate={addPages(baseDate, -1)}
ariaLabelledby={`${headingIdPrefix}-prev${pageUnit}`}
referrerId={referrerId}
/>
)}
<Grid
Expand All @@ -204,6 +206,7 @@ export const Grids = ({
className={testutilStyles['second-grid']}
baseDate={baseDate}
ariaLabelledby={`${headingIdPrefix}-current${pageUnit}`}
referrerId={referrerId}
/>
</InternalSpaceBetween>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/date-range-picker/calendar/grids/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface GridProps extends GridBaseProps, Required<Pick<CalendarProps, '
currentMonthAriaLabel?: string;
startOfWeek?: DayIndex;
todayAriaLabel?: string;
referrerId?: string;
}

export interface SelectGridProps extends GridBaseProps, Pick<CalendarProps, 'granularity'> {
Expand All @@ -53,4 +54,5 @@ export interface SelectGridProps extends GridBaseProps, Pick<CalendarProps, 'gra
startOfWeek?: DayIndex;
todayAriaLabel?: string;
currentMonthAriaLabel?: string;
referrerId?: string;
}
2 changes: 2 additions & 0 deletions 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',
referrerId,
}: DateRangePickerCalendarProps) {
const isSingleGrid = useMobile();
const isMonthPicker = granularity === 'month';
Expand Down Expand Up @@ -273,6 +274,7 @@ export default function DateRangePickerCalendar({
selectedStartDate={value?.start?.date ? parseDate(value.start.date, !isMonthPicker) : null}
selectedEndDate={value?.end?.date ? parseDate(value.end.date, !isMonthPicker) : null}
headingIdPrefix={headingIdPrefix}
referrerId={referrerId}
/>
</div>

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 @@ -55,6 +55,7 @@ export interface DateRangePickerCalendarProps
value: DateRangePickerProps.PendingAbsoluteValue;
setValue: React.Dispatch<React.SetStateAction<DateRangePickerProps.PendingAbsoluteValue>>;
i18nStrings?: RangeCalendarI18nStrings;
referrerId?: string;
}

export interface RangeInputsProps
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;
referrerId?: string;
}

export function DateRangePickerDropdown({
Expand Down Expand Up @@ -91,6 +92,7 @@ export function DateRangePickerDropdown({
customRelativeRangeUnits,
renderRelativeRangeContent,
granularity = 'day',
referrerId,
}: 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}
referrerId={referrerId}
/>
)}

Expand Down
3 changes: 3 additions & 0 deletions src/date-range-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ const DateRangePicker = React.forwardRef(
);

const mergedRef = useMergeRefs(rootRef, __internalRootRef);
const referrerId = useUniqueId();

return (
<div
Expand All @@ -328,6 +329,7 @@ const DateRangePicker = React.forwardRef(
trigger={trigger}
expandToViewport={expandToViewport}
dropdownId={dropdownId}
triggerId={referrerId}
content={
/* Reset form field context to prevent a wrapper form field from labelling all inputs inside the dropdown. */
<ResetContextsForModal>
Expand Down Expand Up @@ -359,6 +361,7 @@ const DateRangePicker = React.forwardRef(
customRelativeRangeUnits={customRelativeRangeUnits}
renderRelativeRangeContent={renderRelativeRangeContent}
granularity={granularity}
referrerId={referrerId}
/>
)}
</ResetContextsForModal>
Expand Down
4 changes: 2 additions & 2 deletions src/dropdown/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,8 @@ const InternalDropdown = ({
};
}, [open, expandToViewport, isMobile, triggerRef]);

const generatedReferrerId = useUniqueId();
const referrerId = externalTriggerId || generatedReferrerId;
const internalReferrerId = useUniqueId();
const referrerId = externalTriggerId ?? internalReferrerId;

// Compute CSS variable values for min/max width
// These will be used by the use-flexible-width CSS class
Expand Down
Loading
Loading