From c1832e96143733321875b85c409c0f23a8eb5afa Mon Sep 17 00:00:00 2001 From: DipperTheDan Date: Wed, 11 Oct 2023 11:24:07 +0100 Subject: [PATCH 01/24] docs(message): update designer notes to reflect new variants --- src/components/message/message.stories.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/message/message.stories.mdx b/src/components/message/message.stories.mdx index e52c74c046..1d508f4abb 100644 --- a/src/components/message/message.stories.mdx +++ b/src/components/message/message.stories.mdx @@ -35,9 +35,10 @@ Useful for messages which are longer or more important, where the user needs tim Various types are available: - **Error** - tells the user about a negative outcome that has already happened. Try to focus the message text on the action the user needs to take to be successful, rather than what went wrong. -- **Info** - gives context or advice to the user where there’s no risk of a negative outcome. +- **Info** - gives advice to the user where there’s no risk of a negative outcome. - **Success** - indicates that an activity was successful. A good example could also present the user with onward options, such as ‘View a list of items’ or ‘Create another’. - **Warning** - warns the user about a potential negative outcome that hasn’t happened yet. +- **Neutral** - gives context to the user where there’s no risk of a negative outcome. - The Transparent configuration is useful if you’d like the message to be more visually subtle, perhaps in a Dialog. ## Related Components From f09ef5ecf76ae1551a6e01dac1c45be56583df2b Mon Sep 17 00:00:00 2001 From: DipperTheDan Date: Wed, 11 Oct 2023 11:32:47 +0100 Subject: [PATCH 02/24] feat(message): remove role of status from component role of status has been removed this component as Message should not be a live region. If you require a screenreader to read out the Message component, please focus it by using a ref instead. fixes #6013 --- src/components/message/message.component.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/message/message.component.tsx b/src/components/message/message.component.tsx index 71b3b2a172..456529a8a6 100644 --- a/src/components/message/message.component.tsx +++ b/src/components/message/message.component.tsx @@ -87,7 +87,6 @@ export const Message = React.forwardRef( className={className} transparent={transparent} variant={variant} - role="status" id={id} ref={refToPass} {...marginProps} From b5eb150bcc5760f548db60610f70c5ccd6a7db18 Mon Sep 17 00:00:00 2001 From: edleeks87 Date: Thu, 19 Oct 2023 14:41:32 +0100 Subject: [PATCH 03/24] fix(date): add aria labels to picker navigation buttons and fix other accessibility issues Adds aria-label support to `next` and `previous` month navigation buttons in `Date` picker component. Removes invalid role and aria-live attributes from caption element to fix some axe warnings. fix #5804 --- cypress/components/date/date.cy.tsx | 20 ++ .../date-range/date-range.stories.mdx | 14 + .../date-range/date-range.stories.tsx | 8 +- .../date-picker/date-picker.component.tsx | 241 ++++++++++-------- .../date-picker/date-picker.spec.tsx | 8 +- .../__internal__/navbar/navbar.component.tsx | 32 ++- .../date/__internal__/navbar/navbar.spec.tsx | 24 +- src/components/date/date.spec.tsx | 20 +- src/components/date/date.stories.mdx | 14 + src/components/date/date.stories.tsx | 16 +- src/locales/en-gb.ts | 4 + src/locales/locale.ts | 4 + src/locales/pl-pl.ts | 4 + 13 files changed, 274 insertions(+), 135 deletions(-) diff --git a/cypress/components/date/date.cy.tsx b/cypress/components/date/date.cy.tsx index 02ffe3615d..04defa7217 100644 --- a/cypress/components/date/date.cy.tsx +++ b/cypress/components/date/date.cy.tsx @@ -411,6 +411,10 @@ context("Test for DateInput component", () => { locale: () => localeValue, date: { dateFnsLocale: () => dateFnsLocaleValue, + ariaLabels: { + previousMonthButton: () => "Previous month", + nextMonthButton: () => "Next month", + }, }, } ); @@ -478,6 +482,10 @@ context("Test for DateInput component", () => { locale: () => localeValue, date: { dateFnsLocale: () => dateFnsLocaleValue, + ariaLabels: { + previousMonthButton: () => "Previous month", + nextMonthButton: () => "Next month", + }, }, } ); @@ -544,6 +552,10 @@ context("Test for DateInput component", () => { locale: () => localeValue, date: { dateFnsLocale: () => dateFnsLocaleValue, + ariaLabels: { + previousMonthButton: () => "Previous month", + nextMonthButton: () => "Next month", + }, }, } ); @@ -864,4 +876,12 @@ context("Test for DateInput component", () => { cy.checkAccessibility(); }); + + it("should check accessibility when the picker is open", () => { + CypressMountWithProviders(); + + dateInputParent() + .click() + .then(() => cy.checkAccessibility()); + }); }); diff --git a/src/components/date-range/date-range.stories.mdx b/src/components/date-range/date-range.stories.mdx index fed786508c..ca1029bfcd 100644 --- a/src/components/date-range/date-range.stories.mdx +++ b/src/components/date-range/date-range.stories.mdx @@ -141,5 +141,19 @@ The following keys are available to override the translations for this component type: "func", returnType: "date-fns locale object", }, + { + name: "date.ariaLabels.previousMonthButton", + description: + "Aria label text for the previous month navigation button in the date picker", + type: "func", + returnType: "string", + }, + { + name: "date.ariaLabels.nextMonthButton", + description: + "Aria label text for the next month navigation button in the date picker", + type: "func", + returnType: "string", + } ]} /> diff --git a/src/components/date-range/date-range.stories.tsx b/src/components/date-range/date-range.stories.tsx index 73aeadce11..a8af9bd2bb 100644 --- a/src/components/date-range/date-range.stories.tsx +++ b/src/components/date-range/date-range.stories.tsx @@ -285,7 +285,13 @@ export const LocaleOverrideExampleImplementation: ComponentStory< "fr-FR", - date: { dateFnsLocale: () => fr }, + date: { + dateFnsLocale: () => fr, + ariaLabels: { + previousMonthButton: () => "Mois précédent", + nextMonthButton: () => "Mois prochain", + }, + }, }} > ( - ( - { - inputElement, - minDate, - maxDate, - selectedDays, - disablePortal, - onDayClick, - pickerMouseDown, - pickerProps, - open, - }: DatePickerProps, - ref - ) => { - const l = useLocale(); - const { localize, options } = l.date.dateFnsLocale(); - const { weekStartsOn } = options || /* istanbul ignore next */ {}; - const monthsLong = useMemo( - () => - Array.from({ length: 12 }).map((_, i) => { - const month = localize?.month(i); - return month[0].toUpperCase() + month.slice(1); - }), - [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] +export const DatePicker = ({ + inputElement, + minDate, + maxDate, + selectedDays, + disablePortal, + onDayClick, + pickerMouseDown, + pickerProps, + open, +}: DatePickerProps) => { + const l = useLocale(); + const { localize, options } = l.date.dateFnsLocale(); + const { weekStartsOn } = options || /* istanbul ignore next */ {}; + const monthsLong = useMemo( + () => + Array.from({ length: 12 }).map((_, i) => { + const month = localize?.month(i); + return month[0].toUpperCase() + month.slice(1); + }), + [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] + ); + const weekdaysShort = useMemo(() => { + const isGivenLocale = (str: string) => l.locale().includes(str); + return Array.from({ length: 7 }).map((_, i) => + localize + ?.day( + i, + ["de", "pl"].some(isGivenLocale) + ? { width: "wide" } + : { width: "abbreviated" } + ) + .substring(0, isGivenLocale("de") ? 2 : 3) ); - const weekdaysShort = useMemo(() => { - const isGivenLocale = (str: string) => l.locale().includes(str); - return Array.from({ length: 7 }).map((_, i) => - localize - ?.day( - i, - ["de", "pl"].some(isGivenLocale) - ? { width: "wide" } - : { width: "abbreviated" } - ) - .substring(0, isGivenLocale("de") ? 2 : 3) - ); - }, [l, localize]); - - const handleDayClick = ( - date: Date, - modifiers: DayModifiers, - ev: React.MouseEvent - ) => { - if (!modifiers.disabled) { - const { id, name } = inputElement?.current - ?.firstChild as HTMLInputElement; - ev.target = { - ...ev.target, - id, - name, - } as HTMLInputElement; - onDayClick?.(date, ev); + }, [l, localize]); + + const handleDayClick = ( + date: Date, + modifiers: DayModifiers, + ev: React.MouseEvent + ) => { + if (!modifiers.disabled) { + const { id, name } = inputElement?.current + ?.firstChild as HTMLInputElement; + ev.target = { + ...ev.target, + id, + name, + } as HTMLInputElement; + onDayClick?.(date, ev); + } + }; + + const formatDay = (date: Date) => + `${weekdaysShort[date.getDay()]} ${date.getDate()} ${ + monthsShort[date.getMonth()] + } ${date.getFullYear()}`; + + 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"); } - }; - const formatDay = (date: Date) => - `${weekdaysShort[date.getDay()]} ${date.getDate()} ${ - monthsShort[date.getMonth()] - } ${date.getFullYear()}`; + // 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']" + ); - if (!open) { - return null; + /* istanbul ignore else */ + if (selectedDay && firstDay !== selectedDay) { + selectedDay?.setAttribute("tabindex", "0"); + firstDay?.setAttribute("tabindex", "-1"); + } } + }, [open]); - const localeUtils = { formatDay } as LocaleUtils; - - return ( - - - { - const { className, weekday } = weekdayElementProps; - - return ( - - {weekdaysShort[weekday]} - - ); - }} - navbarElement={} - fixedWeeks - initialMonth={selectedDays || undefined} - disabledDays={getDisabledDays(minDate, maxDate)} - locale={l.locale()} - localeUtils={localeUtils} - {...pickerProps} - /> - - - ); + if (!open) { + return null; } -); + + const localeUtils = { formatDay } as LocaleUtils; + + return ( + + + { + const { className, weekday } = weekdayElementProps; + + return ( + + {weekdaysShort[weekday]} + + ); + }} + navbarElement={} + fixedWeeks + initialMonth={selectedDays || undefined} + disabledDays={getDisabledDays(minDate, maxDate)} + locale={l.locale()} + localeUtils={localeUtils} + {...pickerProps} + /> + + + ); +}; DatePicker.displayName = "DatePicker"; diff --git a/src/components/date/__internal__/date-picker/date-picker.spec.tsx b/src/components/date/__internal__/date-picker/date-picker.spec.tsx index 70e2e5357d..91f758fa14 100644 --- a/src/components/date/__internal__/date-picker/date-picker.spec.tsx +++ b/src/components/date/__internal__/date-picker/date-picker.spec.tsx @@ -225,7 +225,13 @@ describe("StyledDayPicker", () => { const buildLocale = (l: keyof typeof translations) => ({ locale: () => l, - date: { dateFnsLocale: () => translations[l] }, + date: { + dateFnsLocale: () => translations[l], + ariaLabels: { + previousMonthButton: () => "foo", + nextMonthButton: () => "foo", + }, + }, }); type WeekdaysType = { long?: string[]; short?: string[] }; diff --git a/src/components/date/__internal__/navbar/navbar.component.tsx b/src/components/date/__internal__/navbar/navbar.component.tsx index 9cd5cd1ec9..4a4222f5a2 100644 --- a/src/components/date/__internal__/navbar/navbar.component.tsx +++ b/src/components/date/__internal__/navbar/navbar.component.tsx @@ -2,6 +2,7 @@ import React from "react"; import StyledButton from "./button.style"; import StyledNavbar from "./navbar.style"; import Icon from "../../../icon"; +import useLocale from "../../../../hooks/__internal__/useLocale"; export interface NavbarProps { onPreviousClick?: () => void; @@ -13,15 +14,26 @@ export const Navbar = ({ onPreviousClick, onNextClick, className, -}: NavbarProps) => ( - - onPreviousClick?.()}> - - - onNextClick?.()}> - - - -); +}: NavbarProps) => { + const locale = useLocale(); + const { previousMonthButton, nextMonthButton } = locale.date.ariaLabels; + + return ( + + onPreviousClick?.()} + > + + + onNextClick?.()} + > + + + + ); +}; export default Navbar; diff --git a/src/components/date/__internal__/navbar/navbar.spec.tsx b/src/components/date/__internal__/navbar/navbar.spec.tsx index 346d97934a..6c8b2147f2 100644 --- a/src/components/date/__internal__/navbar/navbar.spec.tsx +++ b/src/components/date/__internal__/navbar/navbar.spec.tsx @@ -1,12 +1,12 @@ import React from "react"; import TestRenderer from "react-test-renderer"; -import { shallow, ShallowWrapper } from "enzyme"; +import { mount, ReactWrapper, shallow, ShallowWrapper } from "enzyme"; import Navbar, { NavbarProps } from "./navbar.component"; import StyledButton from "./button.style"; describe("Navbar", () => { - let wrapper: ShallowWrapper; + let wrapper: ShallowWrapper | ReactWrapper; let onPreviousClick: jest.Mock; let onNextClick: jest.Mock; @@ -30,14 +30,30 @@ describe("Navbar", () => { }); it("returns a next button that calls onNextClick", () => { - const prevButton = wrapper.find(StyledButton).at(1); - prevButton.simulate("click"); + const nextButton = wrapper.find(StyledButton).at(1); + nextButton.simulate("click"); expect(onNextClick.mock.calls.length).toEqual(1); }); it("applies the custom class name", () => { expect(wrapper.find(".custom-class").length).toEqual(1); }); + + it("applies the expected aria-labels to the buttons", () => { + wrapper = mount( + + ); + + const prevButton = wrapper.find(StyledButton).at(0).getDOMNode(); + const nextButton = wrapper.find(StyledButton).at(1).getDOMNode(); + + expect(prevButton.getAttribute("aria-label")).toBe("Previous month"); + expect(nextButton.getAttribute("aria-label")).toBe("Next month"); + }); }); describe("Navbar Button", () => { diff --git a/src/components/date/date.spec.tsx b/src/components/date/date.spec.tsx index b0cff6d43d..d187b72086 100644 --- a/src/components/date/date.spec.tsx +++ b/src/components/date/date.spec.tsx @@ -43,45 +43,49 @@ import { } from "../../locales/date-fns-locales"; import Logger from "../../__internal__/utils/logger"; +const ariaLabels = { + nextMonthButton: () => "foo", + previousMonthButton: () => "foo", +}; const locales = { "en-GB": { locale: () => "en-GB", - date: { dateFnsLocale: () => enGBLocale }, + date: { ariaLabels, dateFnsLocale: () => enGBLocale }, separator: "/", }, de: { locale: () => "de", - date: { dateFnsLocale: () => deLocale }, + date: { ariaLabels, dateFnsLocale: () => deLocale }, separator: ".", }, es: { locale: () => "es", - date: { dateFnsLocale: () => esLocale }, + date: { ariaLabels, dateFnsLocale: () => esLocale }, separator: "/", }, "en-ZA": { locale: () => "en-ZA", - date: { dateFnsLocale: () => enZALocale }, + date: { ariaLabels, dateFnsLocale: () => enZALocale }, separator: "/", }, "fr-FR": { locale: () => "fr-FR", - date: { dateFnsLocale: () => frLocale }, + date: { ariaLabels, dateFnsLocale: () => frLocale }, separator: "/", }, "fr-CA": { locale: () => "fr-CA", - date: { dateFnsLocale: () => frCALocale }, + date: { ariaLabels, dateFnsLocale: () => frCALocale }, separator: "/", }, "en-US": { locale: () => "en-US", - date: { dateFnsLocale: () => enUSLocale }, + date: { ariaLabels, dateFnsLocale: () => enUSLocale }, separator: "/", }, "en-CA": { locale: () => "en-CA", - date: { dateFnsLocale: () => enCALocale }, + date: { ariaLabels, dateFnsLocale: () => enCALocale }, separator: "/", }, }; diff --git a/src/components/date/date.stories.mdx b/src/components/date/date.stories.mdx index 58457af0d9..b3f84d556f 100644 --- a/src/components/date/date.stories.mdx +++ b/src/components/date/date.stories.mdx @@ -230,5 +230,19 @@ The following keys are available to override the translations for this component type: "func", returnType: "date-fns locale object", }, + { + name: "date.ariaLabels.previousMonthButton", + description: + "Aria label text for the previous month navigation button in the date picker", + type: "func", + returnType: "string", + }, + { + name: "date.ariaLabels.nextMonthButton", + description: + "Aria label text for the next month navigation button in the date picker", + type: "func", + returnType: "string", + } ]} /> diff --git a/src/components/date/date.stories.tsx b/src/components/date/date.stories.tsx index 849bf7fdf0..402dab35af 100644 --- a/src/components/date/date.stories.tsx +++ b/src/components/date/date.stories.tsx @@ -448,7 +448,13 @@ export const LocaleOverrideExampleImplementation: ComponentStory< "de-DE", - date: { dateFnsLocale: () => de }, + date: { + dateFnsLocale: () => de, + ariaLabels: { + previousMonthButton: () => "Vorheriger Monat", + nextMonthButton: () => "Nächster Monat", + }, + }, }} > "zh-CN", - date: { dateFnsLocale: () => zhCN }, + date: { + dateFnsLocale: () => zhCN, + ariaLabels: { + previousMonthButton: () => "上个月", + nextMonthButton: () => "下个月", + }, + }, }} > enGBDateLocale, + ariaLabels: { + previousMonthButton: () => "Previous month", + nextMonthButton: () => "Next month", + }, }, dialog: { ariaLabels: { diff --git a/src/locales/locale.ts b/src/locales/locale.ts index bd4a8be0fe..32bb0ef6cf 100644 --- a/src/locales/locale.ts +++ b/src/locales/locale.ts @@ -31,6 +31,10 @@ interface Locale { }; date: { dateFnsLocale: () => DateFnsLocale; + ariaLabels: { + previousMonthButton: () => string; + nextMonthButton: () => string; + }; }; dialog: { ariaLabels: { diff --git a/src/locales/pl-pl.ts b/src/locales/pl-pl.ts index ea42aded9f..1fa4f62a9e 100644 --- a/src/locales/pl-pl.ts +++ b/src/locales/pl-pl.ts @@ -101,6 +101,10 @@ const plPL: Locale = { date: { dateFnsLocale: () => plDateLocale, + ariaLabels: { + previousMonthButton: () => "Poprzedni miesiąc", + nextMonthButton: () => "Następny miesiąc", + }, }, dialog: { ariaLabels: { From 11d185306a6cf30e67e1efcf31c7985366594b78 Mon Sep 17 00:00:00 2001 From: edleeks87 Date: Fri, 20 Oct 2023 16:03:41 +0100 Subject: [PATCH 04/24] feat(date): add support for keyboard navigation in picker Adds support for allowing users to keyboard navigate into the picker from the input element and around the focusable elements within it. The behaviour is as follows: - Tabbing from the input will focus the `previous` month navigation button. - Tabbing again will focus the `next` month navigation button. - Pressing enter or space on either of these buttons will change the currently displayed month. - Pressing tab again will focus the day element corresponding to either the selected or current (today's) date. - Pressing enter or space on a day element will select it, update the input value and close the picker. - Pressing tab will close the picker and focus the next focusable element in the DOM fix #6324, fix #3969 --- cypress/components/date/date.cy.tsx | 242 +++++++++++++++++- cypress/support/helper.ts | 7 +- .../date-picker/date-picker.component.tsx | 119 +++++++-- .../date-picker/date-picker.spec.tsx | 13 +- .../__internal__/navbar/navbar.component.tsx | 15 ++ .../date/__internal__/navbar/navbar.spec.tsx | 111 ++++++++ src/components/date/date-test.stories.tsx | 5 +- src/components/date/date.component.tsx | 17 +- src/components/date/date.spec.tsx | 94 ++++++- 9 files changed, 566 insertions(+), 57 deletions(-) diff --git a/cypress/components/date/date.cy.tsx b/cypress/components/date/date.cy.tsx index 04defa7217..02ff25c75b 100644 --- a/cypress/components/date/date.cy.tsx +++ b/cypress/components/date/date.cy.tsx @@ -42,7 +42,7 @@ import { } from "../../locators/date-input"; import { getDataElementByValue, fieldHelpPreview } from "../../locators"; -import { keyCode } from "../../support/helper"; +import { KeyIds, keyCode } from "../../support/helper"; import { verifyRequiredAsteriskForLabel, assertCssValueIsApproximately, @@ -75,7 +75,12 @@ const DDMMYYY_DATE_TO_ENTER_SHORT = "1,7,22"; const MMDDYYYY_DATE_TO_ENTER_SHORT = "7,1,22"; const YYYYMMDD_DATE_TO_ENTER_SHORT = "22,7,1"; const DATE_TO_VERIFY = "2022-05-12"; -const keysToTrigger = ["rightarrow", "leftarrow"] as const; +const arrowKeys = [ + "rightarrow", + "leftarrow", + "uparrow", + "downarrow", +] as KeyIds[]; context("Test for DateInput component", () => { describe("check functionality for DateInput component", () => { @@ -222,26 +227,239 @@ context("Test for DateInput component", () => { } ); + it.each(arrowKeys)( + "should not change the displayed month when %s is pressed and next button is focused", + (key) => { + CypressMountWithProviders(); + dateInputParent().click(); + getDataElementByValue("chevron_right").parent().focus(); + getDataElementByValue("chevron_right").trigger("keydown", keyCode(key)); + dayPickerHeading().should("have.text", "May 2022"); + } + ); + + it.each(arrowKeys)( + "should not change the displayed month when %s is pressed and previous button is focused", + (key) => { + CypressMountWithProviders(); + dateInputParent().click(); + getDataElementByValue("chevron_left").parent().focus(); + getDataElementByValue("chevron_left").trigger("keydown", keyCode(key)); + dayPickerHeading().should("have.text", "May 2022"); + } + ); + + it("should allow a user to tab into the picker and through its controls", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + getDataElementByValue("chevron_left").parent().should("be.focused"); + cy.focused().tab(); + getDataElementByValue("chevron_right").parent().should("be.focused"); + cy.focused().tab(); + cy.get(".DayPicker-Day--selected").should("be.focused"); + }); + + it("should close the picker and focus the next element in the DOM when focus is on a day element and tab pressed", () => { + CypressMountWithProviders( + <> + + + + ); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + getDataElementByValue("chevron_left").parent().should("be.focused"); + cy.focused().tab(); + getDataElementByValue("chevron_right").parent().should("be.focused"); + cy.focused().tab(); + cy.get(".DayPicker-Day--selected").should("be.focused"); + cy.focused().tab(); + dayPickerWrapper().should("not.exist"); + cy.get('[data-element="foo-button"]').should("be.focused"); + }); + + it("should focus today's date if no day selected when tabbing to day elements", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + getDataElementByValue("chevron_left").parent().should("be.focused"); + cy.focused().tab(); + getDataElementByValue("chevron_right").parent().should("be.focused"); + cy.focused().tab(); + cy.get(".DayPicker-Day--today").should("be.focused"); + cy.focused().tab(); + dayPickerWrapper().should("not.exist"); + }); + + it("should navigate through the day elements using the arrow keys", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("downarrow")); + cy.focused().should("have.text", "8"); + cy.focused().trigger("keydown", keyCode("downarrow")); + cy.focused().should("have.text", "15"); + cy.focused().trigger("keydown", keyCode("leftarrow")); + cy.focused().should("have.text", "14"); + cy.focused().trigger("keydown", keyCode("leftarrow")); + cy.focused().should("have.text", "13"); + cy.focused().trigger("keydown", keyCode("rightarrow")); + cy.focused().should("have.text", "14"); + cy.focused().trigger("keydown", keyCode("rightarrow")); + cy.focused().should("have.text", "15"); + cy.focused().trigger("keydown", keyCode("uparrow")); + cy.focused().should("have.text", "8"); + cy.focused().trigger("keydown", keyCode("uparrow")); + cy.focused().should("have.text", "1"); + }); + + it("should navigate to the previous month when left arrow pressed on first day element of a month", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("leftarrow")); + cy.focused().should("have.text", "30"); + dayPickerHeading().should("have.text", PREVIOUS_MONTH); + }); + it.each([ - ["chevron_right", "next", keysToTrigger[0]], - ["chevron_left", "previous", keysToTrigger[1]], + ["24", "1"], + ["25", "2"], + ["26", "3"], + ["27", "4"], + ["28", "5"], + ["29", "6"], + ["30", "7"], ])( - "should trigger %s arrow in DayPicker to verify %s month is shown using %s keyboard key", - (arrow, month, key) => { + "should navigate to day %s of previous month when up arrow pressed on day %s of first week of current month", + (result, input) => { + CypressMountWithProviders( + + ); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("uparrow")); + cy.focused().should("have.text", result); + dayPickerHeading().should("have.text", PREVIOUS_MONTH); + } + ); + + it.each([ + ["7", "31"], + ["6", "30"], + ["5", "29"], + ["4", "28"], + ["3", "27"], + ["2", "26"], + ["1", "25"], + ])( + "should navigate to day %s of next month when down arrow pressed on day %s of last week of current month", + (result, input) => { + CypressMountWithProviders( + + ); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("downarrow")); + cy.focused().should("have.text", result); + dayPickerHeading().should("have.text", NEXT_MONTH); + } + ); + + it.each(["Enter", "Space"] as KeyIds[])( + "should update the selected date when %s pressed on a day element", + (key) => { CypressMountWithProviders(); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("leftarrow")); + cy.focused().should("have.text", "30"); + cy.focused().trigger("keydown", keyCode(key)); + getDataElementByValue("input").should("have.value", "30/04/2022"); + } + ); - dateInput().clear().type(DATE_INPUT); - dateInputParent().click(); + it("should close the picker when escape is pressed and input focused", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dayPickerWrapper().should("exist"); - dayPickerWrapper().focus(); - getDataElementByValue(arrow).trigger("keydown", keyCode(key)); + cy.focused().trigger("keydown", keyCode("Esc")); + dayPickerWrapper().should("not.exist"); + }); + + it("should close the picker when escape is pressed and focus is within the picker and refocus the input", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dayPickerWrapper().should("exist"); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("Esc")); + dayPickerWrapper().should("not.exist"); + getDataElementByValue("input").should("be.focused"); + }); + + it("should close the picker when shift + tab is pressed and focus is on the previous month button in the picker and refocus the input", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dayPickerWrapper().should("exist"); + cy.focused().tab(); + cy.focused().tab({ shift: true }); + dayPickerWrapper().should("not.exist"); + getDataElementByValue("input").should("be.focused"); + }); + + it("should navigate to the next month when right arrow pressed on last day element of a month", () => { + CypressMountWithProviders(); + cy.get("body").tab(); + dateInput().should("be.focused"); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().tab(); + cy.focused().trigger("keydown", keyCode("rightarrow")); + cy.focused().should("have.text", "1"); + dayPickerHeading().should("have.text", NEXT_MONTH); + }); + + it.each([ + ["enter", "next", "chevron_right"], + ["space", "next", "chevron_right"], + ["enter", "previous", "chevron_left"], + ["space", "previous", "chevron_left"], + ])( + "should change the displayed month when %s is pressed and %s button is focused", + (key, month, arrow) => { + CypressMountWithProviders(); + + const keyToType = key === "space" ? " " : key; + dateInputParent().click(); + getDataElementByValue(arrow).parent().focus(); + getDataElementByValue(arrow).type(`{${keyToType}}`); if (month === "next") { dayPickerHeading().should("have.text", NEXT_MONTH); } else if (month === "previous") { dayPickerHeading().should("have.text", PREVIOUS_MONTH); - } else { - throw new Error("Only Next or Previous month can be applied"); } } ); diff --git a/cypress/support/helper.ts b/cypress/support/helper.ts index 44a660bcbe..12aaa95472 100644 --- a/cypress/support/helper.ts +++ b/cypress/support/helper.ts @@ -65,7 +65,7 @@ const keys = { uparrow: { key: "ArrowUp", keyCode: 38, which: 38 }, leftarrow: { key: "ArrowLeft", keyCode: 37, which: 37 }, rightarrow: { key: "ArrowRight", keyCode: 39, which: 39 }, - Enter: { key: "Enter", keyCode: 13, which: 13 }, + Enter: { key: "Enter", keyCode: 13, which: 13, bubbles: true }, EnterForce: { key: "Enter", keyCode: 13, which: 13, force: true }, Space: { key: " ", keyCode: 32, which: 32 }, Tab: { key: "Tab", keyCode: 9, which: 9 }, @@ -76,8 +76,11 @@ const keys = { pagedown: { key: "PageDown", keyCode: 34, which: 34 }, pageup: { key: "PageUp", keyCode: 33, which: 33 }, }; + +export type KeyIds = keyof typeof keys; + export function keyCode( - type: keyof typeof keys + type: KeyIds ): { key: string; keyCode: number; 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 94a1b44518..0370ac8de3 100644 --- a/src/components/date/__internal__/date-picker/date-picker.component.tsx +++ b/src/components/date/__internal__/date-picker/date-picker.component.tsx @@ -13,6 +13,8 @@ import useLocale from "../../../../hooks/__internal__/useLocale"; import Navbar from "../navbar"; import Weekday from "../weekday"; import StyledDayPicker from "./day-picker.style"; +import Events from "../../../../__internal__/utils/helpers/events"; +import { defaultFocusableSelectors } from "../../../../__internal__/focus-trap/focus-trap-utils"; type CustomRefObject = { current?: T | null; @@ -51,6 +53,10 @@ export interface DatePickerProps { open?: boolean; /** Callback triggered when a Day is clicked */ onDayClick?: (date: Date, ev: React.MouseEvent) => void; + /** Sets the picker open state */ + setOpen: (isOpen: boolean) => void; + /** Id passed to tab guard element */ + pickerTabGuardId?: string; } const popoverMiddleware = [ @@ -70,6 +76,8 @@ export const DatePicker = ({ pickerMouseDown, pickerProps, open, + setOpen, + pickerTabGuardId, }: DatePickerProps) => { const l = useLocale(); const { localize, options } = l.date.dateFnsLocale(); @@ -106,29 +114,6 @@ export const DatePicker = ({ .substring(0, isGivenLocale("de") ? 2 : 3) ); }, [l, localize]); - - const handleDayClick = ( - date: Date, - modifiers: DayModifiers, - ev: React.MouseEvent - ) => { - if (!modifiers.disabled) { - const { id, name } = inputElement?.current - ?.firstChild as HTMLInputElement; - ev.target = { - ...ev.target, - id, - name, - } as HTMLInputElement; - onDayClick?.(date, ev); - } - }; - - const formatDay = (date: Date) => - `${weekdaysShort[date.getDay()]} ${date.getDate()} ${ - monthsShort[date.getMonth()] - } ${date.getFullYear()}`; - const ref = useRef(null); useEffect(() => { @@ -157,12 +142,87 @@ export const DatePicker = ({ } }, [open]); + const handleDayClick = ( + date: Date, + modifiers: DayModifiers, + ev: React.MouseEvent + ) => { + if (!modifiers.disabled) { + const { id, name } = inputElement?.current + ?.firstChild as HTMLInputElement; + ev.target = { + ...ev.target, + id, + name, + } as HTMLInputElement; + onDayClick?.(date, ev); + } + }; + + const handleOnKeyDown = (ev: React.KeyboardEvent) => { + if (Events.isEscKey(ev)) { + inputElement.current?.querySelector("input")?.focus(); + setOpen(false); + } + + if ( + ref.current?.querySelector(".DayPicker-NavBar button") === + document.activeElement && + Events.isTabKey(ev) && + Events.isShiftKey(ev) + ) { + ev.preventDefault(); + setOpen(false); + inputElement.current?.querySelector("input")?.focus(); + } + }; + + const handleOnDayKeyDown = ( + _day: Date, + _modifiers: DayModifiers, + ev: React.KeyboardEvent + ) => { + // we need to manually handle this as the picker may be in a Portal + /* istanbul ignore else */ + if (Events.isTabKey(ev) && !Events.isShiftKey(ev)) { + ev.preventDefault(); + setOpen(false); + const input = inputElement.current?.querySelector("input"); + + /* istanbul ignore else */ + if (input) { + const elements = Array.from( + document.querySelectorAll(defaultFocusableSelectors) || + /* istanbul ignore next */ [] + ) as HTMLElement[]; + const elementsInPicker = Array.from( + ref.current?.querySelectorAll("button, [tabindex]") || + /* istanbul ignore next */ [] + ) as HTMLElement[]; + const filteredElements = elements.filter( + (el) => Number(el.tabIndex) !== -1 && !elementsInPicker.includes(el) + ); + const nextIndex = filteredElements.indexOf(input as HTMLElement) + 1; + filteredElements[nextIndex]?.focus(); + } + } + }; + + const formatDay = (date: Date) => + `${weekdaysShort[date.getDay()]} ${date.getDate()} ${ + monthsShort[date.getMonth()] + } ${date.getFullYear()}`; + if (!open) { return null; } const localeUtils = { formatDay } as LocaleUtils; + const handleTabGuardFocus = () => { + ref.current?.querySelector("button")?.focus(); + }; + return ( - + +
diff --git a/src/components/date/__internal__/date-picker/date-picker.spec.tsx b/src/components/date/__internal__/date-picker/date-picker.spec.tsx index 91f758fa14..d29c4d99dc 100644 --- a/src/components/date/__internal__/date-picker/date-picker.spec.tsx +++ b/src/components/date/__internal__/date-picker/date-picker.spec.tsx @@ -61,16 +61,19 @@ const MockComponent = (props: MockProps) => { function renderI18n({ locale, ...props -}: { locale?: Partial } & Omit) { +}: { locale?: Partial } & Omit< + DatePickerProps, + "inputElement" | "setOpen" +>) { return mount( - + {}} /> ); } -function render(props: Omit) { - return mount(); +function render(props: Omit) { + return mount( {}} />); } describe("DatePicker", () => { @@ -162,7 +165,7 @@ describe("DatePicker", () => { }); }); - describe('when the "onDayClick" prop have been triggered', () => { + describe('when the "onDayClick" prop has been triggered', () => { let onDayClickFn: jest.Mock; beforeEach(() => { diff --git a/src/components/date/__internal__/navbar/navbar.component.tsx b/src/components/date/__internal__/navbar/navbar.component.tsx index 4a4222f5a2..1732c337f8 100644 --- a/src/components/date/__internal__/navbar/navbar.component.tsx +++ b/src/components/date/__internal__/navbar/navbar.component.tsx @@ -2,6 +2,7 @@ import React from "react"; import StyledButton from "./button.style"; import StyledNavbar from "./navbar.style"; import Icon from "../../../icon"; +import Events from "../../../../__internal__/utils/helpers/events"; import useLocale from "../../../../hooks/__internal__/useLocale"; export interface NavbarProps { @@ -18,17 +19,31 @@ export const Navbar = ({ const locale = useLocale(); const { previousMonthButton, nextMonthButton } = locale.date.ariaLabels; + const handleKeyDown = (ev: React.KeyboardEvent) => { + if ( + Events.isLeftKey(ev) || + Events.isRightKey(ev) || + Events.isUpKey(ev) || + Events.isDownKey(ev) + ) { + ev.stopPropagation(); + ev.preventDefault(); + } + }; + return ( onPreviousClick?.()} + onKeyDown={handleKeyDown} > onNextClick?.()} + onKeyDown={handleKeyDown} > diff --git a/src/components/date/__internal__/navbar/navbar.spec.tsx b/src/components/date/__internal__/navbar/navbar.spec.tsx index 6c8b2147f2..833b644d3e 100644 --- a/src/components/date/__internal__/navbar/navbar.spec.tsx +++ b/src/components/date/__internal__/navbar/navbar.spec.tsx @@ -5,6 +5,9 @@ import { mount, ReactWrapper, shallow, ShallowWrapper } from "enzyme"; import Navbar, { NavbarProps } from "./navbar.component"; import StyledButton from "./button.style"; +const arrowKeys = ["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"]; +const actionKeys = ["Enter", "Space"]; + describe("Navbar", () => { let wrapper: ShallowWrapper | ReactWrapper; let onPreviousClick: jest.Mock; @@ -54,6 +57,114 @@ describe("Navbar", () => { expect(prevButton.getAttribute("aria-label")).toBe("Previous month"); expect(nextButton.getAttribute("aria-label")).toBe("Next month"); }); + + it.each(arrowKeys)( + "does not change the current month when %s key is pressed and previous button is focused", + (key) => { + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + + wrapper = mount( + + ); + + const prevButton = wrapper.find(StyledButton).at(0); + (prevButton.getDOMNode() as HTMLElement).focus(); + prevButton.prop("onKeyDown")({ + key, + stopPropagation, + preventDefault, + }); + + expect(stopPropagation).toHaveBeenCalled(); + expect(preventDefault).toHaveBeenCalled(); + } + ); + + it.each(arrowKeys)( + "does not change the current month when %s key is pressed and next button is focused", + (key) => { + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + + wrapper = mount( + + ); + + const nextButton = wrapper.find(StyledButton).at(1); + (nextButton.getDOMNode() as HTMLElement).focus(); + nextButton.prop("onKeyDown")({ + key, + stopPropagation, + preventDefault, + }); + + expect(stopPropagation).toHaveBeenCalled(); + expect(preventDefault).toHaveBeenCalled(); + } + ); + + it.each(actionKeys)( + "changes the current month when %s key is pressed and previous button is focused", + (key) => { + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + + wrapper = mount( + + ); + + const prevButton = wrapper.find(StyledButton).at(0); + (prevButton.getDOMNode() as HTMLElement).focus(); + prevButton.prop("onKeyDown")({ + key, + stopPropagation, + preventDefault, + }); + + expect(stopPropagation).not.toHaveBeenCalled(); + expect(preventDefault).not.toHaveBeenCalled(); + } + ); + + it.each(actionKeys)( + "changes the current month when %s key is pressed and next button is focused", + (key) => { + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + + wrapper = mount( + + ); + + const nextButton = wrapper.find(StyledButton).at(1); + (nextButton.getDOMNode() as HTMLElement).focus(); + nextButton.prop("onKeyDown")({ + key, + stopPropagation, + preventDefault, + }); + + expect(stopPropagation).not.toHaveBeenCalled(); + expect(preventDefault).not.toHaveBeenCalled(); + } + ); }); describe("Navbar Button", () => { diff --git a/src/components/date/date-test.stories.tsx b/src/components/date/date-test.stories.tsx index 5c14dd696f..2dee56f5de 100644 --- a/src/components/date/date-test.stories.tsx +++ b/src/components/date/date-test.stories.tsx @@ -91,9 +91,12 @@ NewValidationStory.args = { export const DateInputCustom = ({ onChange, onBlur, + value, ...props }: Partial & Partial) => { - const [state, setState] = React.useState("01/05/2022"); + const [state, setState] = React.useState( + value?.length !== undefined ? value : "01/05/2022" + ); const handleOnChange = (ev: DateChangeEvent) => { if (onChange) { diff --git a/src/components/date/date.component.tsx b/src/components/date/date.component.tsx index ae7fd4b7e6..206303ec99 100644 --- a/src/components/date/date.component.tsx +++ b/src/components/date/date.component.tsx @@ -29,6 +29,7 @@ import DateRangeContext, { InputName } from "../date-range/date-range.context"; import useClickAwayListener from "../../hooks/__internal__/useClickAwayListener"; import Logger from "../../__internal__/utils/logger"; import useFormSpacing from "../../hooks/__internal__/useFormSpacing"; +import guid from "../../__internal__/utils/helpers/guid"; interface CustomDateEvent { type: string; @@ -153,6 +154,7 @@ export const DateInput = React.forwardRef( : parseDate(format, value) ); const isInitialValue = useRef(true); + const pickerTabGuardId = useRef(guid()); if (!deprecateInputRefWarnTriggered && inputRef) { deprecateInputRefWarnTriggered = true; @@ -306,8 +308,19 @@ export const DateInput = React.forwardRef( onKeyDown(ev); } - if (Events.isTabKey(ev)) { + if (Events.isEscKey(ev)) { setOpen(false); + } + + if (open && Events.isTabKey(ev)) { + if (Events.isShiftKey(ev)) { + setOpen(false); + } else if (!disablePortal) { + ev.preventDefault(); + (document?.querySelector( + `[id="${pickerTabGuardId.current}"]` + ) as HTMLElement)?.focus(); + } alreadyFocused.current = false; } }; @@ -488,6 +501,8 @@ export const DateInput = React.forwardRef( maxDate={maxDate} pickerMouseDown={handlePickerMouseDown} open={open} + setOpen={setOpen} + pickerTabGuardId={pickerTabGuardId.current} /> ); diff --git a/src/components/date/date.spec.tsx b/src/components/date/date.spec.tsx index d187b72086..6e76402793 100644 --- a/src/components/date/date.spec.tsx +++ b/src/components/date/date.spec.tsx @@ -42,6 +42,7 @@ import { enUS as enUSLocale, } from "../../locales/date-fns-locales"; import Logger from "../../__internal__/utils/logger"; +import StyledButton from "./__internal__/navbar/button.style"; const ariaLabels = { nextMonthButton: () => "foo", @@ -155,8 +156,12 @@ function simulateMouseDownOnPicker(wrapper: ReactWrapper) { }); } -function simulateOnKeyDown(wrapper: ReactWrapper, key: string) { - const keyDownParams = { key }; +function simulateOnKeyDown( + wrapper: ReactWrapper, + key: string, + shiftKey?: boolean +) { + const keyDownParams = { key, shiftKey }; const input = wrapper.find("input"); act(() => { @@ -519,20 +524,85 @@ describe("Date", () => { }); }); - describe('and with the "Tab" key', () => { - it('then the "DatePicker" should be closed', () => { - expect(wrapper.update().find(DayPicker).exists()).toBe(true); - simulateOnKeyDown(wrapper, "Tab"); - expect(wrapper.update().find(DayPicker).exists()).toBe(false); + it('the "DatePicker" should remain open and the previous month navigation button should be focused when the "Tab" key is pressed', () => { + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + simulateOnKeyDown(wrapper, "Tab"); + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + expect(wrapper.find(StyledButton).first()).toBeFocused(); + }); + + it('the "DatePicker" should remain open and the previous month navigation button should be focused when the "Tab" key is pressed and disablePortal set', () => { + wrapper = render({ disablePortal: true }); + simulateFocusOnInput(wrapper); + + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + simulateOnKeyDown(wrapper, "Tab"); + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + expect(wrapper.find(StyledButton).first()).toBeFocused(); + }); + + it('the "DatePicker" should close when the "Escape" key is pressed', () => { + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + act(() => { + simulateOnKeyDown(wrapper, "Escape"); }); + expect(wrapper.update().find(DayPicker).exists()).toBe(false); }); - describe('and with the key other that "Tab"', () => { - it('then the "DatePicker" should not be closed', () => { - expect(wrapper.update().find(DayPicker).exists()).toBe(true); - simulateOnKeyDown(wrapper, "Enter"); - expect(wrapper.update().find(DayPicker).exists()).toBe(true); + it('the "DatePicker" should close when the "Shift" and "Tab" keys are pressed', () => { + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + act(() => { + simulateOnKeyDown(wrapper, "Tab", true); + }); + expect(wrapper.update().find(DayPicker).exists()).toBe(false); + }); + + it('the "DatePicker" should not be closed when a key other than "Tab" or "Escape" is pressed', () => { + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + simulateOnKeyDown(wrapper, "Enter"); + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + }); + }); + + describe('when the "keyDown" event is triggered on the picker', () => { + beforeEach(() => { + wrapper = render(); + simulateFocusOnInput(wrapper); + }); + + it('should close the picker when "Escape" key is pressed and focus is in picker', () => { + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + simulateOnKeyDown(wrapper, "Tab"); + expect(wrapper.find(StyledButton).first()).toBeFocused(); + act(() => { + wrapper.find(StyledDayPicker).simulate("keydown", { key: "Escape" }); + }); + expect(wrapper.update().find(DayPicker).exists()).toBe(false); + }); + + it('should close the picker when "Shift" + "Tab" keys are pressed and focus is on previous month button', () => { + expect(wrapper.update().find(DayPicker).exists()).toBe(true); + simulateOnKeyDown(wrapper, "Tab"); + act(() => { + wrapper + .find(StyledDayPicker) + .simulate("keydown", { key: "Tab", shiftKey: true }); + }); + expect(wrapper.update().find(DayPicker).exists()).toBe(false); + }); + + it("should close the picker when Tab pressed and day element focused", () => { + const picker = wrapper.update().find(DayPicker); + expect(picker.exists()).toBe(true); + act(() => { + picker + ?.props() + ?.onDayKeyDown?.(new Date(), { today: false, outside: false }, { + key: "Tab", + preventDefault: () => {}, + } as React.KeyboardEvent); }); + expect(wrapper.update().find(DayPicker).exists()).toBe(false); }); }); From 47fd717e92ef2d701b9d98acbf89f547ec02c8f1 Mon Sep 17 00:00:00 2001 From: edleeks87 Date: Fri, 20 Oct 2023 17:14:50 +0100 Subject: [PATCH 05/24] chore(date): increase spacing between input and picker when new focus styles applied --- cypress/components/date/date.cy.tsx | 2 ++ .../__snapshots__/date-picker.spec.tsx.snap | 1 + .../date/__internal__/date-picker/day-picker.style.ts | 10 ++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cypress/components/date/date.cy.tsx b/cypress/components/date/date.cy.tsx index 02ff25c75b..9c28bc31dc 100644 --- a/cypress/components/date/date.cy.tsx +++ b/cypress/components/date/date.cy.tsx @@ -892,6 +892,8 @@ context("Test for DateInput component", () => { dateInput().focus(); + dayPickerParent().should("have.css", "margin-top", "4px"); + dateInputParent() .should( "have.css", diff --git a/src/components/date/__internal__/date-picker/__snapshots__/date-picker.spec.tsx.snap b/src/components/date/__internal__/date-picker/__snapshots__/date-picker.spec.tsx.snap index 1540d6b7c5..cb7c678324 100644 --- a/src/components/date/__internal__/date-picker/__snapshots__/date-picker.spec.tsx.snap +++ b/src/components/date/__internal__/date-picker/__snapshots__/date-picker.spec.tsx.snap @@ -6,6 +6,7 @@ exports[`StyledDayPicker renders presentational div and context provider for its height: 346px; width: 352px; z-index: 6000; + margin-top: var(--spacing050); } .c0 .DayPicker { 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 55364c2b7a..8cdf8d4b86 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,4 @@ -import styled from "styled-components"; +import styled, { css } from "styled-components"; import baseTheme from "../../../../style/themes/base"; import addFocusStyling from "../../../../style/utils/add-focus-styling"; @@ -187,7 +187,13 @@ const StyledDayPicker = styled.div` position: absolute; height: 346px; width: 352px; - z-index: ${({ theme }) => theme.zIndex.popover}; + ${({ theme }) => css` + z-index: ${theme.zIndex.popover}; + ${!theme.focusRedesignOptOut && + ` + margin-top: var(--spacing050); + `} + `} .DayPicker { z-index: 1000; From 2c691011688c5186a7d20f8af7e6acd768e5a0e8 Mon Sep 17 00:00:00 2001 From: DipperTheDan Date: Wed, 25 Oct 2023 16:07:36 +0100 Subject: [PATCH 06/24] feat(confirm): make data tag props available on cancel and confirm buttons This enhancement makes `data-component`, `data-role` and `data-element` tags available on the cancel and confirm buttons in this component fixes #6374 --- src/components/confirm/confirm.component.tsx | 17 +++++++++-- src/components/confirm/confirm.pw.tsx | 32 ++++++++++++++++++++ src/components/confirm/confirm.spec.tsx | 32 ++++++++++++++++++++ src/components/confirm/confirm.stories.mdx | 6 ++++ src/components/confirm/confirm.stories.tsx | 27 +++++++++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/components/confirm/confirm.component.tsx b/src/components/confirm/confirm.component.tsx index 76050757cd..6c5b35045c 100644 --- a/src/components/confirm/confirm.component.tsx +++ b/src/components/confirm/confirm.component.tsx @@ -8,6 +8,7 @@ import Button from "../button/button.component"; import Icon, { IconType } from "../icon"; import Loader from "../loader"; import useLocale from "../../hooks/__internal__/useLocale"; +import tagComponent, { TagProps } from "../../__internal__/utils/helpers/tags"; export interface ConfirmProps extends Omit< @@ -55,6 +56,10 @@ export interface ConfirmProps confirmButtonIconPosition?: "before" | "after"; /** Defines an Icon type within the confirm button (see Icon for options) */ confirmButtonIconType?: IconType; + /** Data tag prop bag for cancelButton */ + cancelButtonDataProps?: TagProps; + /** Data tag prop bag for confirmButton */ + confirmButtonDataProps?: TagProps; /** Makes cancel button disabled */ disableCancel?: boolean; /** Makes confirm button disabled */ @@ -81,6 +86,8 @@ export const Confirm = ({ cancelButtonIconPosition, confirmButtonIconType, confirmButtonIconPosition, + cancelButtonDataProps, + confirmButtonDataProps, cancelLabel, onCancel, disableCancel, @@ -122,12 +129,15 @@ export const Confirm = ({ ev: React.MouseEvent ) => void } - data-element="cancel" buttonType={cancelButtonType} destructive={cancelButtonDestructive} disabled={disableCancel} iconType={cancelButtonIconType} iconPosition={cancelButtonIconPosition} + {...tagComponent("cancel", { + "data-element": "cancel", + ...cancelButtonDataProps, + })} > {cancelLabel || l.confirm.no()} @@ -142,13 +152,16 @@ export const Confirm = ({ ev: React.MouseEvent ) => void } - data-element="confirm" buttonType={confirmButtonType} destructive={confirmButtonDestructive} disabled={isLoadingConfirm || disableConfirm} ml={2} iconType={confirmButtonIconType} iconPosition={confirmButtonIconPosition} + {...tagComponent("confirm", { + "data-element": "confirm", + ...confirmButtonDataProps, + })} > {isLoadingConfirm ? ( diff --git a/src/components/confirm/confirm.pw.tsx b/src/components/confirm/confirm.pw.tsx index aa02dbd6fe..6f3da3858a 100644 --- a/src/components/confirm/confirm.pw.tsx +++ b/src/components/confirm/confirm.pw.tsx @@ -495,6 +495,38 @@ test.describe("should render Confirm component", () => { "rgb(255, 188, 25) 0px 0px 0px 3px, rgba(0, 0, 0, 0.9) 0px 0px 0px 6px" ); }); + + test(`should check custom data tags are correctly applied to their respective buttons`, async ({ + mount, + page, + }) => { + await mount( + {}} + onConfirm={() => {}} + cancelButtonDataProps={{ + "data-element": "bang", + "data-role": "wallop", + }} + confirmButtonDataProps={{ + "data-element": "bar", + "data-role": "wiz", + }} + open + /> + ); + + const cancelButton = getDataElementByValue(page, "bang"); + const confirmButton = getDataElementByValue(page, "bar"); + + await expect(cancelButton).toHaveAttribute("data-component", "cancel"); + await expect(cancelButton).toHaveAttribute("data-element", "bang"); + await expect(cancelButton).toHaveAttribute("data-role", "wallop"); + + await expect(confirmButton).toHaveAttribute("data-component", "confirm"); + await expect(confirmButton).toHaveAttribute("data-element", "bar"); + await expect(confirmButton).toHaveAttribute("data-role", "wiz"); + }); }); test.describe("should render Confirm component for event tests", () => { diff --git a/src/components/confirm/confirm.spec.tsx b/src/components/confirm/confirm.spec.tsx index 434783f602..2d451b69b3 100644 --- a/src/components/confirm/confirm.spec.tsx +++ b/src/components/confirm/confirm.spec.tsx @@ -14,6 +14,7 @@ import IconButton from "../icon-button"; import StyledIconButton from "../icon-button/icon-button.style"; import { StyledDialog } from "../dialog/dialog.style"; import Logger from "../../__internal__/utils/logger"; +import { rootTagTest } from "../../__internal__/utils/helpers/tags/tags-specs"; // mock Logger.deprecate so that no console warnings occur while running the tests const loggerSpy = jest.spyOn(Logger, "deprecate"); @@ -490,4 +491,35 @@ describe("Confirm", () => { ) ); }); + + it("has proper data attributes applied to elements", () => { + wrapper = mount( + {}} + onConfirm={() => {}} + cancelButtonDataProps={{ + "data-element": "bang", + "data-role": "wallop", + }} + confirmButtonDataProps={{ + "data-element": "bar", + "data-role": "wiz", + }} + open + /> + ); + const cancelButton = wrapper + .find(Button) + .filter('[data-component="cancel"]') + .at(0); + + const confirmButton = wrapper + .find(Button) + .filter('[data-component="confirm"]') + .at(0); + + rootTagTest(cancelButton, "cancel", "bang", "wallop"); + rootTagTest(confirmButton, "confirm", "bar", "wiz"); + }); }); diff --git a/src/components/confirm/confirm.stories.mdx b/src/components/confirm/confirm.stories.mdx index 2d609af717..57eb29a0db 100644 --- a/src/components/confirm/confirm.stories.mdx +++ b/src/components/confirm/confirm.stories.mdx @@ -106,6 +106,12 @@ Allows to set variant which is supported in ` + setIsOpen(false)} + onCancel={() => setIsOpen(false)} + cancelButtonDataProps={{ + "data-element": "bang", + "data-role": "wallop", + }} + confirmButtonDataProps={{ + "data-element": "bar", + "data-role": "wiz", + }} + > + Content + + + ); +}; + export const SingleAction = () => { const [isOpen, setIsOpen] = useState(defaultOpenState); return ( From 6dc0a93ff29f12814b8098c253bb28937a316638 Mon Sep 17 00:00:00 2001 From: divya jindel Date: Wed, 11 Oct 2023 20:41:42 +0100 Subject: [PATCH 07/24] test(icon-button): add playwright tests --- .../components/icon-button/icon-button.cy.tsx | 155 ------------- playwright/components/index.ts | 5 + .../icon-button/component.test-pw.tsx | 13 ++ .../icon-button/icon-button-test.stories.tsx | 8 - src/components/icon-button/icon-button.pw.tsx | 215 ++++++++++++++++++ 5 files changed, 233 insertions(+), 163 deletions(-) delete mode 100644 cypress/components/icon-button/icon-button.cy.tsx create mode 100644 src/components/icon-button/component.test-pw.tsx create mode 100644 src/components/icon-button/icon-button.pw.tsx diff --git a/cypress/components/icon-button/icon-button.cy.tsx b/cypress/components/icon-button/icon-button.cy.tsx deleted file mode 100644 index f5c2cc94a7..0000000000 --- a/cypress/components/icon-button/icon-button.cy.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React from "react"; -import { IconButtonProps } from "components/icon-button"; -import CypressMountWithProviders from "../../support/component-helper/cypress-mount"; -import { icon } from "../../locators"; -import { IconButtonComponent } from "../../../src/components/icon-button/icon-button-test.stories"; -import { CHARACTERS } from "../../support/component-helper/constants"; -import { keyCode } from "../../support/helper"; - -const keyToTrigger = ["Space", "Enter"] as const; - -context("Tests for IconButton component", () => { - describe("when focused", () => { - it("should have the expected styling when the focusRedesignOptOut is false", () => { - CypressMountWithProviders( - - ); - - icon() - .parent() - .focus() - .should( - "have.css", - "box-shadow", - "rgb(255, 188, 25) 0px 0px 0px 3px, rgba(0, 0, 0, 0.9) 0px 0px 0px 6px" - ) - .and("have.css", "outline", "rgba(0, 0, 0, 0) solid 3px"); - }); - - it("should have the expected styling when the focusRedesignOptOut is true", () => { - CypressMountWithProviders( - , - undefined, - undefined, - { - focusRedesignOptOut: true, - } - ); - icon() - .parent() - .focus() - .should("have.css", "outline", "rgb(255, 188, 25) solid 3px"); - }); - }); - describe("check props for IconButton component", () => { - it("should render IconButton with aria-label prop", () => { - CypressMountWithProviders( - - ); - - icon() - .parent() - .should("have.attr", "aria-label", CHARACTERS.STANDARD) - .and("be.visible"); - }); - - it("should render IconButton with children prop", () => { - CypressMountWithProviders(); - - icon().should("be.visible"); - }); - - it("should render IconButton with disabled prop", () => { - CypressMountWithProviders(); - - icon().parent().should("be.disabled").and("have.attr", "disabled"); - }); - }); - - describe("check events for IconButton component", () => { - it("should call onBlur callback when a blur event is triggered", () => { - const callback: IconButtonProps["onBlur"] = cy.stub().as("onBlur"); - CypressMountWithProviders(); - - icon().parent().focus().blur(); - cy.get("@onBlur").should("have.been.calledOnce"); - }); - - it("should call onFocus callback when a focus event is triggered", () => { - const callback: IconButtonProps["onFocus"] = cy.stub().as("onFocus"); - CypressMountWithProviders(); - - icon().parent().focus(); - cy.get("@onFocus").should("have.been.calledOnce"); - }); - - it("should call onMouseEnter callback when a mouseover event is triggered", () => { - const callback: IconButtonProps["onMouseEnter"] = cy - .stub() - .as("onMouseEnter"); - CypressMountWithProviders( - - ); - - icon().parent().trigger("mouseover"); - cy.get("@onMouseEnter").should("have.been.calledOnce"); - }); - - it("should call onMouseLeave callback when a mouseout event is triggered", () => { - const callback: IconButtonProps["onMouseLeave"] = cy - .stub() - .as("onMouseLeave"); - CypressMountWithProviders( - - ); - - icon().parent().trigger("mouseover").trigger("mouseout"); - cy.get("@onMouseLeave").should("have.been.calledOnce"); - }); - - it("should call onClick callback when a click event is triggered", () => { - const callback: IconButtonProps["onClick"] = cy.stub().as("onClick"); - CypressMountWithProviders(); - - icon().parent().click(); - cy.get("@onClick").should("have.been.calledOnce"); - }); - - it.each([...keyToTrigger])( - "should call onClick callback when a keydown event is triggered with %s", - (key) => { - const callback: IconButtonProps["onClick"] = cy.stub().as("onClick"); - CypressMountWithProviders(); - - icon().parent().trigger("keydown", keyCode(key)); - cy.get("@onClick").should("have.been.calledOnce"); - } - ); - }); - - describe("check accessibility tests for IconButton component", () => { - it("should pass accessibility tests for aria-label prop", () => { - CypressMountWithProviders( - - ); - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for children prop", () => { - CypressMountWithProviders(); - cy.checkAccessibility(); - }); - - it("should pass accessibility tests for disabled prop", () => { - CypressMountWithProviders(); - cy.checkAccessibility(); - }); - }); - - it("render with the expected border radius when roundness is %s", () => { - CypressMountWithProviders(); - - icon().parent().focus(); - icon().parent().should("have.css", "border-radius", "4px"); - }); -}); diff --git a/playwright/components/index.ts b/playwright/components/index.ts index d98765264b..2949a58c58 100644 --- a/playwright/components/index.ts +++ b/playwright/components/index.ts @@ -11,6 +11,7 @@ import { STICKY_FOOTER, COMMMON_DATA_ELEMENT_INPUT, PORTAL, + BUTTON, } from "./locators"; export const icon = (page: Page) => { @@ -25,6 +26,10 @@ export const commonDataElementInputPreview = (page: Page) => { return page.locator(COMMMON_DATA_ELEMENT_INPUT); }; +export const button = (page: Page) => { + return page.locator(BUTTON); +}; + export const closeIconButton = (page: Page) => { return page.locator(CLOSE_ICON_BUTTON); }; diff --git a/src/components/icon-button/component.test-pw.tsx b/src/components/icon-button/component.test-pw.tsx new file mode 100644 index 0000000000..18683a59d4 --- /dev/null +++ b/src/components/icon-button/component.test-pw.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import IconButton, { IconButtonProps } from "."; +import Icon from "../icon"; + +const IconButtonComponent = (props: Partial) => { + return ( + {}} {...props}> + + + ); +}; + +export default IconButtonComponent; diff --git a/src/components/icon-button/icon-button-test.stories.tsx b/src/components/icon-button/icon-button-test.stories.tsx index c0ef019e97..1852d0d483 100644 --- a/src/components/icon-button/icon-button-test.stories.tsx +++ b/src/components/icon-button/icon-button-test.stories.tsx @@ -22,11 +22,3 @@ export const Default = (props: IconButtonProps) => { }; Default.storyName = "default"; - -export const IconButtonComponent = (props: Partial) => { - return ( - {}} {...props}> - - - ); -}; diff --git a/src/components/icon-button/icon-button.pw.tsx b/src/components/icon-button/icon-button.pw.tsx new file mode 100644 index 0000000000..e32dd4b970 --- /dev/null +++ b/src/components/icon-button/icon-button.pw.tsx @@ -0,0 +1,215 @@ +import React from "react"; +import { test, expect } from "@playwright/experimental-ct-react17"; +import IconButtonComponent from "./component.test-pw"; +import { button as iconButton } from "../../../playwright/components/index"; +import { CHARACTERS } from "../../../playwright/support/constants"; +import { HooksConfig } from "../../../playwright"; +import { checkAccessibility } from "../../../playwright/support/helper"; + +test.describe( + "check IconButton component focus outlines and border radius", + () => { + test(`should have the expected styling when the focusRedesignOptOut is false`, async ({ + mount, + page, + }) => { + await mount(); + + await iconButton(page).focus(); + await expect(iconButton(page)).toHaveCSS( + "box-shadow", + "rgb(255, 188, 25) 0px 0px 0px 3px, rgba(0, 0, 0, 0.9) 0px 0px 0px 6px" + ); + + await expect(iconButton(page)).toHaveCSS( + "outline", + "rgba(0, 0, 0, 0) solid 3px" + ); + }); + + test(`should have the expected styling when the focusRedesignOptOut is true`, async ({ + mount, + page, + }) => { + await mount(, { + hooksConfig: { focusRedesignOptOut: true }, + }); + + await iconButton(page).focus(); + await expect(iconButton(page)).toHaveCSS( + "outline", + "rgb(255, 188, 25) solid 3px" + ); + }); + + test(`should render with the expected border radius`, async ({ + mount, + page, + }) => { + await mount(); + + await iconButton(page).focus(); + await expect(iconButton(page)).toHaveCSS("border-radius", "4px"); + }); + } +); + +test.describe("check props for IconButton component", () => { + test(`should render with aria-label prop`, async ({ mount, page }) => { + await mount(); + + await expect(iconButton(page)).toHaveAttribute( + "aria-label", + CHARACTERS.STANDARD + ); + await expect(iconButton(page)).toBeVisible(); + }); + + test(`should render with a child`, async ({ mount, page }) => { + await mount(); + + await expect(iconButton(page)).toBeVisible(); + }); + + test(`should render with disabled prop`, async ({ mount, page }) => { + await mount(); + + await expect(iconButton(page)).toBeDisabled(); + }); +}); + +test.describe("check events for IconButton component", () => { + test(`should call onBlur callback when a blur event is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await iconButton(page).focus(); + await iconButton(page).blur(); + expect(callbackCount).toBe(1); + }); + + test(`should call onFocus callback when a focus event is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await iconButton(page).focus(); + expect(callbackCount).toBe(1); + }); + + test(`should call onMouseEnter callback when a mouseover event is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await iconButton(page).hover(); + expect(callbackCount).toBe(1); + }); + + test(`should call onMouseLeave callback when a mouseout event is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await iconButton(page).hover(); + await page.mouse.move(100, 0); + expect(callbackCount).toBe(1); + }); + + test(`should call onClick callback when a click event is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + await iconButton(page).click(); + expect(callbackCount).toBe(1); + }); + + ["Enter", "Space"].forEach((key) => { + test(`should call onClick callback when ${key} key is triggered`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + await iconButton(page).press(key); + expect(callbackCount).toBe(1); + }); + }); +}); + +test.describe("check accessibility tests for IconButton component", () => { + test(`should pass accessibility tests when rendered with an aria-label`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests when rendered with a child`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests when disabled`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); +}); From ab1c49df2050938ad8cdf12e4476bc734483625b Mon Sep 17 00:00:00 2001 From: DipperTheDan Date: Fri, 27 Oct 2023 15:21:13 +0100 Subject: [PATCH 08/24] chore: address various spelling mistakes and some code formatting --- cypress/components/flat-table/flat-table.cy.tsx | 4 ++-- .../grouped-character/grouped-character.cy.tsx | 2 +- cypress/components/menu/menu.cy.tsx | 8 ++++---- .../multi-action-button.cy.tsx | 4 ++-- cypress/components/number/number.cy.tsx | 2 +- cypress/components/pager/pager.cy.tsx | 6 +++--- cypress/components/password/password.cy.tsx | 2 +- cypress/components/switch/switch.cy.tsx | 2 +- cypress/components/tooltip/tooltip.cy.tsx | 16 ++++++++-------- .../vertical-menu/vertical-menu.cy.tsx | 16 ++++++++-------- playwright/components/step-sequence/index.ts | 2 +- .../action-popover/action-popover.pw.tsx | 10 ++++++---- .../action-popover/action-popover.stories.tsx | 4 ++-- src/components/button-bar/button-bar.spec.tsx | 2 +- src/components/button-minor/button-minor.pw.tsx | 2 +- .../button-toggle/button-toggle.spec.tsx | 2 +- .../flat-table/flat-table-test.stories.tsx | 2 +- src/components/loader/loader.pw.tsx | 2 +- src/components/menu/menu.spec.tsx | 2 +- .../progress-tracker/progress-tracker.spec.tsx | 2 +- src/components/text-editor/text-editor.spec.tsx | 2 +- src/components/tooltip/tooltip.stories.mdx | 2 +- src/components/tooltip/tooltip.stories.tsx | 2 +- .../useCharacterCount/useCharacterCount.spec.tsx | 4 ++-- 24 files changed, 52 insertions(+), 50 deletions(-) diff --git a/cypress/components/flat-table/flat-table.cy.tsx b/cypress/components/flat-table/flat-table.cy.tsx index ace2d473ad..b729fca4f8 100644 --- a/cypress/components/flat-table/flat-table.cy.tsx +++ b/cypress/components/flat-table/flat-table.cy.tsx @@ -526,7 +526,7 @@ context("Tests for Flat Table component", () => { it.skip("should render Flat Table with multiple sticky row headers, stickyAlignment set to right", () => { cy.viewport(700, 700); - CypressMountWithProviders(); + CypressMountWithProviders(); flatTableBodyRowByPosition(1) .find("td") @@ -2932,7 +2932,7 @@ context("Tests for Flat Table component", () => { it.skip("should render Flat Table with multiple sticky row headers for accessibility tests", () => { cy.viewport(700, 700); - CypressMountWithProviders(); + CypressMountWithProviders(); cy.checkAccessibility(); }); diff --git a/cypress/components/grouped-character/grouped-character.cy.tsx b/cypress/components/grouped-character/grouped-character.cy.tsx index efbd8f72b3..d8febd130f 100644 --- a/cypress/components/grouped-character/grouped-character.cy.tsx +++ b/cypress/components/grouped-character/grouped-character.cy.tsx @@ -148,7 +148,7 @@ context("Tests for GroupedCharacter component", () => { ["right", "end"], ["left", "start"], ] as [GroupedCharacterProps["labelAlign"], string][])( - "should use %s as labelAligment and render it with %s as css properties", + "should use %s as labelAlignment and render it with %s as css properties", (alignment, cssProp) => { CypressMountWithProviders( diff --git a/cypress/components/menu/menu.cy.tsx b/cypress/components/menu/menu.cy.tsx index 519cf0c60d..dd9854e6ca 100644 --- a/cypress/components/menu/menu.cy.tsx +++ b/cypress/components/menu/menu.cy.tsx @@ -333,12 +333,12 @@ context("Testing Menu component", () => { searchDefaultInput().tab(); searchCrossIcon().parent().should("have.focus"); - const bouding = (element: JQuery) => { + const bounding = (element: JQuery) => { return element[0].getBoundingClientRect(); }; searchCrossIcon() - .then(($el) => bouding($el)) + .then(($el) => bounding($el)) .as("position"); cy.get("@position") @@ -627,7 +627,7 @@ context("Testing Menu component", () => { "center", "flex-start", "flex-end", - ])("should verify Menu alignItmes is %s", (alignment) => { + ])("should verify Menu alignItems is %s", (alignment) => { CypressMountWithProviders(); menu().should("have.css", "align-items", alignment); @@ -1501,7 +1501,7 @@ context("Testing Menu component", () => { "text-top", "top", ])( - "should pass accessibility tests for Menu when alignItmes is %s", + "should pass accessibility tests for Menu when alignItems is %s", (alignment) => { CypressMountWithProviders(); diff --git a/cypress/components/multi-action-button/multi-action-button.cy.tsx b/cypress/components/multi-action-button/multi-action-button.cy.tsx index 94e8225019..0ec9430e69 100644 --- a/cypress/components/multi-action-button/multi-action-button.cy.tsx +++ b/cypress/components/multi-action-button/multi-action-button.cy.tsx @@ -229,7 +229,7 @@ context("Tests for MultiActionButton component", () => { }); }); - describe("user interactions with MultiActionutton", () => { + describe("user interactions with MultiActionButton", () => { describe("pressing ArrowUp while MultiActionButton is open", () => { it("should move focus to previous child button and should not loop to last button when first is focused", () => { CypressMountWithProviders( @@ -448,7 +448,7 @@ context("Tests for MultiActionButton component", () => { }); }); - describe("user interactions with MultiActionutton when wrapping the child buttons in a custom component", () => { + describe("user interactions with MultiActionButton when wrapping the child buttons in a custom component", () => { describe("pressing ArrowUp while MultiActionButton is open", () => { it("should move focus to previous child button and should not loop to last button when first is focused", () => { CypressMountWithProviders( diff --git a/cypress/components/number/number.cy.tsx b/cypress/components/number/number.cy.tsx index c071bb7c9b..33f472cd16 100644 --- a/cypress/components/number/number.cy.tsx +++ b/cypress/components/number/number.cy.tsx @@ -178,7 +178,7 @@ context("Tests for Number component", () => { ["right", "end"], ["left", "start"], ] as [NumberProps["labelAlign"], string][])( - "should use %s as labelAligment and render it with flex-%s as css properties", + "should use %s as labelAlignment and render it with flex-%s as css properties", (alignment, cssProp) => { CypressMountWithProviders( diff --git a/cypress/components/pager/pager.cy.tsx b/cypress/components/pager/pager.cy.tsx index a41fa68ba7..47de7e6729 100644 --- a/cypress/components/pager/pager.cy.tsx +++ b/cypress/components/pager/pager.cy.tsx @@ -323,7 +323,7 @@ context("Test for Pager component", () => { }); }); - describe("check funtionality for Pager component", () => { + describe("check functionality for Pager component", () => { it.each([-1, -10, -100, ...testData])( "should set totalRecords out of scope to %s", (totalRecords) => { @@ -379,14 +379,14 @@ context("Test for Pager component", () => { viewportWidth, showItemsAssertion, firstAndLastArrowsAssertion, - totalRecordsAssetion + totalRecordsAssertion ) => { cy.viewport(viewportWidth, 768); CypressMountWithProviders(); showLabelBefore().should(showItemsAssertion); - pagerSummary().should(totalRecordsAssetion); + pagerSummary().should(totalRecordsAssertion); firstArrow().should(firstAndLastArrowsAssertion); lastArrow().should(firstAndLastArrowsAssertion); nextArrow().should("be.visible"); diff --git a/cypress/components/password/password.cy.tsx b/cypress/components/password/password.cy.tsx index 0bd2de5821..9b5a8a0b08 100644 --- a/cypress/components/password/password.cy.tsx +++ b/cypress/components/password/password.cy.tsx @@ -305,7 +305,7 @@ context("Tests for Password component", () => { ["right", "end"], ["left", "start"], ] as [PasswordProps["labelAlign"], string][])( - "should use %s as labelAligment and render it with %s as css properties", + "should use %s as labelAlignment and render it with %s as css properties", (alignment, cssProp) => { CypressMountWithProviders( diff --git a/cypress/components/switch/switch.cy.tsx b/cypress/components/switch/switch.cy.tsx index 2934318169..5ce15ffbfd 100644 --- a/cypress/components/switch/switch.cy.tsx +++ b/cypress/components/switch/switch.cy.tsx @@ -314,7 +314,7 @@ context("Testing Switch component", () => { ["warning", VALIDATION.WARNING], ["info", VALIDATION.INFO], ])( - "verify Switch component is verifyed with appropriate border color for validations", + "verify Switch component is verified with appropriate border color for validations", (type, validation) => { CypressMountWithProviders(); diff --git a/cypress/components/tooltip/tooltip.cy.tsx b/cypress/components/tooltip/tooltip.cy.tsx index 8cea855472..b4c876db14 100644 --- a/cypress/components/tooltip/tooltip.cy.tsx +++ b/cypress/components/tooltip/tooltip.cy.tsx @@ -208,14 +208,14 @@ context("Tests for Tooltip component", () => { }); describe("Accessibility tests for Tooltip component", () => { - it("should pass accessibilty tests for Tooltip Default story", () => { + it("should pass accessibility tests for Tooltip Default story", () => { CypressMountWithProviders(); getDataElementByValue("main-text").click(); cy.checkAccessibility(); }); - it("should pass accessibilty tests for Tooltip Controlled story", () => { + it("should pass accessibility tests for Tooltip Controlled story", () => { CypressMountWithProviders(); getDataElementByValue("main-text").eq(0).click(); @@ -228,7 +228,7 @@ context("Tests for Tooltip component", () => { ["left", 2], ["right", 3], ])( - "should pass accessibilty tests for Tooltip Positioning story %s position", + "should pass accessibility tests for Tooltip Positioning story %s position", (position, button) => { CypressMountWithProviders(); @@ -237,20 +237,20 @@ context("Tests for Tooltip component", () => { } ); - it("should pass accessibilty tests for Tooltip FlipBehviourOverrides story", () => { - CypressMountWithProviders(); + it("should pass accessibility tests for Tooltip FlipBehaviourOverrides story", () => { + CypressMountWithProviders(); cy.checkAccessibility(); }); - it("should pass accessibilty tests for Tooltip LargeTooltip story", () => { + it("should pass accessibility tests for Tooltip LargeTooltip story", () => { CypressMountWithProviders(); getDataElementByValue("main-text").click(); cy.checkAccessibility(); }); - it("should pass accessibilty tests for Tooltip Types story", () => { + it("should pass accessibility tests for Tooltip Types story", () => { CypressMountWithProviders(); getDataElementByValue("main-text").eq(1).click(); @@ -259,7 +259,7 @@ context("Tests for Tooltip component", () => { cy.checkAccessibility(); }); - it("should pass accessibilty tests for Tooltip ColorOverrides story", () => { + it("should pass accessibility tests for Tooltip ColorOverrides story", () => { CypressMountWithProviders(); getDataElementByValue("main-text").click(); diff --git a/cypress/components/vertical-menu/vertical-menu.cy.tsx b/cypress/components/vertical-menu/vertical-menu.cy.tsx index 373ea6e42c..b756ae3961 100644 --- a/cypress/components/vertical-menu/vertical-menu.cy.tsx +++ b/cypress/components/vertical-menu/vertical-menu.cy.tsx @@ -487,20 +487,20 @@ context("Testing Vertical Menu component", () => { }); describe("should check the accessibility tests", () => { - it("should check accessiblity for verticalMenuComponent", () => { + it("should check accessibility for verticalMenuComponent", () => { CypressMountWithProviders(); cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent open", () => { + it("should check accessibility for verticalMenuComponent open", () => { CypressMountWithProviders(); verticalMenuItem().tab().tab().click(); cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent Active", () => { + it("should check accessibility for verticalMenuComponent Active", () => { CypressMountWithProviders( !isOpen} /> ); @@ -508,32 +508,32 @@ context("Testing Vertical Menu component", () => { cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent Adornment", () => { + it("should check accessibility for verticalMenuComponent Adornment", () => { CypressMountWithProviders(); cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent CustomItemHeight", () => { + it("should check accessibility for verticalMenuComponent CustomItemHeight", () => { CypressMountWithProviders(); cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent CustomItemPadding", () => { + it("should check accessibility for verticalMenuComponent CustomItemPadding", () => { CypressMountWithProviders(); cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent FullScreen", () => { + it("should check accessibility for verticalMenuComponent FullScreen", () => { cy.viewport(320, 599); CypressMountWithProviders(); cy.checkAccessibility(); }); - it("should check accessiblity for verticalMenuComponent FullScreen open", () => { + it("should check accessibility for verticalMenuComponent FullScreen open", () => { cy.viewport(320, 599); CypressMountWithProviders(); diff --git a/playwright/components/step-sequence/index.ts b/playwright/components/step-sequence/index.ts index 671aa7bb5b..3607b1b76b 100644 --- a/playwright/components/step-sequence/index.ts +++ b/playwright/components/step-sequence/index.ts @@ -11,6 +11,6 @@ export const stepSequenceItemIndicator = (page: Page) => export const stepSequenceDataComponentItem = (page: Page) => page.locator(STEP_SEQUENCE_DATA_COMPONENT_ITEM); - + export const stepSequenceDataComponent = (page: Page) => page.locator(STEP_SEQUENCE_DATA_COMPONENT); diff --git a/src/components/action-popover/action-popover.pw.tsx b/src/components/action-popover/action-popover.pw.tsx index a1d14c8cbd..639a93d0b0 100644 --- a/src/components/action-popover/action-popover.pw.tsx +++ b/src/components/action-popover/action-popover.pw.tsx @@ -38,8 +38,8 @@ import { ActionPopoverComponentIcons, ActionPopoverComponentInFlatTable, ActionPopoverComponentInOverflowHiddenContainer, - ActionPopoverComponentKeyboardNaviationLeftAlignedSubmenu, - ActionPopoverComponentKeyboardNaviationRightAlignedSubmenu, + ActionPopoverComponentKeyboardNavigationLeftAlignedSubmenu, + ActionPopoverComponentKeyboardNavigationRightAlignedSubmenu, ActionPopoverComponentKeyboardNavigation, ActionPopoverComponentMenuOpeningAbove, ActionPopoverComponentMenuRightAligned, @@ -1272,7 +1272,7 @@ test.describe("Accessibility tests for ActionPopover", () => { mount, page, }) => { - await mount(); + await mount(); const actionPopoverButtonElement = await actionPopoverButton(page).nth(0); await actionPopoverButtonElement.click(); const submenuTrigger = await actionPopoverInnerItem(page, 0); @@ -1284,7 +1284,9 @@ test.describe("Accessibility tests for ActionPopover", () => { mount, page, }) => { - await mount(); + await mount( + + ); const actionPopoverButtonElement = await actionPopoverButton(page).nth(0); await actionPopoverButtonElement.click(); const submenuTrigger = await actionPopoverInnerItem(page, 0); diff --git a/src/components/action-popover/action-popover.stories.tsx b/src/components/action-popover/action-popover.stories.tsx index fb4bbfb35c..bfe39d6cf6 100644 --- a/src/components/action-popover/action-popover.stories.tsx +++ b/src/components/action-popover/action-popover.stories.tsx @@ -384,7 +384,7 @@ export const ActionPopoverComponentKeyboardNavigation: ComponentStory< ); }; -export const ActionPopoverComponentKeyboardNaviationLeftAlignedSubmenu: ComponentStory< +export const ActionPopoverComponentKeyboardNavigationLeftAlignedSubmenu: ComponentStory< typeof ActionPopover > = () => { return ( @@ -433,7 +433,7 @@ export const ActionPopoverComponentKeyboardNaviationLeftAlignedSubmenu: Componen ); }; -export const ActionPopoverComponentKeyboardNaviationRightAlignedSubmenu: ComponentStory< +export const ActionPopoverComponentKeyboardNavigationRightAlignedSubmenu: ComponentStory< typeof ActionPopover > = () => { return ( diff --git a/src/components/button-bar/button-bar.spec.tsx b/src/components/button-bar/button-bar.spec.tsx index 668004b6bf..0591c6083b 100644 --- a/src/components/button-bar/button-bar.spec.tsx +++ b/src/components/button-bar/button-bar.spec.tsx @@ -47,7 +47,7 @@ describe("Button Bar", () => { }); }); - describe("when props are passed to the compontent", () => { + describe("when props are passed to the component", () => { it("renders proper props and children", () => { const wrapper = renderButtonBar("Large", 3, { size: "large", diff --git a/src/components/button-minor/button-minor.pw.tsx b/src/components/button-minor/button-minor.pw.tsx index 17f47d9a0c..da504e4683 100644 --- a/src/components/button-minor/button-minor.pw.tsx +++ b/src/components/button-minor/button-minor.pw.tsx @@ -541,7 +541,7 @@ test.describe("accessibility tests", () => { await checkAccessibility(page); }); - test("should check accessibility for secondary destrictive Button Minor", async ({ + test("should check accessibility for secondary destructive Button Minor", async ({ mount, page, }) => { diff --git a/src/components/button-toggle/button-toggle.spec.tsx b/src/components/button-toggle/button-toggle.spec.tsx index 061990a31f..0b3c4d8f2b 100644 --- a/src/components/button-toggle/button-toggle.spec.tsx +++ b/src/components/button-toggle/button-toggle.spec.tsx @@ -317,7 +317,7 @@ describe("ButtonToggle", () => {
); - // Uses snapshot as jest/enzyme doesnt support :first-of-type + // Uses snapshot as jest/enzyme doesn't support :first-of-type expect(wrapper).toMatchSnapshot(); }); }); diff --git a/src/components/flat-table/flat-table-test.stories.tsx b/src/components/flat-table/flat-table-test.stories.tsx index 30f1304b15..92518beab2 100644 --- a/src/components/flat-table/flat-table-test.stories.tsx +++ b/src/components/flat-table/flat-table-test.stories.tsx @@ -893,7 +893,7 @@ export const FlatTableCellRowSpanComponent = ( ); }; -export const FlatTableMutipleStickyComponent = ( +export const FlatTableMultipleStickyComponent = ( props: Partial ) => { return ( diff --git a/src/components/loader/loader.pw.tsx b/src/components/loader/loader.pw.tsx index 712b1b7703..2fb884d7cb 100644 --- a/src/components/loader/loader.pw.tsx +++ b/src/components/loader/loader.pw.tsx @@ -182,7 +182,7 @@ test.describe("check props for Loader component test", () => { }); test.describe("Accessibility tests for Loader component", async () => { - test("should pass accessibilty tests for Loader default story", async ({ + test("should pass accessibility tests for Loader default story", async ({ mount, page, }) => { diff --git a/src/components/menu/menu.spec.tsx b/src/components/menu/menu.spec.tsx index b79d88ef8b..f14378756a 100644 --- a/src/components/menu/menu.spec.tsx +++ b/src/components/menu/menu.spec.tsx @@ -197,7 +197,7 @@ describe("Menu", () => { }); describe("with multiple submenus", () => { - it("when a sumenu is opened, any previously open submenu is closed", () => { + it("when a submenu is opened, any previously open submenu is closed", () => { wrapper = mount( menu item diff --git a/src/components/progress-tracker/progress-tracker.spec.tsx b/src/components/progress-tracker/progress-tracker.spec.tsx index ae344cde9b..cc3f26015e 100644 --- a/src/components/progress-tracker/progress-tracker.spec.tsx +++ b/src/components/progress-tracker/progress-tracker.spec.tsx @@ -246,7 +246,7 @@ describe("ProgressTracker", () => { }); }); - describe("get a correct background of inner and outter bar color, when progress is 100 or the error occurs", () => { + describe("get a correct background of inner and outer bar color, when progress is 100 or the error occurs", () => { it("applies correct background color if progress is 100", () => { wrapper = mount(); assertStyleMatch( diff --git a/src/components/text-editor/text-editor.spec.tsx b/src/components/text-editor/text-editor.spec.tsx index e5f7bc09e1..1806c70877 100644 --- a/src/components/text-editor/text-editor.spec.tsx +++ b/src/components/text-editor/text-editor.spec.tsx @@ -1188,7 +1188,7 @@ describe("TextEditor", () => { ); }); - it("applies error styling when hasError is true and focusRedesignOptOut and isForcused are also true", () => { + it("applies error styling when hasError is true and focusRedesignOptOut and isFocused are also true", () => { const focusRedesignWrapper = mount( diff --git a/src/components/tooltip/tooltip.stories.mdx b/src/components/tooltip/tooltip.stories.mdx index d7c0375075..b3c7e8d183 100644 --- a/src/components/tooltip/tooltip.stories.mdx +++ b/src/components/tooltip/tooltip.stories.mdx @@ -78,7 +78,7 @@ Tooltip "bottom" intitially, then flip to "right" when there is not enough room when there is no space to render to the right anymore. - + ### Large tooltip diff --git a/src/components/tooltip/tooltip.stories.tsx b/src/components/tooltip/tooltip.stories.tsx index a223094c01..74921f3319 100644 --- a/src/components/tooltip/tooltip.stories.tsx +++ b/src/components/tooltip/tooltip.stories.tsx @@ -73,7 +73,7 @@ export const Positioning = () => { ); }; -export const FlipBehviourOverrides = () => { +export const FlipBehaviourOverrides = () => { const Component = forwardRef( ({ children }: ButtonProps, ref) => ( + setIsDialogOpen(false)} + title="Duelling Picklist" + size="large" + > + + + + ); +}; + +export const AddItem = () => ( +
    + null}> +
    +
    +

    + Title for Item +

    +
    +
    +
    +
+); + +export const RemoveItem = () => ( +
    + null}> +
    +
    +

    + Title for Item +

    +
    +
    +
    +
+); + +export const Locked = () => ( +
    + null} locked> +
    +
    +

    + Title for Item +

    +
    +
    +
    +
+); + +export const CustomTooltipMessage = () => ( +
    + null} + locked + tooltipMessage="This is a custom locked tooltip message" + > +
    +
    +

    + Title for Item +

    +
    +
    +
    +
+); diff --git a/src/components/duelling-picklist/duelling-picklist-test.stories.tsx b/src/components/duelling-picklist/duelling-picklist-test.stories.tsx index d8c4f1576e..2375393d95 100644 --- a/src/components/duelling-picklist/duelling-picklist-test.stories.tsx +++ b/src/components/duelling-picklist/duelling-picklist-test.stories.tsx @@ -8,8 +8,6 @@ import { PicklistItemProps, PicklistDivider, PicklistPlaceholder, - DuellingPicklistProps, - PicklistProps, } from "."; import Search from "../search"; import { Checkbox } from "../checkbox"; @@ -175,396 +173,3 @@ export const Default = () => { }; Default.storyName = "default"; - -export const DuellingPicklistComponent = ( - props: Partial -) => { - const mockData: Item[] = useMemo(() => { - const arr = []; - for (let i = 0; i < 10; i++) { - const data = { - key: i.toString(), - title: `Content ${i + 1}`, - description: `Description ${i + 1}`, - }; - arr.push(data); - } - return arr; - }, []); - - const allItems = useMemo(() => { - return mockData.reduce((obj, item) => { - obj[item.key] = item; - return obj; - }, {} as { [key: string]: Item }); - }, [mockData]); - - const [isEachItemSelected] = useState(false); - const [order] = useState(mockData.map(({ key }) => key)); - const [notSelectedItems, setNotSelectedItems] = useState(allItems); - const [notSelectedSearch, setNotSelectedSearch] = useState({}); - const [selectedItems, setSelectedItems] = useState({}); - const [searchQuery, setSearchQuery] = useState(""); - const isSearchMode = Boolean(searchQuery.length); - - const onAdd = useCallback( - (item) => { - const { [item.key]: removed, ...rest } = notSelectedItems; - setNotSelectedItems(rest); - setSelectedItems({ ...selectedItems, [item.key]: item }); - const { [item.key]: removed2, ...rest2 } = notSelectedSearch; - setNotSelectedSearch(rest2); - }, - [notSelectedItems, notSelectedSearch, selectedItems] - ); - - const onRemove = useCallback( - (item) => { - const { [item.key]: removed, ...rest } = selectedItems; - setSelectedItems(rest); - setNotSelectedItems({ ...notSelectedItems, [item.key]: item }); - if (isSearchMode && item.title.includes(searchQuery)) { - setNotSelectedSearch({ ...notSelectedSearch, [item.key]: item }); - } - }, - [ - isSearchMode, - notSelectedItems, - notSelectedSearch, - searchQuery, - selectedItems, - ] - ); - - const handleSearch = useCallback( - (ev) => { - setSearchQuery(ev.target.value); - const tempNotSelectedItems = Object.keys(notSelectedItems).reduce( - (items, key) => { - const item = notSelectedItems[key]; - if (item.title.includes(ev.target.value)) { - items[item.key] = item; - } - return items; - }, - {} as AllItems - ); - setNotSelectedSearch(tempNotSelectedItems); - }, - [notSelectedItems] - ); - - const renderItems = ( - list: AllItems, - type: PicklistItemProps["type"], - handler: PicklistItemProps["onChange"] - ) => - order.reduce((items, key) => { - const item = list[key]; - if (item) { - items.push( - -
-
-

- {item.title} -

-
-
-

{item.description}

-
-
-
- ); - } - return items; - }, [] as JSX.Element[]); - - return ( -
- - } - rightControls={ - - } - {...props} - > - } - > - {renderItems( - isSearchMode ? notSelectedSearch : notSelectedItems, - "add", - onAdd - )} - - } - > - {renderItems(selectedItems, "remove", onRemove)} - - -
- ); -}; - -export const DuellingPicklistComponentPicklistItemProps = ( - props: Partial -) => { - const mockData: Item[] = useMemo(() => { - const arr = []; - for (let i = 0; i < 10; i++) { - const data = { - key: i.toString(), - title: `Content ${i + 1}`, - description: `Description ${i + 1}`, - }; - arr.push(data); - } - return arr; - }, []); - - const allItems = useMemo(() => { - return mockData.reduce((obj, item) => { - obj[item.key] = item; - return obj; - }, {} as { [key: string]: Item }); - }, [mockData]); - - const [isEachItemSelected] = useState(false); - const [order] = useState(mockData.map(({ key }) => key)); - const [notSelectedItems, setNotSelectedItems] = useState(allItems); - const [notSelectedSearch, setNotSelectedSearch] = useState({}); - const [selectedItems, setSelectedItems] = useState({}); - const [searchQuery] = useState(""); - const isSearchMode = Boolean(searchQuery.length); - - const onAdd = useCallback( - (item) => { - const { [item.key]: removed, ...rest } = notSelectedItems; - setNotSelectedItems(rest); - setSelectedItems({ ...selectedItems, [item.key]: item }); - const { [item.key]: removed2, ...rest2 } = notSelectedSearch; - setNotSelectedSearch(rest2); - }, - [notSelectedItems, notSelectedSearch, selectedItems] - ); - - const onRemove = React.useCallback( - (item) => { - const { [item.key]: removed, ...rest } = selectedItems; - setSelectedItems(rest); - setNotSelectedItems({ ...notSelectedItems, [item.key]: item }); - if (isSearchMode && item.title.includes(searchQuery)) { - setNotSelectedSearch({ ...notSelectedSearch, [item.key]: item }); - } - }, - [ - isSearchMode, - notSelectedItems, - notSelectedSearch, - searchQuery, - selectedItems, - ] - ); - const renderItems = ( - list: AllItems, - type: PicklistItemProps["type"], - handler: PicklistItemProps["onChange"] - ) => - order.reduce((items, key) => { - const item = list[key]; - if (item) { - items.push( - -
-
-

- {item.title} -

-
-
-

{item.description}

-
-
-
- ); - } - return items; - }, [] as JSX.Element[]); - return ( -
- - } - {...props} - > - {renderItems( - isSearchMode ? notSelectedSearch : notSelectedItems, - "add", - onAdd - )} - - } - > - {renderItems( - isSearchMode ? notSelectedSearch : notSelectedItems, - "remove", - onRemove - )} - - -
- ); -}; - -export const DuellingPicklistComponentPicklistProps = ( - props: Partial -) => { - const mockData: Item[] = useMemo(() => { - const arr = []; - for (let i = 0; i < 10; i++) { - const data = { - key: i.toString(), - title: `Content ${i + 1}`, - description: `Description ${i + 1}`, - }; - arr.push(data); - } - return arr; - }, []); - - const allItems = useMemo(() => { - return mockData.reduce((obj, item) => { - obj[item.key] = item; - return obj; - }, {} as { [key: string]: Item }); - }, [mockData]); - - const [isEachItemSelected] = useState(false); - const [order] = useState(mockData.map(({ key }) => key)); - const [notSelectedItems, setNotSelectedItems] = useState(allItems); - const [notSelectedSearch, setNotSelectedSearch] = useState({}); - const [selectedItems, setSelectedItems] = useState({}); - const [searchQuery] = useState(""); - const isSearchMode = Boolean(searchQuery.length); - - const onAdd = useCallback( - (item) => { - const { [item.key]: removed, ...rest } = notSelectedItems; - setNotSelectedItems(rest); - setSelectedItems({ ...selectedItems, [item.key]: item }); - const { [item.key]: removed2, ...rest2 } = notSelectedSearch; - setNotSelectedSearch(rest2); - }, - [notSelectedItems, notSelectedSearch, selectedItems] - ); - - const onRemove = React.useCallback( - (item) => { - const { [item.key]: removed, ...rest } = selectedItems; - setSelectedItems(rest); - setNotSelectedItems({ ...notSelectedItems, [item.key]: item }); - if (isSearchMode && item.title.includes(searchQuery)) { - setNotSelectedSearch({ ...notSelectedSearch, [item.key]: item }); - } - }, - [ - isSearchMode, - notSelectedItems, - notSelectedSearch, - searchQuery, - selectedItems, - ] - ); - const renderItems = ( - list: AllItems, - type: PicklistItemProps["type"], - handler: PicklistItemProps["onChange"] - ) => - order.reduce((items, key) => { - const item = list[key]; - if (item) { - items.push( - -
-
-

- {item.title} -

-
-
-

{item.description}

-
-
-
- ); - } - return items; - }, [] as JSX.Element[]); - return ( -
- - } - {...props} - > - {renderItems( - isSearchMode ? notSelectedSearch : notSelectedItems, - "add", - onAdd - )} - - } - > - {renderItems( - isSearchMode ? notSelectedSearch : notSelectedItems, - "remove", - onRemove - )} - - -
- ); -}; diff --git a/src/components/duelling-picklist/duelling-picklist.pw.tsx b/src/components/duelling-picklist/duelling-picklist.pw.tsx new file mode 100644 index 0000000000..75c389a315 --- /dev/null +++ b/src/components/duelling-picklist/duelling-picklist.pw.tsx @@ -0,0 +1,743 @@ +import React from "react"; +import { test, expect } from "@playwright/experimental-ct-react17"; +import { PicklistItemProps } from "./picklist-item/picklist-item.component"; +import { + DuellingPicklistComponent, + DuellingPicklistComponentAssigned, + AlternativeSearch, + Grouped, + InDialog, + AddItem, + RemoveItem, + Locked, + CustomTooltipMessage, +} from "./components.test-pw"; +import { checkAccessibility } from "../../../playwright/support/helper"; +import { + getComponent, + getDataElementByValue, + tooltipPreview, +} from "../../../playwright/components"; +import { CHARACTERS } from "../../../playwright/support/constants"; +import { ICON } from "../../../playwright/components/locators"; +import { DuellingPicklistProps } from "./duelling-picklist.component"; + +const specialCharacters = [ + CHARACTERS.STANDARD, + CHARACTERS.DIACRITICS, + CHARACTERS.SPECIALCHARACTERS, +]; +const keyToTrigger = ["Space", "Enter"] as const; + +test.describe(`should render Duelling-Picklist component`, () => { + test(`should verify unassigned picklist has 10 items`, async ({ + mount, + page, + }) => { + await mount(); + + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(10); + await expect(getDataElementByValue(page, "picklist-left-label")).toHaveText( + "List 1 (10)" + ); + }); + + test(`should verify assigned picklist has 0 items`, async ({ + mount, + page, + }) => { + await mount(); + + await expect( + getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .filter({ hasText: "Content" }) + ).toHaveCount(0); + await expect( + getDataElementByValue(page, "picklist-placeholder") + ).toHaveText("Nothing to see here"); + await expect( + getDataElementByValue(page, "picklist-right-label").nth(0) + ).toHaveText("List 2 (0)"); + }); + + test(`should verify component is enabled by default`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getComponent(page, "duelling-picklist")).not.toHaveAttribute( + "disabled", + /.*/ + ); + }); + + [ + [1, 9], + [7, 3], + ].forEach(([items, leftItems]) => { + test(`should verify when ${items} item(s) are assigned that unassigned picklist has ${leftItems} items and assigned picklist has ${items} item(s)`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + for (let i = 0; i < items; i++) { + await addItemButton.click(); + } + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(leftItems); + await expect( + getDataElementByValue(page, "picklist-left-label").nth(0) + ).toHaveText(`List 1 (${leftItems})`); + await expect( + getDataElementByValue(page, "picklist").nth(1).locator("li") + ).toHaveCount(items); + await expect( + getDataElementByValue(page, "picklist-right-label").nth(0) + ).toHaveText(`List 2 (${items})`); + }); + }); + + test(`should verify assigned picklist has 10 items when all items are added`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + for (let i = 0; i < 10; i++) { + await addItemButton.click(); + } + await expect( + getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .filter({ hasText: "Content" }) + ).toHaveCount(0); + await expect( + getDataElementByValue(page, "picklist-placeholder") + ).toHaveText("Unassigned list empty"); + await expect( + getDataElementByValue(page, "picklist-left-label").nth(0) + ).toHaveText(`List 1 (0)`); + await expect( + getDataElementByValue(page, "picklist").nth(1).locator("li") + ).toHaveCount(10); + await expect( + getDataElementByValue(page, "picklist-right-label").nth(0) + ).toHaveText(`List 2 (10)`); + }); + + test(`should verify assigned picklist has 0 items when assigned item is removed`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + await addItemButton.click(); + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(9); + await expect( + getDataElementByValue(page, "picklist").nth(1).locator("li") + ).toHaveCount(1); + + const removeItemButton = getDataElementByValue(page, "remove"); + await removeItemButton.click(); + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(10); + await expect( + getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .filter({ hasText: "Content" }) + ).toHaveCount(0); + }); + + [...keyToTrigger].forEach((pressed) => { + test(`should verify item is added to assigned picklist when ${pressed} key is pressed`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + await addItemButton.press(pressed); + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(9); + await expect( + getDataElementByValue(page, "picklist").nth(1).locator("li") + ).toHaveCount(1); + }); + }); + + [...keyToTrigger].forEach((pressed) => { + test(`should verify item is removed from assigned picklist when ${pressed} key is pressed`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + for (let i = 0; i < 10; i++) { + await addItemButton.click(); + } + await expect( + getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .filter({ hasText: "Content" }) + ).toHaveCount(0); + await expect( + getDataElementByValue(page, "picklist").nth(1).locator("li") + ).toHaveCount(10); + + const removeItemButton = getDataElementByValue(page, "remove").first(); + for (let i = 0; i < 10; i++) { + await removeItemButton.press(pressed); + } + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(10); + await expect( + getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .filter({ hasText: "Content" }) + ).toHaveCount(0); + }); + }); + + ([ + ["Content", 10], + ["Content 1", 2], + ["Content 10", 1], + ] as [string, number][]).forEach(([searchString, results]) => { + test(`should verify when ${searchString} is enterted into search field that ${results} results are displayed`, async ({ + mount, + page, + }) => { + await mount(); + + const searchInput = page.getByLabel("search").first(); + await searchInput.fill(searchString); + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(results); + }); + }); + + test(`should verify leftControl prop in component generates search field appears above unassigned picklist`, async ({ + mount, + page, + }) => { + await mount(); + + const leftSearch = getDataElementByValue(page, "picklist-left-control") + .locator("div") + .nth(0); + await expect(leftSearch).toHaveAttribute("data-component", "search"); + }); + + test(`should verify rightControl prop in component generates search field appears above assigned picklist`, async ({ + mount, + page, + }) => { + await mount(); + + const rightSearch = getDataElementByValue(page, "picklist-right-label") + .locator("div") + .nth(0); + await expect(rightSearch).toHaveAttribute("data-component", "search"); + }); + + ([ + ["disabled", true], + ["enabled", false], + ] as [string, DuellingPicklistProps["disabled"]][]).forEach( + ([state, bool]) => { + test(`should verify Duelling-Picklist is ${state} when disabled prop is ${bool}`, async ({ + mount, + page, + }) => { + await mount(); + + if (bool) { + await expect(getComponent(page, "duelling-picklist")).toHaveAttribute( + "disabled", + /.*/ + ); + } else { + await expect( + getComponent(page, "duelling-picklist") + ).not.toHaveAttribute("disabled", /.*/); + } + }); + } + ); + + test(`should verify unassigned picklist label is 'Left Label'`, async ({ + mount, + page, + }) => { + await mount(); + + await expect(getDataElementByValue(page, "picklist-left-label")).toHaveText( + "Left Label" + ); + }); + + test(`should verify assigned picklist label is 'Right Label'`, async ({ + mount, + page, + }) => { + await mount(); + + await expect( + getDataElementByValue(page, "picklist-right-label").nth(0) + ).toHaveText("Right Label"); + }); +}); + +test.describe(`should render Duelling-Picklist to test Picklist props`, () => { + [...specialCharacters].forEach((chars) => { + test(`should verify picklist placeholder is set to ${chars}`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + for (let i = 0; i < 10; i++) { + await addItemButton.click(); + } + + await expect( + getDataElementByValue(page, "picklist-placeholder") + ).toHaveText(chars); + }); + }); + + ([ + ["locked", true, "rgb(242, 245, 246)"], + ["unlocked", false, "rgb(255, 255, 255)"], + ] as [string, PicklistItemProps["locked"], string][]).forEach( + ([state, bool, backColor]) => { + test(`should verify picklist item is ${state} when locked prop is ${bool}`, async ({ + mount, + page, + }) => { + await mount(); + + const picklistItem = getDataElementByValue( + page, + "picklist-item" + ).first(); + await expect(picklistItem).toHaveCSS("background-color", backColor); + const picklistIcon = getDataElementByValue(page, "picklist-item") + .first() + .locator(ICON); + if (bool) { + await expect(picklistIcon).toHaveAttribute("data-element", state); + } else { + await expect(picklistIcon).not.toHaveAttribute("data-element", state); + } + }); + } + ); + + test(`should verify picklist tooltip is 'Item Locked' when locked prop is true`, async ({ + mount, + page, + }) => { + await mount( + + ); + + const listItemIcon = getDataElementByValue(page, "picklist-item") + .first() + .locator(ICON); + await listItemIcon.hover(); + await expect(tooltipPreview(page)).toHaveText("Item Locked"); + }); +}); + +test.describe( + `should render Duelling-Picklist with external searchbar and access checkbox`, + () => { + ([ + ["Content", 20], + ["Content 1", 11], + ["Content 10", 1], + ] as [string, number][]).forEach(([searchString, results]) => { + test(`should verify ${results} are found when search field is placed outside the component`, async ({ + mount, + page, + }) => { + await mount(); + + await page.getByLabel("search").fill(searchString); + await expect( + getDataElementByValue(page, "picklist").nth(0).locator("li") + ).toHaveCount(results); + }); + }); + + test(`should verify component is disabled when access checkox is checked`, async ({ + mount, + page, + }) => { + await mount(); + + const checkbox = page.getByRole("checkbox"); + await checkbox.check(); + await expect(getComponent(page, "duelling-picklist")).toHaveAttribute( + "disabled", + /.*/ + ); + }); + + test(`should verify component is re-enabled when access checkbox is unchecked`, async ({ + mount, + page, + }) => { + await mount(); + + const checkbox = page.getByRole("checkbox"); + await checkbox.check(); + await checkbox.uncheck(); + await expect(getComponent(page, "duelling-picklist")).not.toHaveAttribute( + "disabled", + /.*/ + ); + }); + } +); + +test.describe( + `should render Duelling-Picklist with items grouped and a picklist divider`, + () => { + test(`should verify component is displayed with divider`, async ({ + mount, + page, + }) => { + await mount(); + + await expect( + getDataElementByValue(page, "picklist-divider") + ).toBeAttached(); + }); + + test(`should verify component is displayed in groups with group label`, async ({ + mount, + page, + }) => { + await mount(); + + const group = getDataElementByValue(page, "picklist-group").first(); + await expect(group).toHaveText("Group A"); + }); + + test(`should verify all items in a group are added to assigned picklist when group add button is clicked`, async ({ + mount, + page, + }) => { + await mount(); + + const groupAddButton = getDataElementByValue(page, "picklist-group") + .first() + .locator("button"); + await groupAddButton.click(); + const group = getDataElementByValue(page, "picklist-group").nth(2); + await expect(group).toHaveText("Group A"); + await expect( + getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .locator("p") + ).toHaveCount(3); + }); + + test(`should verify all items in a group are removed from assigned picklist when group remove button is clicked`, async ({ + mount, + page, + }) => { + await mount(); + + const groupAddButton = getDataElementByValue(page, "picklist-group") + .first() + .locator("button"); + await groupAddButton.click(); + await expect( + getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .locator("p") + ).toHaveCount(3); + await expect( + getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .locator("p") + ).toHaveCount(3); + const groupRemoveButton = getDataElementByValue(page, "picklist-group") + .nth(2) + .locator("button"); + await groupRemoveButton.click(); + await expect( + getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .locator("p") + ).toHaveCount(6); + await expect( + getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .locator("p") + ).toHaveCount(0); + }); + } +); + +test.describe(`check events for Duelling-Picklist component`, () => { + test(`should call onChange when add button clicked`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + const addItemButton = page.getByRole("button").first(); + await addItemButton.click(); + expect(callbackCount).toBe(1); + }); + + test(`should call onChange when remove button clicked`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + const removeItemButton = page.getByRole("button").first(); + await removeItemButton.click(); + expect(callbackCount).toBe(1); + }); + + [...keyToTrigger].forEach((pressed) => { + test(`should call onChange when ${pressed} key pressed on add button`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + const addItemButton = page.getByRole("button").first(); + await addItemButton.press(pressed); + expect(callbackCount).toBe(1); + }); + }); + + [...keyToTrigger].forEach((pressed) => { + test(`should call onChange when ${pressed} key pressed on remove button`, async ({ + mount, + page, + }) => { + let callbackCount = 0; + await mount( + { + callbackCount += 1; + }} + /> + ); + + const removeItemButton = page.getByRole("button").first(); + await removeItemButton.press(pressed); + expect(callbackCount).toBe(1); + }); + }); +}); + +test.describe(`Accessibility tests for Duelling-Picklist component`, () => { + test(`should pass accessibility tests for default example`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for AlternativeSearch example`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for Grouped example`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for InDialog example`, async ({ + mount, + page, + }) => { + await mount(); + + const dialogButton = getDataElementByValue(page, "main-text"); + await dialogButton.click(); + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for AddItem example`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for RemoveItem example`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for Locked example`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); + + test(`should pass accessibility tests for CustomTooltipMessage example`, async ({ + mount, + page, + }) => { + await mount(); + + const lockedItem = getDataElementByValue(page, "locked").first(); + await lockedItem.hover({ force: true }); + await expect(tooltipPreview(page)).toBeVisible(); + await checkAccessibility(page); + }); + + test.skip(`should pass accessibility tests when disabled`, async ({ + mount, + page, + }) => { + await mount(); + + await checkAccessibility(page); + }); +}); + +test.describe("Border radius tests", () => { + test(`should render the items with the expected border radius styling`, async ({ + mount, + page, + }) => { + await mount(); + + const addItemButton = page.getByRole("button").first(); + for (let i = 0; i < 5; i++) { + await addItemButton.click(); + } + + const assignedItem1 = getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .nth(0); + const assignedItem2 = getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .nth(1); + const assignedItem3 = getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .nth(2); + const assignedItem4 = getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .nth(3); + const assignedItem5 = getDataElementByValue(page, "picklist") + .nth(0) + .locator("li") + .nth(4); + await expect(assignedItem1).toHaveCSS("border-radius", "8px"); + await expect(assignedItem2).toHaveCSS("border-radius", "8px"); + await expect(assignedItem3).toHaveCSS("border-radius", "8px"); + await expect(assignedItem4).toHaveCSS("border-radius", "8px"); + await expect(assignedItem5).toHaveCSS("border-radius", "8px"); + + const unassignedItem1 = getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .nth(0); + const unassignedItem2 = getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .nth(1); + const unassignedItem3 = getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .nth(2); + const unassignedItem4 = getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .nth(3); + const unassignedItem5 = getDataElementByValue(page, "picklist") + .nth(1) + .locator("li") + .nth(4); + await expect(unassignedItem1).toHaveCSS("border-radius", "8px"); + await expect(unassignedItem2).toHaveCSS("border-radius", "8px"); + await expect(unassignedItem3).toHaveCSS("border-radius", "8px"); + await expect(unassignedItem4).toHaveCSS("border-radius", "8px"); + await expect(unassignedItem5).toHaveCSS("border-radius", "8px"); + }); +}); From 437ee03386d82c9d32799efce05ea009cc7feb6f Mon Sep 17 00:00:00 2001 From: DipperTheDan Date: Wed, 1 Nov 2023 15:06:24 +0000 Subject: [PATCH 19/24] fix(action-popover): fix issue with stories not rendering in storybook --- src/components/action-popover/action-popover.stories.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/action-popover/action-popover.stories.mdx b/src/components/action-popover/action-popover.stories.mdx index 992ff7085a..90871ce45c 100644 --- a/src/components/action-popover/action-popover.stories.mdx +++ b/src/components/action-popover/action-popover.stories.mdx @@ -184,7 +184,7 @@ the sub-menu. Pressing the "right" key will return focus back to the main menu a triggering an action. - + ### Keyboard navigation right aligned submenu @@ -196,7 +196,7 @@ is on a item will open a sub-menu if it has one and pressing the "left" key will From 233782ef1a7290e4582ac44a20fa425584570723 Mon Sep 17 00:00:00 2001 From: nuria1110 Date: Wed, 18 Oct 2023 12:06:14 +0100 Subject: [PATCH 20/24] fix(loader-bar): loaderBar height fix fix #5999 --- .../loader-bar/loader-bar-test.stories.tsx | 21 ++++++++++++++++++- src/components/loader-bar/loader-bar.style.ts | 1 - 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/loader-bar/loader-bar-test.stories.tsx b/src/components/loader-bar/loader-bar-test.stories.tsx index 094ec6d2cd..74de0db4d5 100644 --- a/src/components/loader-bar/loader-bar-test.stories.tsx +++ b/src/components/loader-bar/loader-bar-test.stories.tsx @@ -1,10 +1,13 @@ import React from "react"; +import { ComponentStory } from "@storybook/react"; import LoaderBar, { LoaderBarProps } from "."; import { LOADER_BAR_SIZES } from "./loader-bar.config"; +import Box from "../box"; +import Typography from "../typography"; export default { title: "Loader Bar/Test", - includeStories: ["DefaultStory"], + includeStories: ["DefaultStory", "LoaderBarWithMinHeight"], parameters: { info: { disable: true }, chromatic: { @@ -30,3 +33,19 @@ DefaultStory.storyName = "default"; export const LoaderBarComponentTest = (props: LoaderBarProps) => { return ; }; + +export const LoaderBarWithMinHeight: ComponentStory = () => { + return ( + + + Small bar + + + + ); +}; + +LoaderBarWithMinHeight.parameters = { + chromatic: { disableSnapshot: false }, + controls: { disable: true }, +}; diff --git a/src/components/loader-bar/loader-bar.style.ts b/src/components/loader-bar/loader-bar.style.ts index 67a29559c0..1f94e34d2d 100644 --- a/src/components/loader-bar/loader-bar.style.ts +++ b/src/components/loader-bar/loader-bar.style.ts @@ -36,7 +36,6 @@ const innerBarAnimation = keyframes` const StyledLoaderBar = styled.div` ${({ size }) => css` - display: inline-block; border-radius: var(--borderRadius400); height: ${getHeight(size)}; width: 100%; From 6f7c1ff89a0139cb5fdb16ff03bf2d7e4860f857 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 15:42:10 +0000 Subject: [PATCH 21/24] chore(release): 123.2.2 ### [123.2.2](https://github.com/Sage/carbon/compare/v123.2.1...v123.2.2) (2023-11-02) ### Bug Fixes * **action-popover:** fix issue with stories not rendering in storybook ([437ee03](https://github.com/Sage/carbon/commit/437ee03386d82c9d32799efce05ea009cc7feb6f)) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 110102a6be..274fe25104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### [123.2.2](https://github.com/Sage/carbon/compare/v123.2.1...v123.2.2) (2023-11-02) + + +### Bug Fixes + +* **action-popover:** fix issue with stories not rendering in storybook ([437ee03](https://github.com/Sage/carbon/commit/437ee03386d82c9d32799efce05ea009cc7feb6f)) + ### [123.2.1](https://github.com/Sage/carbon/compare/v123.2.0...v123.2.1) (2023-11-01) diff --git a/package-lock.json b/package-lock.json index fafd865754..c9608c1357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbon-react", - "version": "123.2.1", + "version": "123.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "carbon-react", - "version": "123.2.1", + "version": "123.2.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index a6bc5ca603..f3a3bdab5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-react", - "version": "123.2.1", + "version": "123.2.2", "description": "A library of reusable React components for easily building user interfaces.", "files": [ "lib", From f230eef4f95b631625ca429461cc8a74d76dc2d6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 16:32:41 +0000 Subject: [PATCH 22/24] chore(release): 123.3.0 ## [123.3.0](https://github.com/Sage/carbon/compare/v123.2.2...v123.3.0) (2023-11-02) ### Features * **message:** remove role of status from component ([f09ef5e](https://github.com/Sage/carbon/commit/f09ef5ecf76ae1551a6e01dac1c45be56583df2b)), closes [#6013](https://github.com/Sage/carbon/issues/6013) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 274fe25104..edd3ca53b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [123.3.0](https://github.com/Sage/carbon/compare/v123.2.2...v123.3.0) (2023-11-02) + + +### Features + +* **message:** remove role of status from component ([f09ef5e](https://github.com/Sage/carbon/commit/f09ef5ecf76ae1551a6e01dac1c45be56583df2b)), closes [#6013](https://github.com/Sage/carbon/issues/6013) + ### [123.2.2](https://github.com/Sage/carbon/compare/v123.2.1...v123.2.2) (2023-11-02) diff --git a/package-lock.json b/package-lock.json index c9608c1357..b4d3281e6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbon-react", - "version": "123.2.2", + "version": "123.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "carbon-react", - "version": "123.2.2", + "version": "123.3.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index f3a3bdab5d..61436d87be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-react", - "version": "123.2.2", + "version": "123.3.0", "description": "A library of reusable React components for easily building user interfaces.", "files": [ "lib", From 8fcc359b9a98321ee8dfd21222094e9448996487 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 17:20:09 +0000 Subject: [PATCH 23/24] chore(release): 123.4.0 ## [123.4.0](https://github.com/Sage/carbon/compare/v123.3.0...v123.4.0) (2023-11-02) ### Features * **date:** add support for keyboard navigation in picker ([11d1853](https://github.com/Sage/carbon/commit/11d185306a6cf30e67e1efcf31c7985366594b78)), closes [#6324](https://github.com/Sage/carbon/issues/6324) [#3969](https://github.com/Sage/carbon/issues/3969) ### Bug Fixes * **date:** add aria labels to picker navigation buttons and fix other accessibility issues ([b5eb150](https://github.com/Sage/carbon/commit/b5eb150bcc5760f548db60610f70c5ccd6a7db18)), closes [#5804](https://github.com/Sage/carbon/issues/5804) --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edd3ca53b7..031aa82ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [123.4.0](https://github.com/Sage/carbon/compare/v123.3.0...v123.4.0) (2023-11-02) + + +### Features + +* **date:** add support for keyboard navigation in picker ([11d1853](https://github.com/Sage/carbon/commit/11d185306a6cf30e67e1efcf31c7985366594b78)), closes [#6324](https://github.com/Sage/carbon/issues/6324) [#3969](https://github.com/Sage/carbon/issues/3969) + + +### Bug Fixes + +* **date:** add aria labels to picker navigation buttons and fix other accessibility issues ([b5eb150](https://github.com/Sage/carbon/commit/b5eb150bcc5760f548db60610f70c5ccd6a7db18)), closes [#5804](https://github.com/Sage/carbon/issues/5804) + ## [123.3.0](https://github.com/Sage/carbon/compare/v123.2.2...v123.3.0) (2023-11-02) diff --git a/package-lock.json b/package-lock.json index b4d3281e6b..6135cd5e69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbon-react", - "version": "123.3.0", + "version": "123.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "carbon-react", - "version": "123.3.0", + "version": "123.4.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 61436d87be..dbf7906ea5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-react", - "version": "123.3.0", + "version": "123.4.0", "description": "A library of reusable React components for easily building user interfaces.", "files": [ "lib", From a79fa6b1bf82fdc46d083793d27337c7b3c64bf9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 3 Nov 2023 11:03:15 +0000 Subject: [PATCH 24/24] chore(release): 123.4.1 ### [123.4.1](https://github.com/Sage/carbon/compare/v123.4.0...v123.4.1) (2023-11-03) ### Bug Fixes * **loader-bar:** loaderBar height fix ([233782e](https://github.com/Sage/carbon/commit/233782ef1a7290e4582ac44a20fa425584570723)), closes [#5999](https://github.com/Sage/carbon/issues/5999) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 031aa82ef7..5159afa8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### [123.4.1](https://github.com/Sage/carbon/compare/v123.4.0...v123.4.1) (2023-11-03) + + +### Bug Fixes + +* **loader-bar:** loaderBar height fix ([233782e](https://github.com/Sage/carbon/commit/233782ef1a7290e4582ac44a20fa425584570723)), closes [#5999](https://github.com/Sage/carbon/issues/5999) + ## [123.4.0](https://github.com/Sage/carbon/compare/v123.3.0...v123.4.0) (2023-11-02) diff --git a/package-lock.json b/package-lock.json index 6135cd5e69..ec8dc35759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbon-react", - "version": "123.4.0", + "version": "123.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "carbon-react", - "version": "123.4.0", + "version": "123.4.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index dbf7906ea5..e020731652 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-react", - "version": "123.4.0", + "version": "123.4.1", "description": "A library of reusable React components for easily building user interfaces.", "files": [ "lib",