diff --git a/jest.config.ts b/jest.config.ts index 97916292de..113ba5ddf6 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,4 +1,5 @@ import { Config } from "jest"; + import coverageThresholds from "./coverage-thresholds.json"; const esmOnlyPackages = [ diff --git a/package-lock.json b/package-lock.json index b275957836..884955d2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "lodash": "^4.17.21", "polished": "^4.2.2", "prop-types": "^15.8.1", - "react-day-picker": "~7.4.10", + "react-day-picker": "^9.3.2", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-is": "^17.0.2", @@ -2695,6 +2695,12 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", + "license": "MIT" + }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -24219,14 +24225,33 @@ } }, "node_modules/react-day-picker": { - "version": "7.4.10", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-7.4.10.tgz", - "integrity": "sha512-/QkK75qLKdyLmv0kcVzhL7HoJPazoZXS8a6HixbVoK6vWey1Od1WRLcxfyEiUsRfccAlIlf6oKHShqY2SM82rA==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.3.2.tgz", + "integrity": "sha512-Rj2gPPVYKqZbSF8DxaLteHY+45zd6swf5yE3hmJ8m6VEqPI2ve9CuZsDvQ10tIt3ckRJ9hmLa5t0SsmLlXllhw==", + "license": "MIT", "dependencies": { - "prop-types": "^15.6.2" + "@date-fns/tz": "^1.2.0", + "date-fns": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" }, "peerDependencies": { - "react": "~0.13.x || ~0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0" + "react": ">=16.8.0" + } + }, + "node_modules/react-day-picker/node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/react-dnd": { diff --git a/package.json b/package.json index 809539244b..cbac8a1ce5 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "lodash": "^4.17.21", "polished": "^4.2.2", "prop-types": "^15.8.1", - "react-day-picker": "~7.4.10", + "react-day-picker": "^9.3.2", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-is": "^17.0.2", diff --git a/playwright/components/date-input/index.ts b/playwright/components/date-input/index.ts index 187fd6024b..b4cf96625d 100644 --- a/playwright/components/date-input/index.ts +++ b/playwright/components/date-input/index.ts @@ -1,4 +1,5 @@ import type { Page } from "@playwright/test"; + import { DAY_PICKER_WRAPPER, DAY_PICKER_HEADING } from "./locators"; // component preview locators @@ -6,4 +7,4 @@ import { DAY_PICKER_WRAPPER, DAY_PICKER_HEADING } from "./locators"; export const dayPickerWrapper = (page: Page) => page.locator(DAY_PICKER_WRAPPER); export const dayPickerHeading = (page: Page) => - page.locator(DAY_PICKER_HEADING).locator("div"); + page.locator(DAY_PICKER_HEADING); diff --git a/playwright/components/date-input/locators.ts b/playwright/components/date-input/locators.ts index 7e4ea8af56..7dd736eea1 100644 --- a/playwright/components/date-input/locators.ts +++ b/playwright/components/date-input/locators.ts @@ -1,3 +1,3 @@ // component preview locators -export const DAY_PICKER_WRAPPER = 'div[class="DayPicker-wrapper"]'; -export const DAY_PICKER_HEADING = ".DayPicker-Caption"; +export const DAY_PICKER_WRAPPER = 'div[class="rdp-root"]'; +export const DAY_PICKER_HEADING = 'span[class="rdp-caption_label"]'; diff --git a/src/__internal__/utils/logger/index.ts b/src/__internal__/utils/logger/index.ts index 56900e0145..5dcc7aa0af 100644 --- a/src/__internal__/utils/logger/index.ts +++ b/src/__internal__/utils/logger/index.ts @@ -22,6 +22,12 @@ const Logger = { console.warn(`[Deprecation] ${message}`); } }, + + warn: (message: string) => { + if (enabled) { + console.warn(`[Warning] ${message}`); + } + }, }; export default Logger; diff --git a/src/__internal__/utils/logger/logger.test.ts b/src/__internal__/utils/logger/logger.test.ts index cb3df963fa..5100a3d736 100644 --- a/src/__internal__/utils/logger/logger.test.ts +++ b/src/__internal__/utils/logger/logger.test.ts @@ -1,6 +1,6 @@ import Logger from "."; -test("should not output a warning to the console when logging is disabled", () => { +test("should not output a warning to the console when logging is disabled and a deprecation message is fired", () => { Logger.setEnabledState(false); const consoleWarnSpy = jest .spyOn(console, "warn") @@ -22,3 +22,26 @@ test("should output a warning to the console with a deprecation prefix when logg ); consoleWarnSpy.mockReset(); }); + +test("should output a warning to the console with a warning prefix when logging is enabled", () => { + Logger.setEnabledState(true); + const consoleWarnSpy = jest + .spyOn(console, "warn") + .mockImplementation(() => {}); + Logger.warn("This is a warning message"); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + "[Warning] This is a warning message", + ); + consoleWarnSpy.mockReset(); +}); + +test("should not output a warning to the console when logging is disabled and a warning message is fired", () => { + Logger.setEnabledState(false); + const consoleWarnSpy = jest + .spyOn(console, "warn") + .mockImplementation(() => {}); + Logger.warn("This is a warning message"); + + expect(consoleWarnSpy).not.toHaveBeenCalled(); +}); diff --git a/src/components/date/__internal__/date-fns-fp/index.ts b/src/components/date/__internal__/date-fns-fp/index.ts index b815474310..2b65e25e17 100644 --- a/src/components/date/__internal__/date-fns-fp/index.ts +++ b/src/components/date/__internal__/date-fns-fp/index.ts @@ -3,5 +3,7 @@ export { default as format } from "date-fns/fp/format"; export { default as formatISO } from "date-fns/fp/formatISO"; export { default as isMatch } from "date-fns/fp/isMatch"; +export { default as isValid } from "date-fns/fp/isValid"; export { default as parse } from "date-fns/fp/parse"; +export { default as parseWithOptions } from "date-fns/fp/parseWithOptions"; export { default as parseISO } from "date-fns/fp/parseISO"; diff --git a/src/components/date/__internal__/date-picker/date-picker.component.tsx b/src/components/date/__internal__/date-picker/date-picker.component.tsx index d975644df4..2c8d663a7b 100644 --- a/src/components/date/__internal__/date-picker/date-picker.component.tsx +++ b/src/components/date/__internal__/date-picker/date-picker.component.tsx @@ -1,37 +1,42 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; -import DayPicker, { +import { flip, offset } from "@floating-ui/dom"; +import React, { + useCallback, + KeyboardEvent, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + DayPicker, DayPickerProps, - DayModifiers, - Modifier, - LocaleUtils, + defaultLocale, + Modifiers, } from "react-day-picker"; -import { flip, offset } from "@floating-ui/dom"; +import Logger from "../../../../__internal__/utils/logger"; -import { getDisabledDays } from "../utils"; -import Popover from "../../../../__internal__/popover"; import useLocale from "../../../../hooks/__internal__/useLocale"; +import Popover from "../../../../__internal__/popover"; import Navbar from "../navbar"; import Weekday from "../weekday"; -import StyledDayPicker from "./day-picker.style"; -import Events from "../../../../__internal__/utils/helpers/events"; +import { getDisabledDays } from "../utils"; import { defaultFocusableSelectors } from "../../../../__internal__/focus-trap/focus-trap-utils"; +import Events from "../../../../__internal__/utils/helpers/events"; + +import StyledDayPicker from "./day-picker.style"; type CustomRefObject = { current?: T | null; }; -/** there is an issue with typescript-to-proptypes package that means we need to override these types */ -interface Modifiers { - today: NonNullable | NonNullable[]; - outside: NonNullable | NonNullable[]; - [other: string]: NonNullable | NonNullable[]; -} - export interface PickerProps - extends Omit { - disabledDays?: NonNullable | NonNullable[] | undefined[]; + extends Omit< + DayPickerProps, + "mode" | "disabledDays" | "modifiers" | "selectedDays" + > { + disabledDays?: NonNullable | NonNullable[] | undefined[]; modifiers?: Partial; - selectedDays?: NonNullable | NonNullable[] | undefined[]; + selectedDays?: NonNullable | NonNullable[] | undefined[]; } export interface DatePickerProps { @@ -46,7 +51,7 @@ export interface DatePickerProps { /** Element that the DatePicker will be displayed under */ inputElement: CustomRefObject; /** Currently selected date */ - selectedDays?: Date; + selectedDays?: Date | undefined; /** Callback to handle mousedown event on picker container */ pickerMouseDown?: () => void; /** Sets whether the picker should be displayed */ @@ -82,6 +87,17 @@ export const DatePicker = ({ pickerTabGuardId, onPickerClose, }: DatePickerProps) => { + let minMaxDateWarningLogged = false; + if (!!minDate !== !!maxDate && !minMaxDateWarningLogged) { + Logger.warn( + "Both minDate and maxDate must be provided to DatePicker component in order to set disabled dates.", + ); + minMaxDateWarningLogged = true; + } + + const [focusedMonth, setFocusedMonth] = useState( + selectedDays || new Date(), + ); const locale = useLocale(); const { localize, options } = locale.date.dateFnsLocale(); const { weekStartsOn } = options || /* istanbul ignore next */ {}; @@ -93,13 +109,6 @@ export const DatePicker = ({ }), [localize], ); - const monthsShort = useMemo( - () => - Array.from({ length: 12 }).map((_, i) => - localize?.month(i, { width: "abbreviated" }).substring(0, 3), - ), - [localize], - ); const weekdaysLong = useMemo( () => Array.from({ length: 7 }).map((_, i) => localize?.day(i)), [localize], @@ -119,67 +128,33 @@ export const DatePicker = ({ }, [locale, localize]); const ref = useRef(null); - useEffect(() => { - if (open) { - // this is a temporary fix for some axe issues that are baked into the library we use for the picker - const captionElement = ref.current?.querySelector(".DayPicker-Caption"); - /* istanbul ignore else */ - if (captionElement) { - captionElement.removeAttribute("role"); - captionElement.removeAttribute("aria-live"); - } - - // focus the selected or today's date first - const selectedDay = - ref.current?.querySelector(".DayPicker-Day--selected") || - ref.current?.querySelector(".DayPicker-Day--today"); - const firstDay = ref.current?.querySelector( - ".DayPicker-Day[tabindex='0']", - ); - - /* istanbul ignore else */ - if (selectedDay && firstDay !== selectedDay) { - selectedDay?.setAttribute("tabindex", "0"); - firstDay?.setAttribute("tabindex", "-1"); - } - } - }, [open]); - const handleDayClick = ( - date: Date, - modifiers: DayModifiers, - ev: React.MouseEvent, + date?: Date, + e?: React.MouseEvent, ) => { - if (!modifiers.disabled) { - const { id, name } = inputElement?.current - ?.firstChild as HTMLInputElement; - ev.target = { - ...ev.target, - id, - name, - } as HTMLInputElement; - onDayClick?.(date, ev); - onPickerClose?.(); - } + /* istanbul ignore else */ + if (date) onDayClick?.(date, e as React.MouseEvent); + onPickerClose?.(); }; const handleKeyUp = useCallback( - (ev) => { + (ev: KeyboardEvent) => { /* istanbul ignore else */ if (open && Events.isEscKey(ev)) { + setFocusedMonth(selectedDays); inputElement.current?.querySelector("input")?.focus(); setOpen(false); onPickerClose?.(); ev.stopPropagation(); } }, - [inputElement, onPickerClose, open, setOpen], + [inputElement, onPickerClose, open, selectedDays, setOpen], ); const handleOnKeyDown = (ev: React.KeyboardEvent) => { /* istanbul ignore else */ if ( - ref.current?.querySelector(".DayPicker-NavBar button") === + ref.current?.querySelector(".rdp-nav button") === document.activeElement && Events.isTabKey(ev) && Events.isShiftKey(ev) @@ -193,7 +168,7 @@ export const DatePicker = ({ const handleOnDayKeyDown = ( _day: Date, - _modifiers: DayModifiers, + _modifiers: Modifiers, ev: React.KeyboardEvent, ) => { // we need to manually handle this as the picker may be in a Portal @@ -223,66 +198,108 @@ export const DatePicker = ({ } }; - const formatDay = (date: Date) => - `${weekdaysShort[date.getDay()]} ${date.getDate()} ${ - monthsShort[date.getMonth()] - } ${date.getFullYear()}`; + useEffect(() => { + if (selectedDays) { + setFocusedMonth(selectedDays); + } + }, [selectedDays]); + + useEffect(() => { + if (!open && selectedDays) { + const fMonth = focusedMonth?.getMonth(); + const sMonth = selectedDays?.getMonth(); + if (fMonth !== sMonth) setFocusedMonth(selectedDays); + } + }, [focusedMonth, open, selectedDays]); if (!open) { return null; } - const localeUtils = { formatDay } as LocaleUtils; - const handleTabGuardFocus = () => { ref.current?.querySelector("button")?.focus(); }; return ( - - + -
- { - const { className, weekday } = weekdayElementProps; + +
+ { + const date = d as Date; + handleDayClick(date, e); + }} + components={{ + Nav: (props) => { + return ; + }, + Weekday: (props) => { + const fixedDays = { + Sunday: 0, + Monday: 1, + Tuesday: 2, + Wednesday: 3, + Thursday: 4, + Friday: 5, + Saturday: 6, + }; + const { className, "aria-label": ariaLabel } = props; + const dayIndex = fixedDays[ariaLabel as keyof typeof fixedDays]; - return ( - - {weekdaysShort[weekday]} - - ); - }} - navbarElement={} - fixedWeeks - initialMonth={selectedDays || undefined} - disabledDays={getDisabledDays(minDate, maxDate)} - locale={locale.locale()} - localeUtils={localeUtils} - onDayKeyDown={handleOnDayKeyDown} - {...pickerProps} - /> - - + return ( + + {weekdaysShort[dayIndex]} + + ); + }, + }} + fixedWeeks + defaultMonth={selectedDays || undefined} + onDayKeyDown={(date, modifiers, e) => { + handleOnDayKeyDown( + date, + modifiers, + e as React.KeyboardEvent, + ); + }} + {...pickerProps} + showOutsideDays + mode="single" + /> + + + ); }; diff --git a/src/components/date/__internal__/date-picker/date-picker.test.tsx b/src/components/date/__internal__/date-picker/date-picker.test.tsx index fbb0f2a94b..85a8d196a8 100644 --- a/src/components/date/__internal__/date-picker/date-picker.test.tsx +++ b/src/components/date/__internal__/date-picker/date-picker.test.tsx @@ -1,7 +1,6 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; - import enGBLocale from "date-fns/locale/en-GB"; import deLocale from "date-fns/locale/de"; import esLocale from "date-fns/locale/es"; @@ -22,7 +21,7 @@ const DatePickerWithInput = (props: MockProps) => { const ref = React.useRef(null); const Input = () => (
- +
); return ( @@ -60,9 +59,15 @@ test("should render the day element that matches the `selectedDate` when prop is open />, ); - const selectedDay = screen.getByRole("gridcell", { name: "Thu 4 Apr 2019" }); - expect(selectedDay).toHaveAttribute("aria-selected", "true"); + const selectedDay = screen.getByLabelText("Thursday, April 4th, 2019", { + exact: false, + }); + + expect(selectedDay).toHaveAttribute( + "aria-label", + "Thursday, April 4th, 2019, selected", + ); }); test("should render the expected weekday with `aria-disabled=true` attribute when `minDate` is '2019-04-02'", () => { @@ -74,11 +79,11 @@ test("should render the expected weekday with `aria-disabled=true` attribute whe open />, ); - const disabledDay = screen.getByRole("gridcell", { name: "Mon 1 Apr 2019" }); - const activeDay = screen.getByRole("gridcell", { name: "Tue 2 Apr 2019" }); + const disabledDay = screen.getByLabelText("Monday, April 1st, 2019"); + const activeDay = screen.getByLabelText("Tuesday, April 2nd, 2019"); - expect(disabledDay).toHaveAttribute("aria-disabled", "true"); - expect(activeDay).toHaveAttribute("aria-disabled", "false"); + expect(disabledDay).toBeDisabled(); + expect(activeDay).toBeEnabled(); }); test("should not render any of the current month's weekdays with `aria-disabled=true` attribute when `minDate` is invalid", () => { @@ -91,12 +96,10 @@ test("should not render any of the current month's weekdays with `aria-disabled= />, ); // need to filter out the weekdays that are not in the current month - const currentMonthDays = screen.getAllByRole("gridcell", { - name: new RegExp("Apr", "i"), - }); + const currentMonthDays = screen.getAllByLabelText("April", { exact: false }); currentMonthDays.forEach((day) => { - expect(day).toHaveAttribute("aria-disabled", "false"); + expect(day).toBeEnabled(); }); }); @@ -110,12 +113,10 @@ test("should not render any of the current month's weekdays with `aria-disabled= />, ); // need to filter out the weekdays that are not in the current month - const currentMonthDays = screen.getAllByRole("gridcell", { - name: new RegExp("Apr", "i"), - }); + const currentMonthDays = screen.getAllByLabelText("April", { exact: false }); currentMonthDays.forEach((day) => { - expect(day).toHaveAttribute("aria-disabled", "false"); + expect(day).toBeEnabled(); }); }); @@ -128,11 +129,11 @@ test("should render the expected weekday with `aria-disabled=true` attribute whe open />, ); - const disabledDay = screen.getByRole("gridcell", { name: "Sat 6 Apr 2019" }); - const activeDay = screen.getByRole("gridcell", { name: "Fri 5 Apr 2019" }); + const disabledDay = screen.getByLabelText("Saturday, April 6th, 2019"); + const activeDay = screen.getByLabelText("Friday, April 5th, 2019"); - expect(disabledDay).toHaveAttribute("aria-disabled", "true"); - expect(activeDay).toHaveAttribute("aria-disabled", "false"); + expect(disabledDay).toBeDisabled(); + expect(activeDay).toBeEnabled(); }); test("should not render any of the current month's weekdays with `aria-disabled=true` attribute when `maxDate` is invalid", () => { @@ -145,12 +146,10 @@ test("should not render any of the current month's weekdays with `aria-disabled= />, ); // need to filter out the weekdays that are not in the current month - const currentMonthDays = screen.getAllByRole("gridcell", { - name: new RegExp("Apr", "i"), - }); + const currentMonthDays = screen.getAllByLabelText("April", { exact: false }); currentMonthDays.forEach((day) => { - expect(day).toHaveAttribute("aria-disabled", "false"); + expect(day).toBeEnabled(); }); }); @@ -164,12 +163,10 @@ test("should not render any of the current month's weekdays with `aria-disabled= />, ); // need to filter out the weekdays that are not in the current month - const currentMonthDays = screen.getAllByRole("gridcell", { - name: new RegExp("Apr", "i"), - }); + const currentMonthDays = screen.getAllByLabelText("April", { exact: false }); currentMonthDays.forEach((day) => { - expect(day).toHaveAttribute("aria-disabled", "false"); + expect(day).toBeEnabled(); }); }); @@ -185,7 +182,7 @@ test("should not call `onDayClick` callback when a user clicks a disabled day", onDayClick={onDayClick} />, ); - const disabledDay = screen.getByRole("gridcell", { name: "Mon 1 Apr 2019" }); + const disabledDay = screen.getByLabelText("Monday, April 1st, 2019"); await user.click(disabledDay); expect(onDayClick).not.toHaveBeenCalled(); @@ -202,7 +199,7 @@ test("should call `onDayClick` callback when a user clicks a day that is not dis onDayClick={onDayClick} />, ); - const activeDay = screen.getByRole("gridcell", { name: "Tue 2 Apr 2019" }); + const activeDay = screen.getByLabelText("Tuesday, April 2nd, 2019"); await user.click(activeDay); expect(onDayClick).toHaveBeenCalled(); diff --git a/src/components/date/__internal__/date-picker/day-picker.style.ts b/src/components/date/__internal__/date-picker/day-picker.style.ts index 3fff700e4b..c745529c86 100644 --- a/src/components/date/__internal__/date-picker/day-picker.style.ts +++ b/src/components/date/__internal__/date-picker/day-picker.style.ts @@ -1,4 +1,5 @@ import styled, { css } from "styled-components"; + import baseTheme from "../../../../style/themes/base"; import addFocusStyling from "../../../../style/utils/add-focus-styling"; @@ -6,196 +7,308 @@ const oldFocusStyling = ` outline: solid 3px var(--colorsSemanticFocus500); `; -// Styles copied from https://github.com/gpbl/react-day-picker/blob/v6.1.1/src/style.css -const addReactDayPickerStyles = () => ` - .DayPicker { - display: inline-block; +const officialReactDayPickerStyling = () => css` + /* Variables declaration */ + /* prettier-ignore */ + .rdp-root { + --rdp-accent-color: blue; /* The accent color used for selected days and UI elements. */ + --rdp-accent-background-color: #f0f0ff; /* The accent background color used for selected days and UI elements. */ + + --rdp-day-height: 2.75rem; /* The height of the day cells. */ + --rdp-day-width: 2.75rem; /* The width of the day cells. */ + + --rdp-day_button-border-radius: 100%; /* The border radius of the day cells. */ + --rdp-day_button-border: 2px solid transparent; /* The border of the day cells. */ + --rdp-day_button-height: var(--rdp-day-height); /* The height of the day cells. */ + --rdp-day_button-width: var(--rdp-day-width); /* The width of the day cells. */ + + --rdp-selected-border: 2px solid var(--rdp-accent-color); /* The border of the selected days. */ + --rdp-disabled-opacity: 0.5; /* The opacity of the disabled days. */ + --rdp-outside-opacity: 0.75; /* The opacity of the days outside the current month. */ + --rdp-today-color: var(--rdp-accent-color); /* The color of the today's date. */ + + --rdp-dropdown-gap: 0.5rem;/* The gap between the dropdowns used in the month captons. */ + + --rdp-months-gap: 2rem; /* The gap between the months in the multi-month view. */ + + --rdp-nav_button-disabled-opacity: 0.5; /* The opacity of the disabled navigation buttons. */ + --rdp-nav_button-height: 2.25rem; /* The height of the navigation buttons. */ + --rdp-nav_button-width: 2.25rem; /* The width of the navigation buttons. */ + --rdp-nav-height: 2.75rem; /* The height of the navigation bar. */ + + --rdp-range_middle-background-color: var(--rdp-accent-background-color); /* The color of the background for days in the middle of a range. */ + --rdp-range_middle-foreground-color: white; /* The foregraound color for days in the middle of a range. */ + --rdp-range_middle-color: inherit;/* The color of the range text. */ + + --rdp-range_start-color: white; /* The color of the range text. */ + --rdp-range_start-background: linear-gradient(var(--rdp-gradient-direction), transparent 50%, var(--rdp-range_middle-background-color) 50%); /* Used for the background of the start of the selected range. */ + --rdp-range_start-date-background-color: var(--rdp-accent-color); /* The background color of the date when at the start of the selected range. */ + + --rdp-range_end-background: linear-gradient(var(--rdp-gradient-direction), var(--rdp-range_middle-background-color) 50%, transparent 50%); /* Used for the background of the end of the selected range. */ + --rdp-range_end-color: white;/* The color of the range text. */ + --rdp-range_end-date-background-color: var(--rdp-accent-color); /* The background color of the date when at the end of the selected range. */ + + --rdp-week_number-border-radius: 100%; /* The border radius of the week number. */ + --rdp-week_number-border: 2px solid transparent; /* The border of the week number. */ + + --rdp-week_number-height: var(--rdp-day-height); /* The height of the week number cells. */ + --rdp-week_number-opacity: 0.75; /* The opacity of the week number. */ + --rdp-week_number-width: var(--rdp-day-width); /* The width of the week number cells. */ + --rdp-weeknumber-text-align: center; /* The text alignment of the weekday cells. */ + + --rdp-weekday-opacity: 0.75; /* The opacity of the weekday. */ + --rdp-weekday-padding: 0.5rem 0rem; /* The padding of the weekday. */ + --rdp-weekday-text-align: center; /* The text alignment of the weekday cells. */ + + --rdp-gradient-direction: 90deg; } - .DayPicker-wrapper { - display: flex; - flex-wrap: wrap; + .rdp-root[dir="rtl"] { + --rdp-gradient-direction: -90deg; + } + + /* Root of the component. */ + .rdp-root { + position: relative; /* Required to position the navigation toolbar. */ + box-sizing: border-box; + } + + .rdp-root * { + box-sizing: border-box; + } + + .rdp-day { + width: var(--sizing500); + height: var(--sizing450); + text-align: center; + } + + .rdp-day_button { + background: none; + padding: 0; + margin: 0; + cursor: pointer; + font: inherit; + color: inherit; justify-content: center; - position: relative; - user-select: none; - flex-direction: row; - padding: 1rem 0; + align-items: center; + display: flex; + min-width: var(--sizing500); + height: var(--sizing450); + border: var(--rdp-day_button-border); + border-radius: var(--rdp-day_button-border-radius); } - .DayPicker-Month { - display: table; - border-collapse: collapse; - border-spacing: 0; - user-select: none; - margin: 0 1rem; + .rdp-day_button:disabled { + cursor: revert; } - .DayPicker-NavBar { - position: absolute; - left: 0; - right: 0; + .rdp-day_button { + outline: none; } - .DayPicker-NavButton { - position: absolute; - width: 1.5rem; - height: 1.5rem; - background-repeat: no-repeat; - background-position: center; - background-size: contain; + .rdp-caption_label { + z-index: 1; + position: relative; + display: inline-flex; + align-items: center; + white-space: nowrap; + border: 0; + } + + .rdp-button_next, + .rdp-button_previous { + border: none; + background: none; + padding: 0; + margin: 0; cursor: pointer; + font: inherit; + color: inherit; + -moz-appearance: none; + -webkit-appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + appearance: none; + width: var(--rdp-nav_button-width); + height: var(--rdp-nav_button-height); } - .DayPicker-NavButton--prev { - left: 1rem; - background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI2cHgiIGhlaWdodD0iNTBweCIgdmlld0JveD0iMCAwIDI2IDUwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjIgKDEyMDQzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5wcmV2PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9InByZXYiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzLjM5MzE5MywgMjUuMDAwMDAwKSBzY2FsZSgtMSwgMSkgdHJhbnNsYXRlKC0xMy4zOTMxOTMsIC0yNS4wMDAwMDApIHRyYW5zbGF0ZSgwLjg5MzE5MywgMC4wMDAwMDApIiBmaWxsPSIjNTY1QTVDIj4KICAgICAgICAgICAgPHBhdGggZD0iTTAsNDkuMTIzNzMzMSBMMCw0NS4zNjc0MzQ1IEwyMC4xMzE4NDU5LDI0LjcyMzA2MTIgTDAsNC4yMzEzODMxNCBMMCwwLjQ3NTA4NDQ1OSBMMjUsMjQuNzIzMDYxMiBMMCw0OS4xMjM3MzMxIEwwLDQ5LjEyMzczMzEgWiIgaWQ9InJpZ2h0IiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4K"); + .rdp-button_next:disabled, + .rdp-button_previous:disabled { + cursor: revert; + opacity: var(--rdp-nav_button-disabled-opacity); } - .DayPicker-NavButton--next { - right: 1rem; - background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI2cHgiIGhlaWdodD0iNTBweCIgdmlld0JveD0iMCAwIDI2IDUwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjIgKDEyMDQzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5uZXh0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Im5leHQiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuOTUxNDUxLCAwLjAwMDAwMCkiIGZpbGw9IiM1NjVBNUMiPgogICAgICAgICAgICA8cGF0aCBkPSJNMCw0OS4xMjM3MzMxIEwwLDQ1LjM2NzQzNDUgTDIwLjEzMTg0NTksMjQuNzIzMDYxMiBMMCw0LjIzMTM4MzE0IEwwLDAuNDc1MDg0NDU5IEwyNSwyNC43MjMwNjEyIEwwLDQ5LjEyMzczMzEgTDAsNDkuMTIzNzMzMSBaIiBpZD0icmlnaHQiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo="); + .rdp-chevron { + display: inline-block; + fill: var(--rdp-accent-color); } - .DayPicker-NavButton--interactionDisabled { - display: none; + .rdp-root[dir="rtl"] .rdp-nav .rdp-chevron { + transform: rotate(180deg); } - .DayPicker-Caption { - display: table-caption; - height: 1.5rem; - text-align: center; + .rdp-root[dir="rtl"] .rdp-nav .rdp-chevron { + transform: rotate(180deg); + transform-origin: 50%; } - .DayPicker-Weekdays { - display: table-header-group; + .rdp-dropdowns { + position: relative; + display: inline-flex; + align-items: center; + gap: var(--rdp-dropdown-gap); + } + .rdp-dropdown { + z-index: 2; + /* Reset */ + opacity: 0; + appearance: none; + position: absolute; + inset-block-start: 0; + inset-block-end: 0; + inset-inline-start: 0; + width: 100%; + margin: 0; + padding: 0; + cursor: inherit; + border: none; + line-height: inherit; } - .DayPicker-WeekdaysRow { - display: table-row; + .rdp-dropdown_root { + position: relative; + display: inline-flex; + align-items: center; } - .DayPicker-Weekday { - display: table-cell; + .rdp-dropdown_root[data-disabled="true"] .rdp-chevron { + opacity: var(--rdp-disabled-opacity); + } - abbr { - text-decoration: none; - } + .rdp-month_caption { + display: flex; + align-content: center; + height: var(--rdp-nav-height); + font-weight: bold; + font-size: large; } - .DayPicker-Body { - display: table-row-group; + .rdp-months { + position: relative; + display: flex; + flex-wrap: wrap; + gap: var(--rdp-months-gap); + max-width: fit-content; } - .DayPicker-Week { - display: table-row; + .rdp-month_grid { + border-collapse: collapse; } - .DayPicker-Day { - display: table-cell; - padding: 0.5rem; - border: 1px solid #eaecec; - text-align: center; - cursor: pointer; - vertical-align: middle; + .rdp-nav { + position: absolute; + inset-block-start: 0; + inset-inline-end: 0; + display: flex; + align-items: center; + height: var(--rdp-nav-height); + width: 100%; } - .DayPicker-WeekNumber { - display: table-cell; - padding: 0.5rem; - text-align: right; - vertical-align: middle; - min-width: 1rem; - font-size: 0.75em; - cursor: pointer; - color: #8b9898; + .rdp-weekday { + opacity: var(--rdp-weekday-opacity); + padding: var(--rdp-weekday-padding); + font-weight: 500; + font-size: smaller; + text-align: var(--rdp-weekday-text-align); + text-transform: var(--rdp-weekday-text-transform); } - .DayPicker--interactionDisabled .DayPicker-Day { - cursor: default; + .rdp-week_number { + opacity: var(--rdp-week_number-opacity); + font-weight: 400; + font-size: small; + height: var(--rdp-week_number-height); + width: var(--rdp-week_number-width); + border: var(--rdp-week_number-border); + border-radius: var(--rdp-week_number-border-radius); + text-align: var(--rdp-weeknumber-text-align); } - .DayPicker-Footer { - display: table-caption; - caption-side: bottom; - padding-top: 0.5rem; + /* DAY MODIFIERS */ + .rdp-today:not(.rdp-outside) { + color: var(--rdp-today-color); } - .DayPicker-TodayButton { - border: none; - background-image: none; - background-color: transparent; - box-shadow: none; - cursor: pointer; - color: #4a90e2; - font-size: 0.875em; + .rdp-selected { + font-weight: bold; + font-size: large; } - /* Default modifiers */ + .rdp-selected .rdp-day_button { + border: var(--rdp-selected-border); + } - .DayPicker-Day--today { - color: #d0021b; - font-weight: 500; + .rdp-outside { + opacity: var(--rdp-outside-opacity); } - .DayPicker-Day--disabled { - color: #dce0e0; - cursor: default; - background-color: #eff1f1; + .rdp-disabled { + opacity: var(--rdp-disabled-opacity); } - .DayPicker-Day--outside { - cursor: default; - color: #dce0e0; + .rdp-hidden { + visibility: hidden; + color: var(--rdp-range_start-color); } - /* Example modifiers */ + .rdp-range_start { + background: var(--rdp-range_start-background); + } - .DayPicker-Day--sunday { - background-color: #f7f8f8; + .rdp-range_start .rdp-day_button { + background-color: var(--rdp-range_start-date-background-color); + color: var(--rdp-range_start-color); } - .DayPicker-Day--sunday:not(.DayPicker-Day--today) { - color: #dce0e0; + .rdp-range_middle { + background-color: var(--rdp-range_middle-background-color); } - .DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) { - color: #fff; - background-color: #4a90e2; + .rdp-range_middle .rdp-day_button { + border-color: transparent; + border: unset; + border-radius: unset; + color: var(--rdp-range_middle-color); } - /* DayPickerInput */ + .rdp-range_end { + background: var(--rdp-range_end-background); + color: var(--rdp-range_end-color); + } - .DayPickerInput { - display: inline-block; + .rdp-range_end .rdp-day_button { + color: var(--rdp-range_start-color); + background-color: var(--rdp-range_end-date-background-color); } - .DayPickerInput-OverlayWrapper { - position: relative; + .rdp-range_start.rdp-range_end { + background: revert; } - .DayPickerInput-Overlay { - left: 0; - position: absolute; - background: white; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); + .rdp-focusable { + cursor: pointer; } `; const StyledDayPicker = styled.div` - ${addReactDayPickerStyles} - - position: absolute; - height: 346px; - width: 352px; - ${({ theme }) => css` - z-index: ${theme.zIndex.popover}; - ${!theme.focusRedesignOptOut && - ` - margin-top: var(--spacing050); - `} - `} + ${officialReactDayPickerStyling} - .DayPicker { + .rdp-root { z-index: 1000; top: calc(100% + 1px); left: 0; @@ -209,46 +322,40 @@ const StyledDayPicker = styled.div` border-radius: var(--borderRadius050); } - .DayPicker * { + .rdp-root * { box-sizing: border-box; } - .DayPicker:focus { + .rdp-root:focus { outline: none; } - .DayPicker abbr[title] { + .rdp-root abbr[title] { border: none; cursor: initial; } - .DayPicker-wrapper { + .rdp-months { padding: 0; - &:focus { - ${({ theme }) => - !theme.focusRedesignOptOut - ? addFocusStyling() - : /* istanbul ignore next */ oldFocusStyling} - border-radius: var(--borderRadius050); - } } - .DayPicker-Month { + .rdp-month { margin: 0 0 2px; } - .DayPicker-Body, - .DayPicker-Week { + .rdp-month_grid, + .rdp_weeks { width: 100%; + margin-top: 8px; } - .DayPicker-Caption { + .rdp-month_caption { color: var(--colorsActionMajorYin090); line-height: var(--sizing500); height: var(--sizing500); - //font: var(--typographyDatePickerCalendarMonthM); font assets to be updated part of FE-4975 font-size: 16px; font-weight: 800; + display: block; > div { margin: 0 auto; @@ -256,15 +363,24 @@ const StyledDayPicker = styled.div` } } - .DayPicker-Day { + .rdp-weekday { + border: medium; + width: var(--sizing500); + height: var(--sizing450); + font-weight: 800; + color: var(--colorsActionMinor500); + text-transform: uppercase; + font-size: 12px; + text-align: center; + } + + .rdp-day { min-width: var(--sizing500); height: var(--sizing450); padding: 0; - background-color: var(--colorsUtilityYang100); + background-color: transparent; cursor: pointer; border: none; - //font-family: var(--fontFamiliesDefault); font assets to be updated part of FE-4975 - //font: var(--typographyDatePickerCalendarDateM); font assets to be updated part of FE-4975 font-weight: var(--fontWeights500); font-size: var(--fontSizes100); line-height: var(--lineHeights500); @@ -275,17 +391,6 @@ const StyledDayPicker = styled.div` color: var(--colorsActionMajorYin090); } - ${({ theme }) => - ` - &:focus { - ${ - !theme.focusRedesignOptOut - ? addFocusStyling(true) - : /* istanbul ignore next */ oldFocusStyling - } - } - `} - + * { border-left: 1px; } @@ -295,42 +400,76 @@ const StyledDayPicker = styled.div` } } - .DayPicker-Day--today, - .DayPicker-Day--today.DayPicker-Day--outside { + .rdp-today, + .rdp-today.rdp-outside { color: var(--colorsActionMajorYin090); background-color: var(--colorsActionMinor200); } - .DayPicker-Day--outside { + .rdp-outside { color: var(--colorsActionMajorYin055); - background-color: var(--colorsUtilityYang100); + background-color: transparent; + } + + .rdp-today:not(.rdp-outside) { + font-weight: var(--fontWeights500); + border-radius: var(--borderRadius400); + color: inherit; } - .DayPicker-Day--disabled, - .DayPicker-Day--disabled:hover { + .rdp-disabled, + .rdp-disabled:hover { color: var(--colorsActionMajorYin030); background-color: var(--colorsUtilityYang100); cursor: default; - &.DayPicker-Day--today { + &.rdp-today { background-color: var(--colorsActionMinor200); } } - .DayPicker-Day--selected:not(.DayPicker-Day--disabled):not( - .DayPicker-Day--outside - ) { + .rdp-selected:not(.rdp-disabled):not(.rdp-outside) { background-color: var(--colorsActionMajor500); color: var(--colorsUtilityYang100); border-radius: var(--borderRadius400); } - .DayPicker-Day--selected.DayPicker-Day--disabled:not( - .DayPicker-Day--outside - ) { + .rdp-selected.rdp-disabled:not(.rdp-outside) { background-color: var(--colorsActionMajor500); color: var(--colorsUtilityYang100); } + + .rdp-selected { + &:focus-visible { + outline: none; + } + } + + .rdp-selected .rdp-day_button { + border: none; + &:focus-visible { + outline: none; + } + } + + .rdp-focused:not(.rdp-disabled):not(.rdp-outside) { + ${({ theme }) => css` + ${!theme.focusRedesignOptOut + ? addFocusStyling(true) + : /* istanbul ignore next */ oldFocusStyling} + `} + border-radius: var(--borderRadius400); + } + + .rdp-day.rdp-selected { + ${({ theme }) => css` + &:focus { + ${!theme.focusRedesignOptOut + ? addFocusStyling(true) + : /* istanbul ignore next */ oldFocusStyling} + } + `} + } `; StyledDayPicker.defaultProps = { diff --git a/src/components/date/__internal__/navbar/navbar.component.tsx b/src/components/date/__internal__/navbar/navbar.component.tsx index 7d1b1c98db..9a7561a928 100644 --- a/src/components/date/__internal__/navbar/navbar.component.tsx +++ b/src/components/date/__internal__/navbar/navbar.component.tsx @@ -1,4 +1,6 @@ import React from "react"; +import { NavProps } from "react-day-picker"; + import StyledButton from "./button.style"; import StyledNavbar from "./navbar.style"; import Icon from "../../../icon"; @@ -15,7 +17,7 @@ export const Navbar = ({ onPreviousClick, onNextClick, className, -}: NavbarProps) => { +}: NavProps) => { const locale = useLocale(); const { previousMonthButton, nextMonthButton } = locale.date.ariaLabels; @@ -35,14 +37,16 @@ export const Navbar = ({ onPreviousClick?.()} + onClick={(e) => { + onPreviousClick?.(e); + }} onKeyDown={handleKeyDown} > onNextClick?.()} + onClick={(e) => onNextClick?.(e)} onKeyDown={handleKeyDown} > diff --git a/src/components/date/__internal__/navbar/navbar.style.ts b/src/components/date/__internal__/navbar/navbar.style.ts index fe756c029e..e6bed9d55c 100644 --- a/src/components/date/__internal__/navbar/navbar.style.ts +++ b/src/components/date/__internal__/navbar/navbar.style.ts @@ -1,7 +1,7 @@ import styled from "styled-components"; const StyledNavbar = styled.div` - &.DayPicker-NavBar { + &.rdp-nav { display: flex; justify-content: space-between; padding: 0; diff --git a/src/components/date/__internal__/utils.test.ts b/src/components/date/__internal__/utils.test.ts index 468f4c88c4..20e8a12ee3 100644 --- a/src/components/date/__internal__/utils.test.ts +++ b/src/components/date/__internal__/utils.test.ts @@ -1,4 +1,7 @@ import MockDate from "mockdate"; + +import { de, enGB, enUS, hu } from "date-fns/locale"; + import { isDateValid, parseDate, @@ -9,6 +12,7 @@ import { parseISODate, getDisabledDays, checkISOFormatAndLength, + isValidLocaleDate, } from "./utils"; const formats = [ @@ -446,3 +450,61 @@ test.each(["foo", "2022-1-1", "2022-01-1", "22-01-01", " "])( expect(checkISOFormatAndLength(value)).toEqual(false); }, ); + +describe("isValidLocaleDate", () => { + describe("with UK date formats", () => { + test("should return true when valid UK date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("30/04/2022", enGB)).toEqual(true); + }); + + test("should return false when invalid UK date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("31/04/2022", enGB)).toEqual(false); // April 31st is invalid + }); + + test("should return false when non-date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("invalid-date", enGB)).toEqual(false); + }); + }); + + describe("with US date formats", () => { + test("should return true when valid US date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("04/30/2022", enUS)).toEqual(true); + }); + + test("should return false when invalid US date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("04/31/2022", enUS)).toEqual(false); // April 31st is invalid + }); + + test("should return false when non-date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("invalid-date", enUS)).toEqual(false); + }); + }); + + describe("with German date formats", () => { + test("should return true when valid German date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("30.04.2022", de)).toEqual(true); + }); + + test("should return false when invalid German date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("31.04.2022", de)).toEqual(false); // April 31st is invalid + }); + + test("should return false when non-date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("invalid-date", de)).toEqual(false); + }); + }); + + describe("with Hungarian date formats", () => { + test("should return true when valid Hungarian date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("2022. 04. 30.", hu)).toEqual(true); + }); + + test("should return false when invalid Hungarian date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("2022. 04. 31.", hu)).toEqual(false); // April 31st is invalid + }); + + test("should return false when non-date string passed to `isValidLocaleDate`", () => { + expect(isValidLocaleDate("invalid-date", hu)).toEqual(false); + }); + }); +}); diff --git a/src/components/date/__internal__/utils.ts b/src/components/date/__internal__/utils.ts index e7afeb121d..6ee019027b 100644 --- a/src/components/date/__internal__/utils.ts +++ b/src/components/date/__internal__/utils.ts @@ -1,9 +1,27 @@ -import { Modifier } from "react-day-picker"; -import { format, formatISO, isMatch, parse, parseISO } from "./date-fns-fp"; +import { Matcher } from "react-day-picker"; + +import { + format, + formatISO, + isMatch, + isValid, + parse, + parseISO, + parseWithOptions, +} from "./date-fns-fp"; const DATE_STRING_LENGTH = 10; const THRESHOLD_FOR_ADDITIONAL_YEARS = 69; +export function isValidLocaleDate(date: string, locale: Locale) { + const dateFormat = "P"; + const parseDateWithLocale = parseWithOptions({ locale }); + const parsedDate = parseDateWithLocale(new Date(), dateFormat, date); + const isValidDate = isValid(parsedDate); + + return isValidDate; +} + export function parseDate(formatString?: string, valueString?: string) { if (!valueString || !formatString) return undefined; @@ -220,7 +238,7 @@ export function checkISOFormatAndLength(value: string) { export function getDisabledDays( minDate = "", maxDate = "", -): Modifier | Modifier[] { +): NonNullable | NonNullable | undefined { const days = []; if (!minDate && !maxDate) { diff --git a/src/components/date/__internal__/weekday/weekday.style.ts b/src/components/date/__internal__/weekday/weekday.style.ts index c65857ab2b..a3ccde62ee 100644 --- a/src/components/date/__internal__/weekday/weekday.style.ts +++ b/src/components/date/__internal__/weekday/weekday.style.ts @@ -1,8 +1,10 @@ import styled from "styled-components"; -const StyledWeekday = styled.div` +import StyledAbbr from "./abbr.style"; + +const StyledWeekday = styled.th` &, - &.DayPicker-Weekday { + & ${StyledAbbr} { border: none; height: var(--sizing500); min-width: var(--sizing500); diff --git a/src/components/date/__internal__/weekday/weekday.test.tsx b/src/components/date/__internal__/weekday/weekday.test.tsx index c6a546a294..e31d9bae13 100644 --- a/src/components/date/__internal__/weekday/weekday.test.tsx +++ b/src/components/date/__internal__/weekday/weekday.test.tsx @@ -3,8 +3,22 @@ import { screen, render } from "@testing-library/react"; import Weekday from "./weekday.component"; +const Component = (props: { + children: React.ReactNode; + className?: string; + title?: string; +}) => ( + + + + Foo + + +
+); + test("should render the passed `title` as the attribute on the `abbr` element", () => { - render(Foo); + render(Foo); const abbr = screen.getByTitle("title"); expect(abbr).toBeInTheDocument(); @@ -12,7 +26,7 @@ test("should render the passed `title` as the attribute on the `abbr` element", }); test("should render the passed `className` on the `div` element", () => { - render(Foo); + render(Foo); const weekday = screen.getByRole("columnheader", { name: "Foo" }); expect(weekday).toHaveClass("custom-class"); diff --git a/src/components/date/date.component.tsx b/src/components/date/date.component.tsx index 7205cfaaa3..85d8755363 100644 --- a/src/components/date/date.component.tsx +++ b/src/components/date/date.component.tsx @@ -17,6 +17,7 @@ import { parseISODate, checkISOFormatAndLength, getSeparator, + isValidLocaleDate, } from "./__internal__/utils"; import useLocale from "../../hooks/__internal__/useLocale"; import Events from "../../__internal__/utils/helpers/events"; @@ -125,7 +126,7 @@ export const DateInput = React.forwardRef( onClick, onFocus, onKeyDown, - pickerProps = {}, + pickerProps, readOnly, size = "medium", tooltipPosition, @@ -155,11 +156,16 @@ export const DateInput = React.forwardRef( ); const { inputRefMap, setInputRefMap } = useContext(DateRangeContext); const [open, setOpen] = useState(false); - const [selectedDays, setSelectedDays] = useState( - checkISOFormatAndLength(value) + const [selectedDays, setSelectedDays] = useState(() => { + const isValidDate = isValidLocaleDate(value, dateFnsLocale()); + if (!isValidDate) { + return undefined; + } + + return checkISOFormatAndLength(value) ? parseISODate(value) - : parseDate(format, value), - ); + : parseDate(format, value); + }); const isInitialValue = useRef(true); const pickerTabGuardId = useRef(guid()); @@ -220,7 +226,7 @@ export const DateInput = React.forwardRef( return function cleanup() { document.removeEventListener("mousedown", handleClick); }; - }, [open]); + }, [open, onPickerClose]); const handleChange = (ev: React.ChangeEvent) => { isInitialValue.current = false; diff --git a/src/components/date/date.mdx b/src/components/date/date.mdx index cb36dd38b8..3c7952c1b7 100644 --- a/src/components/date/date.mdx +++ b/src/components/date/date.mdx @@ -64,6 +64,12 @@ be used to set the input value like in the example below, although the component +### With disabled dates + +You can configure the available dates by passing a `minDate` and `maxDate` prop to the component. + + + ### With labelInline **Note:** The `labelInline` prop is not supported if the `validationRedesignOptIn` flag on the `CarbonProvider` is true. diff --git a/src/components/date/date.pw.tsx b/src/components/date/date.pw.tsx index df29c409af..cb329fdda5 100644 --- a/src/components/date/date.pw.tsx +++ b/src/components/date/date.pw.tsx @@ -1,6 +1,7 @@ import React from "react"; import { expect, test } from "@playwright/experimental-ct-react17"; import dayjs from "dayjs"; +import advancedFormat from "dayjs/plugin/advancedFormat"; import { DateInputCustom, DateInputValidationNewDesign, @@ -33,9 +34,11 @@ import { import { HooksConfig } from "../../../playwright"; import { alertDialogPreview } from "../../../playwright/components/dialog"; +dayjs.extend(advancedFormat); + const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS]; -const DAY_PICKER_PREFIX = "DayPicker-Day--"; -const TODAY = dayjs().format("ddd D MMM YYYY"); +const DAY_PICKER_PREFIX = "rdp-"; +const TODAY = dayjs().format("dddd, MMMM Do, YYYY"); const DATE_INPUT = dayjs("2022-05-01").format("DD/MM/YYYY"); const TODAY_DATE_INPUT = dayjs().format("DD/MM/YYYY"); const NEXT_MONTH = dayjs("2022-05-01").add(1, "months").format("MMMM YYYY"); @@ -44,8 +47,8 @@ const PREVIOUS_MONTH = dayjs("2022-05-01") .subtract(1, "months") .format("MMMM YYYY"); const MIN_DATE = "04/04/2030"; -const DAY_BEFORE_MIN_DATE = "Wed 3 Apr 2030"; -const DAY_AFTER_MAX_DATE = "Fri 5 Apr 2030"; +const DAY_BEFORE_MIN_DATE = "Wednesday, April 3rd, 2030"; +const DAY_AFTER_MAX_DATE = "Friday, April 5th, 2030"; const DDMMYYY_DATE_TO_ENTER = "27,05,2022"; const MMDDYYYY_DATE_TO_ENTER = "05,27,2022"; const YYYYMMDD_DATE_TO_ENTER = "2022,05,27"; @@ -104,11 +107,10 @@ test.describe("Functionality tests", () => { const input = getDataElementByValue(page, "input"); await input.fill(MIN_DATE); - const dayPicker = page - .getByRole("row") - .locator(`div[aria-label="${DAY_BEFORE_MIN_DATE}"]`); - await expect(dayPicker).toHaveAttribute("aria-disabled", "true"); - await expect(dayPicker).toHaveAttribute("aria-selected", "false"); + const dayPicker = page.locator( + `button[aria-label="${DAY_BEFORE_MIN_DATE}"]`, + ); + await expect(dayPicker).toHaveAttribute("disabled", ""); }); test(`should check the maxDate prop`, async ({ mount, page }) => { @@ -117,11 +119,10 @@ test.describe("Functionality tests", () => { const input = getDataElementByValue(page, "input"); await input.fill(MIN_DATE); - const dayPicker = page - .getByRole("row") - .locator(`div[aria-label="${DAY_AFTER_MAX_DATE}"]`); - await expect(dayPicker).toHaveAttribute("aria-disabled", "true"); - await expect(dayPicker).toHaveAttribute("aria-selected", "false"); + const dayPicker = page.locator( + `button[aria-label="${DAY_AFTER_MAX_DATE}"]`, + ); + await expect(dayPicker).toHaveAttribute("disabled", ""); }); test(`should check the date is set to today's day`, async ({ @@ -130,15 +131,17 @@ test.describe("Functionality tests", () => { }) => { await mount(); - const dayClass = `DayPicker-Day ${DAY_PICKER_PREFIX}selected ${DAY_PICKER_PREFIX}today`; + const dayClass = `rdp-day rdp-today`; const input = getDataElementByValue(page, "input"); await input.fill(TODAY_DATE_INPUT); - const dayPicker = page - .getByRole("row") - .locator(`div[aria-label="${TODAY}"]`); - await expect(dayPicker).toHaveAttribute("aria-label", TODAY); - await expect(dayPicker).toHaveClass(dayClass); + const todayButton = page.getByRole("button", { name: `Today, ${TODAY}` }); + const todayCell = page.getByRole("gridcell").filter({ + has: todayButton, + }); + + await expect(todayButton).toBeVisible(); + await expect(todayCell).toHaveClass(dayClass); }); test(`should open dayPicker after click on input`, async ({ @@ -202,7 +205,7 @@ test.describe("Functionality tests", () => { const inputParent = getDataElementByValue(page, "input").locator(".."); await inputParent.click(); - const wrapperParent = dayPickerWrapper(page).locator("..").locator(".."); + const wrapperParent = dayPickerWrapper(page).locator(".."); await expect(wrapperParent).toHaveAttribute( "data-floating-placement", `${position}-start`, @@ -293,7 +296,7 @@ test.describe("Functionality tests", () => { ); await expect(arrowRight).toBeFocused(); await page.keyboard.press("Tab"); - const dayPicker = page.locator(`.${DAY_PICKER_PREFIX}selected`); + const dayPicker = page.locator(`.rdp-selected`).locator("button"); await expect(dayPicker).toBeFocused(); }); @@ -316,7 +319,9 @@ test.describe("Functionality tests", () => { ); await expect(arrowRight).toBeFocused(); await page.keyboard.press("Tab"); - const dayPicker = page.locator(`.${DAY_PICKER_PREFIX}selected`); + const dayPicker = page + .locator(`.${DAY_PICKER_PREFIX}selected`) + .locator("button"); await expect(dayPicker).toBeFocused(); await page.keyboard.press("Tab"); const wrapper = dayPickerWrapper(page); @@ -336,7 +341,9 @@ test.describe("Functionality tests", () => { await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); - const dayPicker = page.locator(`.${DAY_PICKER_PREFIX}today`); + const dayPicker = page + .locator(`.${DAY_PICKER_PREFIX}today`) + .locator("button"); await expect(dayPicker).toBeFocused(); await page.keyboard.press("Tab"); const wrapper = dayPickerWrapper(page); @@ -347,7 +354,7 @@ test.describe("Functionality tests", () => { mount, page, }) => { - await mount(); + await mount(); await page.focus("body"); await page.keyboard.press("Tab"); @@ -356,62 +363,54 @@ test.describe("Functionality tests", () => { await page.keyboard.press("Tab"); await page.keyboard.press(arrowKeys[3]); const focusedElement1 = page - .getByRole("row") - .nth(2) - .locator("div") - .filter({ hasText: "8" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "21" }); await expect(focusedElement1).toBeFocused(); await page.keyboard.press(arrowKeys[3]); const focusedElement2 = page - .getByRole("row") - .nth(3) - .locator("div") - .filter({ hasText: "15" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "28" }); await expect(focusedElement2).toBeFocused(); await page.keyboard.press(arrowKeys[1]); const focusedElement3 = page - .getByRole("row") - .nth(3) - .locator("div") - .filter({ hasText: "14" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "27" }); await expect(focusedElement3).toBeFocused(); await page.keyboard.press(arrowKeys[1]); const focusedElement4 = page - .getByRole("row") - .nth(3) - .locator("div") - .filter({ hasText: "13" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "26" }); await expect(focusedElement4).toBeFocused(); await page.keyboard.press(arrowKeys[0]); const focusedElement5 = page - .getByRole("row") - .nth(3) - .locator("div") - .filter({ hasText: "14" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "27" }); await expect(focusedElement5).toBeFocused(); await page.keyboard.press(arrowKeys[0]); const focusedElement6 = page - .getByRole("row") - .nth(3) - .locator("div") - .filter({ hasText: "15" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "28" }); await expect(focusedElement6).toBeFocused(); await page.keyboard.press(arrowKeys[2]); const focusedElement7 = page - .getByRole("row") - .nth(2) - .locator("div") - .filter({ hasText: "8" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "21" }); await expect(focusedElement7).toBeFocused(); await page.keyboard.press(arrowKeys[2]); const focusedElement8 = page - .getByRole("row") - .nth(1) - .locator("div") - .filter({ hasText: "1" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "14" }); await expect(focusedElement8).toBeFocused(); }); @@ -419,7 +418,7 @@ test.describe("Functionality tests", () => { mount, page, }) => { - await mount(); + await mount(); await page.focus("body"); await page.keyboard.press("Tab"); @@ -428,10 +427,9 @@ test.describe("Functionality tests", () => { await page.keyboard.press("Tab"); await page.keyboard.press(arrowKeys[1]); const focusedElement = page - .getByRole("row") - .nth(5) - .locator("div") - .filter({ hasText: "30" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "13" }); await expect(focusedElement).toBeFocused(); const pickerHeading = dayPickerHeading(page); await expect(pickerHeading).toHaveText(PREVIOUS_MONTH); @@ -460,16 +458,14 @@ test.describe("Functionality tests", () => { await page.keyboard.press(arrowKeys[2]); if (day === "1") { const focusedElement = page - .getByRole("row") - .nth(4) - .locator("div") + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") .filter({ hasText: result }); await expect(focusedElement).toBeFocused(); } else { const focusedElement = page - .getByRole("row") - .nth(5) - .locator("div") + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") .filter({ hasText: result }); await expect(focusedElement).toBeFocused(); } @@ -501,16 +497,14 @@ test.describe("Functionality tests", () => { await page.keyboard.press(arrowKeys[3]); if (day === "30" || day === "31") { const focusedElement = page - .getByRole("row") - .nth(2) - .locator("div") + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") .filter({ hasText: result }); await expect(focusedElement).toBeFocused(); } else { const focusedElement = page - .getByRole("row") - .nth(1) - .locator("div") + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") .filter({ hasText: result }) .filter({ hasNotText: "30" }) .filter({ hasNotText: "31" }); @@ -526,7 +520,7 @@ test.describe("Functionality tests", () => { mount, page, }) => { - await mount(); + await mount(); await page.focus("body"); await page.keyboard.press("Tab"); @@ -535,14 +529,13 @@ test.describe("Functionality tests", () => { await page.keyboard.press("Tab"); await page.keyboard.press(arrowKeys[1]); const focusedElement = page - .getByRole("row") - .nth(5) - .locator("div") - .filter({ hasText: "30" }); + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") + .filter({ hasText: "13" }); await expect(focusedElement).toBeFocused(); await page.keyboard.press(key); await expect(getDataElementByValue(page, "input")).toHaveValue( - "30/04/2022", + "13/04/2022", ); }); }); @@ -606,9 +599,8 @@ test.describe("Functionality tests", () => { await page.keyboard.press("Tab"); await page.keyboard.press(arrowKeys[0]); const focusedElement = page - .getByRole("row") - .nth(1) - .locator("div") + .locator(`.${DAY_PICKER_PREFIX}focused`) + .locator("button") .filter({ hasText: "1" }) .filter({ hasNotText: "31" }); await expect(focusedElement).toBeFocused(); @@ -752,17 +744,17 @@ test.describe("Functionality tests", () => { const input = getDataElementByValue(page, "input"); await input.click(); - const months = page.locator("div[class=DayPicker-Month]"); + const months = page.locator("div[class=rdp-month]"); await expect(months).toHaveCount(2); const pickerHeading1 = page - .locator(".DayPicker-Caption") - .locator("div") + .locator(".rdp-month_caption") + .locator("span") .nth(0); await expect(pickerHeading1).toBeVisible(); await expect(pickerHeading1).toHaveText(ACTUAL_MONTH); const pickerHeading2 = page - .locator(".DayPicker-Caption") - .locator("div") + .locator(".rdp-month_caption") + .locator("span") .nth(1); await expect(pickerHeading2).toBeVisible(); await expect(pickerHeading2).toHaveText(NEXT_MONTH); @@ -836,9 +828,11 @@ test.describe("Functionality tests", () => { const input = getDataElementByValue(page, "input"); await input.click(); await expect(input).toHaveCSS("border-radius", "4px"); - const dayPicker1 = page.getByLabel("Sun 1 May 2022"); + const dayPicker1 = page + .getByLabel("Sunday, May 1st, 2022, selected") + .locator(".."); await expect(dayPicker1).toHaveCSS("border-radius", "32px"); - const dayPicker2 = page.getByLabel("Mon 2 May 2022"); + const dayPicker2 = page.getByLabel("Monday, May 2nd, 2022").locator(".."); await expect(dayPicker2).toHaveCSS("border-radius", "32px"); const dayPickerNavButton1 = page.getByLabel("Previous month"); await expect(dayPickerNavButton1).toHaveCSS("border-radius", "4px"); @@ -858,18 +852,37 @@ test.describe("Functionality tests", () => { await page.keyboard.press("Tab"); const inputParent = getDataElementByValue(page, "input").locator(".."); await checkGoldenOutline(inputParent); - const dayPicker1 = page.getByLabel("Sun 1 May 2022"); - await dayPicker1.focus(); - await checkGoldenOutline(dayPicker1); - const dayPicker2 = page.getByLabel("Mon 2 May 2022"); - await dayPicker2.focus(); - await checkGoldenOutline(dayPicker2); + await page.keyboard.press("Tab"); const dayPickerNavButton1 = page.getByLabel("Previous month"); await dayPickerNavButton1.focus(); - await checkGoldenOutline(dayPickerNavButton1); + await expect(dayPickerNavButton1).toHaveCSS( + "outline", + "rgb(255, 188, 25) solid 3px", + ); + await page.keyboard.press("Tab"); const dayPickerNavButton2 = page.getByLabel("Next month"); await dayPickerNavButton2.focus(); - await checkGoldenOutline(dayPickerNavButton2); + await expect(dayPickerNavButton2).toHaveCSS( + "outline", + "rgb(255, 188, 25) solid 3px", + ); + await page.keyboard.press("Tab"); + const dayPicker1 = page + .getByLabel("Sunday, May 1st, 2022, selected") + .locator(".."); + await dayPicker1.focus(); + await expect(dayPicker1).toHaveCSS( + "outline", + "rgb(255, 188, 25) solid 3px", + ); + + await page.keyboard.press("ArrowRight"); + const dayPicker2 = page.getByLabel("Monday, May 2nd, 2022").locator(".."); + await dayPicker2.focus(); + await expect(dayPicker2).toHaveCSS( + "outline", + "rgb(255, 188, 25) solid 3px", + ); }); test(`should have the expected styling when opt out flag is false`, async ({ @@ -880,8 +893,6 @@ test.describe("Functionality tests", () => { await page.focus("body"); await page.keyboard.press("Tab"); - const wrapperParent = dayPickerWrapper(page).locator("..").locator(".."); - await expect(wrapperParent).toHaveCSS("margin-top", "4px"); const inputParent = getDataElementByValue(page, "input").locator(".."); await expect(inputParent).toHaveCSS( "box-shadow", @@ -891,20 +902,8 @@ test.describe("Functionality tests", () => { "outline", "rgba(0, 0, 0, 0) solid 3px", ); - const dayPicker1 = page.getByLabel("Sun 1 May 2022"); - await dayPicker1.focus(); - await expect(dayPicker1).toHaveCSS( - "box-shadow", - "rgba(0, 0, 0, 0.9) 0px 0px 0px 3px inset, rgb(255, 188, 25) 0px 0px 0px 6px inset", - ); - await expect(dayPicker1).toHaveCSS("outline", "rgba(0, 0, 0, 0) solid 3px"); - const dayPicker2 = page.getByLabel("Mon 2 May 2022"); - await dayPicker2.focus(); - await expect(dayPicker2).toHaveCSS( - "box-shadow", - "rgba(0, 0, 0, 0.9) 0px 0px 0px 3px inset, rgb(255, 188, 25) 0px 0px 0px 6px inset", - ); - await expect(dayPicker2).toHaveCSS("outline", "rgba(0, 0, 0, 0) solid 3px"); + + await page.keyboard.press("Tab"); const dayPickerNavButton1 = page.getByLabel("Previous month"); await dayPickerNavButton1.focus(); await expect(dayPickerNavButton1).toHaveCSS( @@ -915,6 +914,8 @@ test.describe("Functionality tests", () => { "outline", "rgba(0, 0, 0, 0) solid 3px", ); + + await page.keyboard.press("Tab"); const dayPickerNavButton2 = page.getByLabel("Next month"); await dayPickerNavButton2.focus(); await expect(dayPickerNavButton2).toHaveCSS( @@ -925,6 +926,24 @@ test.describe("Functionality tests", () => { "outline", "rgba(0, 0, 0, 0) solid 3px", ); + + await page.keyboard.press("Tab"); + const dayPicker1 = page.getByLabel("Sunday, May 1st, 2022").locator(".."); + await dayPicker1.focus(); + await expect(dayPicker1).toHaveCSS( + "box-shadow", + "rgba(0, 0, 0, 0.9) 0px 0px 0px 3px inset, rgb(255, 188, 25) 0px 0px 0px 6px inset", + ); + await expect(dayPicker1).toHaveCSS("outline", "rgba(0, 0, 0, 0) solid 3px"); + + await page.keyboard.press("ArrowRight"); + const dayPicker2 = page.getByLabel("Monday, May 2nd, 2022").locator(".."); + await dayPicker2.focus(); + await expect(dayPicker2).toHaveCSS( + "box-shadow", + "rgba(0, 0, 0, 0.9) 0px 0px 0px 3px inset, rgb(255, 188, 25) 0px 0px 0px 6px inset", + ); + await expect(dayPicker2).toHaveCSS("outline", "rgba(0, 0, 0, 0) solid 3px"); }); (["top", "bottom", "left", "right"] as const).forEach((position) => { diff --git a/src/components/date/date.stories.tsx b/src/components/date/date.stories.tsx index 9df69d32cc..e0e5bf6217 100644 --- a/src/components/date/date.stories.tsx +++ b/src/components/date/date.stories.tsx @@ -124,6 +124,26 @@ export const Empty: Story = () => { }; Empty.storyName = "Empty"; +export const DisabledDates: Story = () => { + const [state, setState] = useState("04/04/2019"); + const setValue = (ev: DateChangeEvent) => { + setState(ev.target.value.formattedValue); + }; + return ( + console.log("blur")} + /> + ); +}; +DisabledDates.storyName = "Disabled Dates"; +DisabledDates.parameters = { chromatic: { disableSnapshot: true } }; + export const WithLabelInline: Story = () => { const [state, setState] = useState("01/10/2016"); const setValue = (ev: DateChangeEvent) => { diff --git a/src/components/date/date.test.tsx b/src/components/date/date.test.tsx index 2ee4aa1db3..dbb9592072 100644 --- a/src/components/date/date.test.tsx +++ b/src/components/date/date.test.tsx @@ -68,7 +68,7 @@ const MockComponent = ({ ); }; -// temporatrily running timers on every spec as we have issues +// temporarily running timers on every spec as we have issues // around how slow the tests that open the calendar are. // FE-6724 raised to investigate and implement a better solution beforeAll(() => { @@ -479,23 +479,21 @@ test("should not close the picker or call the `onChange` and `onBlur` callbacks test("should not close the picker or call the `onChange` and `onBlur` callbacks when the user clicks on a disabled day", async () => { const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); const onChange = jest.fn(); - const onBlur = jest.fn(); render( , ); const input = screen.getByRole("textbox"); await user.click(input); jest.advanceTimersByTime(10); - await user.click(screen.getByRole("gridcell", { name: "Wed 3 Apr 2019" })); + await user.click(screen.getByLabelText("Wednesday, April 3rd, 2019")); expect(screen.queryByRole("grid")).toBeVisible(); expect(onChange).not.toHaveBeenCalled(); - expect(onBlur).not.toHaveBeenCalled(); }); test("should close the open picker when a user presses the 'Escape' key", async () => { @@ -609,7 +607,7 @@ test("should focus the next button and then the selected day element when the us expect(screen.getByRole("button", { name: "Next month" })).toHaveFocus(); await user.tab(); expect( - screen.getByRole("gridcell", { name: "Thu 4 Apr 2019" }), + screen.getByLabelText("Thursday, April 4th, 2019", { exact: false }), ).toHaveFocus(); await user.tab(); expect(screen.queryByRole("grid")).not.toBeInTheDocument(); @@ -621,10 +619,12 @@ test("should close the picker, update the value and refocus the input element wh const input = screen.getByRole("textbox"); await user.click(input); jest.advanceTimersByTime(10); - await user.click(screen.getByRole("gridcell", { name: "Thu 11 Apr 2019" })); + await user.click(screen.getByLabelText("Thursday, April 11th, 2019")); expect(screen.queryByRole("grid")).not.toBeInTheDocument(); - expect(input).toHaveFocus(); + await waitFor(() => { + expect(input).toHaveFocus(); + }); expect(input).toHaveValue("11/04/2019"); }); @@ -1668,6 +1668,6 @@ test("should select the correct date when the locale is overridden and a date is await user.type(input, "05/04"); jest.advanceTimersByTime(10); - const grid = screen.getByRole("grid").childNodes[0].textContent; + const grid = screen.getByRole("status").textContent; expect(grid).toEqual("April 2019"); }); diff --git a/src/components/tabs/__internal__/tab-title/tab-title.style.ts b/src/components/tabs/__internal__/tab-title/tab-title.style.ts index 6036afc074..0f6c73fc3b 100644 --- a/src/components/tabs/__internal__/tab-title/tab-title.style.ts +++ b/src/components/tabs/__internal__/tab-title/tab-title.style.ts @@ -2,9 +2,10 @@ import styled, { css } from "styled-components"; import StyledIcon from "../../../icon/icon.style"; import StyledValidationIcon from "../../../../__internal__/validations/validation-icon.style"; -import { TabTitleProps } from "."; import addFocusStyling from "../../../../style/utils/add-focus-styling"; +import { TabTitleProps } from "."; + interface StyledTitleContentProps extends Pick< TabTitleProps, @@ -151,7 +152,7 @@ const StyledTitleContent = styled.span` padding: 10px 16px; ${borders && `padding-bottom: 9px;`} - `} + `} ${(warning || info) && css` @@ -180,7 +181,7 @@ const StyledTitleContent = styled.span` border-right-color: transparent; padding-right: ${size === "large" ? "26px" : "18px"}; `} - + &:hover { outline: 1px solid; outline-offset: -1px; @@ -230,7 +231,7 @@ const StyledTitleContent = styled.span` border-right-color: transparent; padding-right: ${size === "large" ? "26px" : "18px"}; `} - + &:hover { outline: 2px solid var(--colorsSemanticNegative500); outline-offset: -2px; @@ -488,7 +489,7 @@ const tabTitleStyles = css< border-right: none; } `} - + background-color: var(--colorsActionMajorYang100); &:hover { @@ -546,7 +547,6 @@ const StyledLayoutWrapper = styled.div` hasCustomLayout, titlePosition = "before", hasCustomSibling, - position, validationRedesignOptIn, }) => css` ${hasCustomLayout &&