diff --git a/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap b/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap index 153b69189e..9792e2468b 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap @@ -212,14 +212,15 @@ exports[`test-utils selectors 1`] = ` ], "date-range-picker": [ "awsui_apply-button_mgja0", + "awsui_calendar-date_1afkv", "awsui_calendar-header_mgja0", "awsui_calendar-next-month-btn_mgja0", "awsui_calendar-prev-month-btn_mgja0", + "awsui_calendar-week_1afkv", "awsui_cancel-button_mgja0", "awsui_clear-button_mgja0", "awsui_custom-range-duration-input_16zmw", "awsui_custom-range-unit-select_16zmw", - "awsui_day_1mfbn", "awsui_disabled-reason-tooltip_1mfbn", "awsui_dropdown_mgja0", "awsui_end-date-input_mgja0", @@ -235,7 +236,6 @@ exports[`test-utils selectors 1`] = ` "awsui_start-date_1mfbn", "awsui_start-time-input_mgja0", "awsui_validation-error_mgja0", - "awsui_week_1mfbn", ], "drawer": [ "awsui_drawer_1sxt8", diff --git a/src/calendar/__tests__/calendar.test.tsx b/src/calendar/__tests__/calendar.test.tsx index a2fc95ac45..5811f4e159 100644 --- a/src/calendar/__tests__/calendar.test.tsx +++ b/src/calendar/__tests__/calendar.test.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import { fireEvent, render } from '@testing-library/react'; +import addMonths from 'date-fns/addMonths'; +import range from 'lodash/range'; import MockDate from 'mockdate'; import '../../__a11y__/to-validate-a11y'; @@ -42,13 +44,20 @@ function getDayText(wrapper: CalendarWrapper, row: number, col: number) { return wrapper.findDateAt(row, col).findByClassName(styles['date-inner'])!.getElement().textContent; } -describe('Calendar', () => { - test('check a11y', async () => { - const { container } = renderCalendar(); - await expect(container).toValidateA11y(); - }); +test('check a11y', async () => { + const { container } = renderCalendar(); + await expect(container).toValidateA11y(); }); +const eachMonthOfTheYear = range(0, 11).map(month => addMonths(new Date('2025-01-01'), month).toISOString().split('T')[0]); +test.each(eachMonthOfTheYear)( + 'always renders 42 days, value=%s', + value => { + renderCalendar({ value }); + expect(document.querySelectorAll(`.${styles['calendar-date']}`)).toHaveLength(42); + } +); + describe('Calendar locale US', () => { beforeEach(() => { const locale = new Intl.DateTimeFormat('en-US', { timeZone: 'EST' }); @@ -60,6 +69,22 @@ describe('Calendar locale US', () => { const { wrapper } = renderCalendar(); expect(findCalendarWeekdays(wrapper)[0]).toBe('Sun'); }); + + describe('Calendar header', () => { + test('previous button navigates to previous month', () => { + const { wrapper } = renderCalendar({ value: '2022-01-07' }); + expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); + wrapper.findPreviousButton().click(); + expect(wrapper.findHeader().getElement()).toHaveTextContent('December 2021'); + }); + + test('next button navigates to next month', () => { + const { wrapper } = renderCalendar({ value: '2022-01-07' }); + expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); + wrapper.findNextButton().click(); + expect(wrapper.findHeader().getElement()).toHaveTextContent('February 2022'); + }); + }); }); describe('Calendar locale DE', () => { @@ -75,22 +100,6 @@ describe('Calendar locale DE', () => { }); }); -describe('Calendar header', () => { - test('previous button navigates to previous month', () => { - const { wrapper } = renderCalendar({ value: '2022-01-07' }); - expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); - wrapper.findPreviousButton().click(); - expect(wrapper.findHeader().getElement()).toHaveTextContent('December 2021'); - }); - - test('next button navigates to next month', () => { - const { wrapper } = renderCalendar({ value: '2022-01-07' }); - expect(wrapper.findHeader().getElement()).toHaveTextContent('January 2022'); - wrapper.findNextButton().click(); - expect(wrapper.findHeader().getElement()).toHaveTextContent('February 2022'); - }); -}); - describe('aria labels', () => { describe('aria-label', () => { test('can be set', () => { diff --git a/src/calendar/grid/use-calendar-grid-rows.ts b/src/calendar/grid/use-calendar-grid-rows.ts index 66d0e25cdd..efaa6a0b82 100644 --- a/src/calendar/grid/use-calendar-grid-rows.ts +++ b/src/calendar/grid/use-calendar-grid-rows.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { useMemo } from 'react'; -import { getCalendarMonth } from 'mnth'; +import { getCalendarMonthWithSixRows, getCalendarYear } from '../../internal/utils/date-time/calendar.js'; import { normalizeStartOfWeek } from '../../internal/utils/locale/index.js'; import { CalendarProps } from '../interfaces.js'; @@ -11,7 +11,7 @@ export default function useCalendarGridRows({ baseDate, granularity, locale, - startOfWeek, + startOfWeek: rawStartOfWeek, }: { baseDate: Date; granularity: CalendarProps.Granularity; @@ -20,21 +20,14 @@ export default function useCalendarGridRows({ }) { const isMonthPicker = granularity === 'month'; - const rows = useMemo( - () => - isMonthPicker - ? getCalendarYear(baseDate) - : getCalendarMonth(baseDate, { firstDayOfWeek: normalizeStartOfWeek(startOfWeek, locale) }), - [baseDate, isMonthPicker, startOfWeek, locale] - ); + const rows = useMemo(() => { + if (isMonthPicker) { + return getCalendarYear(baseDate); + } else { + const startOfWeek = normalizeStartOfWeek(rawStartOfWeek, locale); + return getCalendarMonthWithSixRows(baseDate, { startOfWeek, padDates: 'after' }); + } + }, [baseDate, isMonthPicker, rawStartOfWeek, locale]); return rows; } - -// Returns a 3-by-4 matrix with dates corresponding to the initial date-time of each month of the year for a given date. -function getCalendarYear(date: Date): Date[][] { - const year = date.getFullYear(); - return new Array(4) - .fill(0) - .map((_, i: number) => new Array(3).fill(0).map((_, j: number) => new Date(year, i * 3 + j))); -} diff --git a/src/date-picker/__tests__/date-picker-calendar.test.tsx b/src/date-picker/__tests__/date-picker-calendar.test.tsx index 4e0ee91558..f8bbb9d9b6 100644 --- a/src/date-picker/__tests__/date-picker-calendar.test.tsx +++ b/src/date-picker/__tests__/date-picker-calendar.test.tsx @@ -18,6 +18,8 @@ import { import calendarStyles from '../../../lib/components/calendar/styles.selectors.js'; import screenreaderOnlyStyles from '../../../lib/components/internal/components/screenreader-only/styles.selectors.js'; +const toLocaleDateString = window.Date.prototype.toLocaleDateString; + describe('Date picker calendar', () => { const defaultProps: DatePickerProps = { i18nStrings: { @@ -40,7 +42,10 @@ describe('Date picker calendar', () => { const locale = new Intl.DateTimeFormat('en-US', { timeZone: 'UTC' }); jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => locale); }); - afterEach(() => jest.restoreAllMocks()); + afterEach(() => { + jest.restoreAllMocks(); + window.Date.prototype.toLocaleDateString = toLocaleDateString; + }); describe('basic calendar interaction', () => { let wrapper: DatePickerWrapper, getByTestId: (selector: string) => HTMLElement; @@ -116,18 +121,18 @@ describe('Date picker calendar', () => { test('should allow locale override', () => { const locale = 'de-DE'; - const localStringMock = jest.fn().mockReturnValue('März 2018'); - const oldImpl = window.Date.prototype.toLocaleDateString; + const localStringMock = jest.fn().mockReturnValue('translated'); window.Date.prototype.toLocaleDateString = localStringMock; const { wrapper } = renderDatePicker({ ...defaultProps, locale }); wrapper.findOpenCalendarButton().click(); - expect(findCalendarHeaderText(wrapper)).toBe('März 2018'); - // we render 2018/03/22 which results in - // -> 35 (5 weeks á 7 days) + 7 (weekday names) * 2 + 1 (month name) - expect(localStringMock).toHaveBeenCalledTimes(51); + expect(findCalendarHeaderText(wrapper)).toBe('translated'); + // For each calendar we render 6 weeks (42 days) and each requires a label. + // Additionally, we generate short and full labels for weekday names (14 in total), + // and 2 labels for month name. + // 42 + 14 + 2 = 58. + expect(localStringMock).toHaveBeenCalledTimes(58); expect(localStringMock).toHaveBeenCalledWith(locale, expect.any(Object)); - window.Date.prototype.toLocaleDateString = oldImpl; }); test('should override start day of week', () => { diff --git a/src/date-range-picker/calendar/grids/index.tsx b/src/date-range-picker/calendar/grids/index.tsx index 29ec2e393c..b3eaf69ced 100644 --- a/src/date-range-picker/calendar/grids/index.tsx +++ b/src/date-range-picker/calendar/grids/index.tsx @@ -175,6 +175,7 @@ export const Grids = ({ {!isSingleGrid && ( )} ( - () => getCalendarMonth(baseDate, { firstDayOfWeek: startOfWeek }), + const baseDateTime = baseDate?.getTime(); + const calendar = useMemo( + () => { + const startDate = rangeStartDate ?? rangeEndDate; + const endDate = rangeEndDate ?? rangeStartDate; + const selection = startDate && endDate ? ([startDate, endDate] as [Date, Date]) : null; + return new MonthCalendar({ padDates, startOfWeek, baseDate, selection }); + }, // eslint-disable-next-line react-hooks/exhaustive-deps - [baseDateTime, startOfWeek] + [padDates, startOfWeek, baseDateTime, rangeStartDate, rangeEndDate] ); - const weekdays = weeks[0].map(date => date.getDay()); - return ( - {weekdays.map(dayIndex => ( + {calendar.weekdays.map(dayIndex => ( - {weeks.map((week, weekIndex) => { + {calendar.weeks.map(({ days, testIndex }, weekIndex) => { + const isWeekBelongsToMonth = days.some(({ date }) => isSameMonth(date, baseDate)); return ( - - {week.map((date, dateIndex) => { - const isStartDate = !!selectedStartDate && isSameDay(date, selectedStartDate); - const isEndDate = !!selectedEndDate && isSameDay(date, selectedEndDate); - const isSelected = isStartDate || isEndDate; - const isRangeStartDate = !!rangeStartDate && isSameDay(date, rangeStartDate); - const isRangeEndDate = !!rangeEndDate && isSameDay(date, rangeEndDate); - - const isFocused = !!focusedDate && isSameDay(date, focusedDate) && isSameMonth(date, baseDate); - - const dateIsInRange = isStartDate || isEndDate || isInRange(date, rangeStartDate, rangeEndDate); - const inRangeStartWeek = - rangeStartDate && isInRange(date, rangeStartDate, addDays(addWeeks(rangeStartDate, 1), -1)); - const inRangeEndWeek = - rangeEndDate && isInRange(date, rangeEndDate, addDays(addWeeks(rangeEndDate, -1), 1)); - const onlyOneSelected = - !!rangeStartDate && !!rangeEndDate - ? isSameDay(rangeStartDate, rangeEndDate) - : !selectedStartDate || !selectedEndDate; - - const isEnabled = !isDateEnabled || isDateEnabled(date); - const disabledReason = dateDisabledReason(date); - const isDisabledWithReason = !isEnabled && !!disabledReason; - const isFocusable = isFocused && (isEnabled || isDisabledWithReason); - - const baseClasses = { - [styles.day]: true, - [styles['grid-cell']]: true, - [styles['in-first-row']]: weekIndex === 0, - [styles['in-first-column']]: dateIndex === 0, - }; - - if (!isSameMonth(date, baseDate)) { + + {days.map( + ( + { date, isVisible, isInRange, isSelectionTop, isSelectionBottom, isSelectionLeft, isSelectionRight }, + dateIndex + ) => { + const isStartDate = !!selectedStartDate && isSameDay(date, selectedStartDate); + const isEndDate = !!selectedEndDate && isSameDay(date, selectedEndDate); + const isSelected = isStartDate || isEndDate; + const isFocused = !!focusedDate && isSameDay(date, focusedDate); + const onlyOneSelected = + !!rangeStartDate && !!rangeEndDate + ? isSameDay(rangeStartDate, rangeEndDate) + : !selectedStartDate || !selectedEndDate; + + const isDateBelongsToMonth = isSameMonth(date, baseDate); + const isEnabled = (!isDateEnabled || isDateEnabled(date)) && isDateBelongsToMonth; + const disabledReason = dateDisabledReason(date); + const isDisabledWithReason = !isEnabled && !!disabledReason; + const isFocusable = isFocused && (isEnabled || isDisabledWithReason); + + const baseClasses = { + [styles.day]: true, + [testStyles['calendar-date']]: isDateBelongsToMonth, + [styles['grid-cell']]: true, + [styles['in-first-row']]: weekIndex === 0, + [styles['in-first-column']]: dateIndex === 0, + }; + + if (!isVisible) { + return ( + + ); + } + + const handlers: React.HTMLAttributes = {}; + if (isEnabled) { + handlers.onClick = () => onSelectDate(date); + handlers.onFocus = () => onFocusedDateChange(date); + } + + // Can't be focused. + let tabIndex = undefined; + if (isFocusable && (isEnabled || isDisabledWithReason)) { + // Next focus target. + tabIndex = 0; + } else if (isEnabled || isDisabledWithReason) { + // Can be focused programmatically. + tabIndex = -1; + } + + // Screen-reader announcement for the focused day. + let dayAnnouncement = getDateLabel(locale, date, 'short'); + if (isToday(date)) { + dayAnnouncement += '. ' + todayAriaLabel; + } + return ( - + aria-selected={isEnabled ? isSelected || isInRange : undefined} + aria-current={isToday(date) ? 'date' : undefined} + data-date={formatDate(date)} + aria-disabled={!isEnabled} + tabIndex={tabIndex} + disabledReason={isDisabledWithReason ? disabledReason : undefined} + {...handlers} + > + + {dayAnnouncement} + ); } - - const handlers: React.HTMLAttributes = {}; - if (isEnabled) { - handlers.onClick = () => onSelectDate(date); - handlers.onFocus = () => onFocusedDateChange(date); - } - - // Can't be focused. - let tabIndex = undefined; - if (isFocusable && (isEnabled || isDisabledWithReason)) { - // Next focus target. - tabIndex = 0; - } else if (isEnabled || isDisabledWithReason) { - // Can be focused programmatically. - tabIndex = -1; - } - - // Screen-reader announcement for the focused day. - let dayAnnouncement = getDateLabel(locale, date, 'short'); - if (isToday(date)) { - dayAnnouncement += '. ' + todayAriaLabel; - } - - return ( - getDaysInMonth(date) - 7, - [styles['in-range-border-inline-start']]: - dateIndex === 0 || date.getDate() === 1 || isRangeStartDate, - [styles['in-range-border-inline-end']]: - dateIndex === week.length - 1 || isLastDayOfMonth(date) || isRangeEndDate, - [styles.today]: isToday(date), - })} - aria-selected={isEnabled ? isSelected || dateIsInRange : undefined} - aria-current={isToday(date) ? 'date' : undefined} - data-date={formatDate(date)} - aria-disabled={!isEnabled} - tabIndex={tabIndex} - disabledReason={isDisabledWithReason ? disabledReason : undefined} - {...handlers} - > - - {dayAnnouncement} - - ); - })} + )} ); })} @@ -225,14 +219,3 @@ export function MonthlyGrid({
{renderDayName(locale, dayIndex, 'long')} @@ -110,114 +106,112 @@ export function MonthlyGrid({
); } - -function isInRange(date: Date, dateOne: Date | null, dateTwo: Date | null) { - if (!dateOne || !dateTwo || isSameDay(dateOne, dateTwo)) { - return false; - } - - const inRange = - (isAfter(date, dateOne) && isBefore(date, dateTwo)) || (isAfter(date, dateTwo) && isBefore(date, dateOne)); - - return inRange || isSameDay(date, dateOne) || isSameDay(date, dateTwo); -} diff --git a/src/date-range-picker/calendar/grids/styles.scss b/src/date-range-picker/calendar/grids/styles.scss index 9373a9583e..5b2a9f37d0 100644 --- a/src/date-range-picker/calendar/grids/styles.scss +++ b/src/date-range-picker/calendar/grids/styles.scss @@ -102,9 +102,9 @@ } .in-first-column { - border-inline-start: 1px solid transparent; + border-inline-start: 1px solid calendar.$grid-border; - &.in-current-month { + &.in-visible-calendar { border-inline-start: calendar.$grid-border; } } @@ -112,7 +112,7 @@ .enabled { cursor: pointer; - &.in-current-month { + &.in-visible-calendar { color: calendar.$grid-date-color; &:not(.in-range), &.end-date.start-date, @@ -171,19 +171,8 @@ } &.start-date, - &.range-start-date { - @include border-radius(start, start); - &.in-range-border-bottom { - @include border-radius(end, start); - } - } - - &.end-date, - &.range-end-date { - @include border-radius(end, end); - &.in-range-border-top { - @include border-radius(start, end); - } + &.end-date { + /* used in test utils */ } > .day-inner { diff --git a/src/date-range-picker/test-classes/styles.scss b/src/date-range-picker/test-classes/styles.scss new file mode 100644 index 0000000000..3741eefb28 --- /dev/null +++ b/src/date-range-picker/test-classes/styles.scss @@ -0,0 +1,9 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +.calendar-week, +.calendar-date { + /* used in test-utils */ +} diff --git a/src/internal/utils/date-time/calendar.ts b/src/internal/utils/date-time/calendar.ts new file mode 100644 index 0000000000..70df1ef59b --- /dev/null +++ b/src/internal/utils/date-time/calendar.ts @@ -0,0 +1,165 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import addMonths from 'date-fns/addMonths'; +import isAfter from 'date-fns/isAfter'; +import isBefore from 'date-fns/isBefore'; +import isSameDay from 'date-fns/isSameDay'; +import isSameMonth from 'date-fns/isSameMonth'; +import subMonths from 'date-fns/subMonths'; +import { getCalendarMonth } from 'mnth'; + +import { DayIndex } from '../locale'; + +// Returns a 3-by-4 matrix with dates corresponding to the initial date-time of each month of the year for a given date. +export function getCalendarYear(date: Date): Date[][] { + const year = date.getFullYear(); + return new Array(4) + .fill(0) + .map((_, i: number) => new Array(3).fill(0).map((_, j: number) => new Date(year, i * 3 + j))); +} + +interface CalendarWeek { + days: CalendarDate[]; + testIndex?: number; +} + +interface CalendarDate { + date: Date; + isVisible: boolean; + isSelected: boolean; + isInRange: boolean; + isSelectionTop: boolean; + isSelectionBottom: boolean; + isSelectionLeft: boolean; + isSelectionRight: boolean; +} + +export class MonthCalendar { + padDates: 'before' | 'after'; + weekdays: number[]; + weeks: CalendarWeek[]; + range: [Date, Date]; + + constructor({ + padDates, + baseDate, + startOfWeek, + selection, + }: { + padDates: 'before' | 'after'; + baseDate: Date; + startOfWeek: DayIndex; + selection: null | [Date, Date]; + }) { + this.padDates = padDates; + this.weeks = []; + this.range = [baseDate, baseDate]; + + const allCalendarDates = getCalendarMonthWithSixRows(baseDate, { startOfWeek, padDates }); + this.weekdays = allCalendarDates[0].map(date => date.getDay()); + + const isDateVisible = (weekIndex: number, dayIndex: number) => { + const week = allCalendarDates[weekIndex]; + const date = week?.[dayIndex]; + if (!date) { + return false; + } + switch (padDates) { + case 'before': + return isSameMonth(date, baseDate) || isBefore(date, baseDate); + case 'after': + return isSameMonth(date, baseDate) || isAfter(date, baseDate); + } + }; + + const isDateInRange = (weekIndex: number, dayIndex: number) => { + const week = allCalendarDates[weekIndex]; + const date = week?.[dayIndex]; + return !!(date && selection && checkIsInRange(date, selection[0], selection[1])); + }; + + // The test index is only set for weeks that have at least one day that belongs to the current month. + // It starts from the first such week and counts from 1. + const getWeekTestIndex = (weekIndex: number): undefined | number => { + const week = allCalendarDates[weekIndex]; + if (!week) { + return undefined; + } + if (!isSameMonth(week[0], baseDate) && !isSameMonth(week[week.length - 1], baseDate)) { + return undefined; + } + return (getWeekTestIndex(weekIndex - 1) ?? 0) + 1; + }; + + for (let weekIndex = 0; weekIndex < allCalendarDates.length; weekIndex++) { + const daysOfWeek = allCalendarDates[weekIndex]; + const week: CalendarWeek = { days: [], testIndex: getWeekTestIndex(weekIndex) }; + + for (let dayIndex = 0; dayIndex < daysOfWeek.length; dayIndex++) { + const date = daysOfWeek[dayIndex]; + const isVisible = isDateVisible(weekIndex, dayIndex); + const isSelected = !!(selection && (isSameDay(date, selection[0]) || isSameDay(date, selection[1]))); + const isInRange = isDateInRange(weekIndex, dayIndex); + const isTop = isVisible && !isDateVisible(weekIndex - 1, dayIndex); + const isBottom = isVisible && !isDateVisible(weekIndex + 1, dayIndex); + const isLeft = isVisible && !isDateVisible(weekIndex, dayIndex - 1); + const isRight = isVisible && !isDateVisible(weekIndex, dayIndex + 1); + const isRangeTop = isInRange && !isDateInRange(weekIndex - 1, dayIndex); + const isRangeBottom = isInRange && !isDateInRange(weekIndex + 1, dayIndex); + const isRangeLeft = isInRange && !isDateInRange(weekIndex, dayIndex - 1); + const isRangeRight = isInRange && !isDateInRange(weekIndex, dayIndex + 1); + week.days.push({ + date, + isVisible, + isSelected, + isInRange, + isSelectionTop: isTop || isRangeTop, + isSelectionBottom: isBottom || isRangeBottom, + isSelectionLeft: isLeft || isRangeLeft, + isSelectionRight: isRight || isRangeRight, + }); + } + this.weeks.push(week); + } + } +} + +export function getCalendarMonthWithSixRows( + date: Date, + { startOfWeek, padDates }: { startOfWeek: DayIndex; padDates: 'before' | 'after' } +) { + switch (padDates) { + case 'before': + return [...getPrevMonthRows(date, startOfWeek), ...getCurrentMonthRows(date, startOfWeek)].slice(-6); + case 'after': + return [...getCurrentMonthRows(date, startOfWeek), ...getNextMonthRows(date, startOfWeek)].slice(0, 6); + } +} + +function checkIsInRange(date: Date, dateOne: Date | null, dateTwo: Date | null) { + if (!dateOne || !dateTwo || isSameDay(dateOne, dateTwo)) { + return false; + } + + const inRange = + (isAfter(date, dateOne) && isBefore(date, dateTwo)) || (isAfter(date, dateTwo) && isBefore(date, dateOne)); + + return inRange || isSameDay(date, dateOne) || isSameDay(date, dateTwo); +} + +function getCurrentMonthRows(date: Date, firstDayOfWeek: DayIndex) { + return getCalendarMonth(date, { firstDayOfWeek }); +} + +function getPrevMonthRows(date: Date, firstDayOfWeek: DayIndex) { + const rows = getCalendarMonth(subMonths(date, 1), { firstDayOfWeek }); + const lastDay = rows[rows.length - 1][rows[rows.length - 1].length - 1]; + return !isSameMonth(date, lastDay) ? rows : rows.slice(0, -1); +} + +function getNextMonthRows(date: Date, firstDayOfWeek: DayIndex) { + const rows = getCalendarMonth(addMonths(date, 1), { firstDayOfWeek }); + const firstDay = rows[0][0]; + return !isSameMonth(date, firstDay) ? rows : rows.slice(1); +} diff --git a/src/internal/utils/locale/index.ts b/src/internal/utils/locale/index.ts index db10bf2376..f678a43261 100644 --- a/src/internal/utils/locale/index.ts +++ b/src/internal/utils/locale/index.ts @@ -3,4 +3,4 @@ export { mergeLocales } from './merge-locales'; export { normalizeLocale } from './normalize-locale'; -export { normalizeStartOfWeek } from './normalize-start-of-week'; +export { normalizeStartOfWeek, DayIndex } from './normalize-start-of-week'; diff --git a/src/internal/utils/locale/normalize-start-of-week.ts b/src/internal/utils/locale/normalize-start-of-week.ts index 62269cc660..3e8f45bc12 100644 --- a/src/internal/utils/locale/normalize-start-of-week.ts +++ b/src/internal/utils/locale/normalize-start-of-week.ts @@ -3,7 +3,7 @@ import { getWeekStartByLocale } from 'weekstart'; -type DayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; +export type DayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; export function normalizeStartOfWeek(startOfWeek: number | undefined, locale: string) { return (typeof startOfWeek === 'number' ? startOfWeek % 7 : getWeekStartByLocale(locale)) as DayIndex; diff --git a/src/test-utils/dom/date-range-picker/index.ts b/src/test-utils/dom/date-range-picker/index.ts index 6fe9db2c0b..9bd96b876e 100644 --- a/src/test-utils/dom/date-range-picker/index.ts +++ b/src/test-utils/dom/date-range-picker/index.ts @@ -12,6 +12,7 @@ import SelectWrapper from '../select'; import gridStyles from '../../../date-range-picker/calendar/grids/styles.selectors.js'; import relativeRangeStyles from '../../../date-range-picker/relative-range/styles.selectors.js'; import styles from '../../../date-range-picker/styles.selectors.js'; +import testStyles from '../../../date-range-picker/test-classes/styles.selectors.js'; export class CalendarDateWrapper extends ComponentWrapper { findDisabledReason(): ElementWrapper | null { @@ -122,7 +123,7 @@ export class DrpDropdownWrapper extends ComponentWrapper { ): CalendarDateWrapper { const gridClassName = grid === 'right' ? styles['second-grid'] : styles['first-grid']; return this.findComponent( - `.${gridClassName} .${gridStyles.week}:nth-child(${row}) .${gridStyles.day}:nth-child(${column})`, + `.${gridClassName} .${testStyles['calendar-week']}[data-awsui-weekindex="${row}"] .${testStyles['calendar-date']}:nth-child(${column})`, CalendarDateWrapper )!; }