From cdabe86df383a2403b56fd65d9743811ae7a08b4 Mon Sep 17 00:00:00 2001 From: Michael Winter <36558508+mcwinter07@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:20:18 +1100 Subject: [PATCH] Fix: future Select and FilterSelect page jump on focus issue (#5123) * update overlay and listbox components so focus is manually set * update future select portal story * Add useIsClientReady hook * update FilterSelect tests to increase functional coverage * add a fallback focus and clean up code comments --- .changeset/lemon-tips-look.md | 5 + .../Filter/FilterSelect/FilterSelect.spec.tsx | 99 ++++++++++++++++++- .../src/Filter/FilterSelect/FilterSelect.tsx | 3 + .../src/__future__/Select/Select.spec.tsx | 8 +- .../src/__future__/Select/_docs/Select.mdx | 4 +- .../Select/_docs/Select.stories.tsx | 50 ++++++---- .../Select/subcomponents/ListBox/ListBox.tsx | 60 ++++++++++- .../Select/subcomponents/Overlay/Overlay.tsx | 2 +- .../__utilities__/useIsClientReady/index.ts | 1 + .../useIsClientReady/useIsClientReady.tsx | 17 ++++ 10 files changed, 219 insertions(+), 30 deletions(-) create mode 100644 .changeset/lemon-tips-look.md create mode 100644 packages/components/src/__utilities__/useIsClientReady/index.ts create mode 100644 packages/components/src/__utilities__/useIsClientReady/useIsClientReady.tsx diff --git a/.changeset/lemon-tips-look.md b/.changeset/lemon-tips-look.md new file mode 100644 index 00000000000..aff10c614f6 --- /dev/null +++ b/.changeset/lemon-tips-look.md @@ -0,0 +1,5 @@ +--- +"@kaizen/components": patch +--- + +Fix for the tab focus jump issue on future Select and FilterSelect diff --git a/packages/components/src/Filter/FilterSelect/FilterSelect.spec.tsx b/packages/components/src/Filter/FilterSelect/FilterSelect.spec.tsx index f300b57bcdc..c04e092049b 100644 --- a/packages/components/src/Filter/FilterSelect/FilterSelect.spec.tsx +++ b/packages/components/src/Filter/FilterSelect/FilterSelect.spec.tsx @@ -48,7 +48,7 @@ describe("", () => { it("shows the options initially when isOpen is true", async () => { render() await waitFor(() => { - expect(screen.queryByRole("listbox")).toBeVisible() + expect(screen.getByRole("listbox")).toBeVisible() }) }) @@ -107,10 +107,82 @@ describe("", () => { render() expect(screen.queryByRole("listbox")).toBeVisible() await waitFor(() => { - expect(screen.queryByRole("listbox")).toHaveFocus() + expect(screen.getAllByRole("option")[0]).toHaveFocus() }) }) + it("moves focus to the first item on ArrowDown if nothing has been selected", async () => { + render() + const trigger = screen.getByRole("button", { name: "Coffee" }) + await user.tab() + await waitFor(() => { + expect(trigger).toHaveFocus() + }) + await user.keyboard("{ArrowDown}") + + await waitFor(() => { + expect(screen.getAllByRole("option")[0]).toHaveFocus() + }) + }) + it("moves focus to the last item on ArrowUp if nothing has been selected", async () => { + render() + const trigger = screen.getByRole("button", { name: "Coffee" }) + await user.tab() + await waitFor(() => { + expect(trigger).toHaveFocus() + }) + await user.keyboard("{ArrowUp}") + + await waitFor(() => { + const options = screen.getAllByRole("option") + expect(options[options.length - 1]).toHaveFocus() + }) + }) + it("moves focus to the current selected item on Enter", async () => { + render() + const trigger = screen.getByRole("button", { + name: "Coffee : Hazelnut", + }) + await user.tab() + await waitFor(() => { + expect(trigger).toHaveFocus() + }) + await user.keyboard("{Enter}") + + await waitFor(() => { + expect(screen.getByRole("option", { name: "Hazelnut" })).toHaveFocus() + }) + }) + it("moves focus to the current selected item on ArrowUp", async () => { + render() + const trigger = screen.getByRole("button", { + name: "Coffee : Hazelnut", + }) + await user.tab() + await waitFor(() => { + expect(trigger).toHaveFocus() + }) + await user.keyboard("{ArrowUp}") + + await waitFor(() => { + expect(screen.getByRole("option", { name: "Hazelnut" })).toHaveFocus() + }) + }) + it("moves focus to the current selected item on ArrowDown", async () => { + render() + const trigger = screen.getByRole("button", { + name: "Coffee : Hazelnut", + }) + await user.tab() + await waitFor(() => { + expect(trigger).toHaveFocus() + }) + await user.keyboard("{ArrowDown}") + + await waitFor(() => { + expect(screen.getByRole("option", { name: "Hazelnut" })).toHaveFocus() + }) + }) it("closes when user hits the escape key", async () => { render() expect(screen.queryByRole("listbox")).toBeVisible() @@ -168,6 +240,29 @@ describe("", () => { }) }) +describe("Stringified object values", () => { + it("finds selected option when value is a stringified object", () => { + const { getByRole } = render( + + ) + expect( + getByRole("button", { name: "Coffee : Created by A-Z" }) + ).toBeInTheDocument() + }) +}) + const defaultProps: FilterSelectProps = { label: "Coffee", isOpen: false, diff --git a/packages/components/src/Filter/FilterSelect/FilterSelect.tsx b/packages/components/src/Filter/FilterSelect/FilterSelect.tsx index 33020f0bc73..017e1c45fbe 100644 --- a/packages/components/src/Filter/FilterSelect/FilterSelect.tsx +++ b/packages/components/src/Filter/FilterSelect/FilterSelect.tsx @@ -78,6 +78,9 @@ export const FilterSelect =