From 43b04781ac66d85b25e2849cbb97684c659358c3 Mon Sep 17 00:00:00 2001 From: Cassandra Tam Date: Mon, 5 Jun 2023 17:53:25 +1000 Subject: [PATCH 1/6] feat(filterbar-drp): add FilterBarDateRangePicker --- .../FilterBarDateRangePicker.spec.tsx | 162 ++++++++++++++++++ .../FilterBarDateRangePicker.tsx | 56 ++++++ .../FilterBarDateRangePicker/index.ts | 1 + .../src/FilterBar/subcomponents/index.ts | 1 + packages/components/src/index.ts | 1 + 5 files changed, 221 insertions(+) create mode 100644 packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx create mode 100644 packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx create mode 100644 packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/index.ts diff --git a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx new file mode 100644 index 00000000000..5cbabf79110 --- /dev/null +++ b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx @@ -0,0 +1,162 @@ +import React, { useState } from "react" +import { render, waitFor } from "@testing-library/react" +import userEvent from "@testing-library/user-event" +import { FilterBarProvider } from "~components/FilterBar/context/FilterBarContext" +import { DateRange } from "~components/index" +import { + FilterBarDateRangePicker, + FilterBarDateRangePickerProps, +} from "./FilterBarDateRangePicker" + +const user = userEvent.setup() + +type Values = { + range: DateRange | undefined +} + +const FilterBarDateRangePickerWrapper = ({ + defaultValues, + ...customProps +}: { + defaultValues?: Partial +} & Partial): JSX.Element => { + const [values, setValues] = useState>(defaultValues ?? {}) + return ( + + filters={[ + { + id: "range", + name: "Dates", + Component: ( + + ), + }, + ]} + values={values} + onValuesChange={setValues} + > + {filters => ( + <> + {Object.values(filters).map(({ id, Component }) => ( + {Component} + ))} + + )} + + ) +} + +describe("", () => { + it("shows the name in the trigger button", () => { + const { getByRole } = render() + const triggerButton = getByRole("button", { name: "Dates" }) + expect(triggerButton).toBeInTheDocument() + }) + + it("can toggle its open state", async () => { + const { getByRole, queryByRole } = render( + + ) + const triggerButton = getByRole("button", { name: "Dates" }) + + await user.click(triggerButton) + await waitFor(() => { + const popover = getByRole("dialog") + expect(popover).toBeInTheDocument() + }) + + await user.click(document.body) + await waitFor(() => { + const popover = queryByRole("dialog") + expect(popover).not.toBeInTheDocument() + }) + }) + + it("shows a selected value when provided", () => { + const { getByRole } = render( + + ) + const triggerButton = getByRole("button", { + name: "Dates : 1 May 2022 - 25 Nov 2022", + }) + expect(triggerButton).toBeInTheDocument() + }) + + it("updates the selected value in the trigger button", async () => { + const { getByRole, getByText } = render( + + ) + const triggerButton = getByRole("button", { + name: "Dates : 1 May 2022 - 20 Jun 2022", + }) + + await user.click(triggerButton) + await waitFor(() => { + expect(getByText("May 2022")).toBeVisible() + }) + + const targetDay = getByRole("button", { + name: "23rd June (Thursday)", + }) + await user.click(targetDay) + await user.click(document.body) // Exit the focus lock + + await waitFor(() => { + expect( + getByRole("button", { name: "Dates : 1 May 2022 - 23 Jun 2022" }) + ).toBeInTheDocument() + }) + }, 10000) + + it("allows calling additional functions on value change", async () => { + const onChange = jest.fn() + const { getByRole, getByText } = render( + + ) + const triggerButton = getByRole("button", { + name: "Dates : 1 May 2022 - 20 Jun 2022", + }) + + await user.click(triggerButton) + await waitFor(() => { + expect(getByText("May 2022")).toBeVisible() + }) + + const targetDay = getByRole("button", { + name: "23rd June (Thursday)", + }) + await user.click(targetDay) + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ + from: new Date("2022-05-01"), + to: new Date("2022-06-23"), + }) + }) + }) +}) diff --git a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx new file mode 100644 index 00000000000..61f56f5d4a4 --- /dev/null +++ b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx @@ -0,0 +1,56 @@ +import React from "react" +import { FilterButton } from "~components/FilterButton" +import { + DateRange, + FilterDateRangePicker, + FilterDateRangePickerProps, +} from "~components/FilterDateRangePicker" +import { useFilterBarContext } from "../../context/FilterBarContext" + +export type FilterBarDateRangePickerProps = Omit< + FilterDateRangePickerProps, + | "id" + | "label" + | "renderTrigger" + | "isOpen" + | "setIsOpen" + | "selectedRange" + | "onRangeChange" +> & { + id?: string + onRangeChange?: FilterDateRangePickerProps["onRangeChange"] +} + +export const FilterBarDateRangePicker = ({ + id, + onRangeChange, + ...props +}: FilterBarDateRangePickerProps): JSX.Element => { + const { getFilterState, toggleOpenFilter, updateValue } = useFilterBarContext< + DateRange | undefined + >() + + if (!id) throw Error("Missing `id` prop in FilterBarDateRangePicker") + + const filterState = getFilterState(id) + + return ( + ( + + )} + isOpen={filterState.isOpen} + setIsOpen={(open): void => toggleOpenFilter(id, open)} + selectedRange={filterState.value} + onRangeChange={(range): void => { + updateValue(id, range) + onRangeChange?.(range) + }} + /> + ) +} + +FilterBarDateRangePicker.displayName = "FilterBar.DateRangePicker" diff --git a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/index.ts b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/index.ts new file mode 100644 index 00000000000..0b8ec97d3c7 --- /dev/null +++ b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/index.ts @@ -0,0 +1 @@ +export * from "./FilterBarDateRangePicker" diff --git a/packages/components/src/FilterBar/subcomponents/index.ts b/packages/components/src/FilterBar/subcomponents/index.ts index 59d065d954b..015451d6035 100644 --- a/packages/components/src/FilterBar/subcomponents/index.ts +++ b/packages/components/src/FilterBar/subcomponents/index.ts @@ -1 +1,2 @@ +export * from "./FilterBarDateRangePicker" export * from "./FilterSelect" diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index d0f5f63f62a..17923e63796 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,6 +1,7 @@ export * from "./Filter" export * from "./FilterBar" export * from "./FilterButton" +export * from "./FilterDateRangePicker" export * from "./FilterSelect" export * from "./KaizenProvider" export * from "./Workflow" From d7c21ad871863556d9ad7bc410c7533094393d05 Mon Sep 17 00:00:00 2001 From: Cassandra Tam Date: Mon, 5 Jun 2023 17:55:49 +1000 Subject: [PATCH 2/6] refactor(filterbar-select): rename FilterBarSelect files --- .../FilterBarSelect.spec.tsx} | 10 +++++----- .../FilterBarSelect.tsx} | 0 .../FilterBar/subcomponents/FilterBarSelect/index.ts | 1 + .../src/FilterBar/subcomponents/FilterSelect/index.ts | 1 - .../components/src/FilterBar/subcomponents/index.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename packages/components/src/FilterBar/subcomponents/{FilterSelect/FilterSelect.spec.tsx => FilterBarSelect/FilterBarSelect.spec.tsx} (93%) rename packages/components/src/FilterBar/subcomponents/{FilterSelect/FilterSelect.tsx => FilterBarSelect/FilterBarSelect.tsx} (100%) create mode 100644 packages/components/src/FilterBar/subcomponents/FilterBarSelect/index.ts delete mode 100644 packages/components/src/FilterBar/subcomponents/FilterSelect/index.ts diff --git a/packages/components/src/FilterBar/subcomponents/FilterSelect/FilterSelect.spec.tsx b/packages/components/src/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.spec.tsx similarity index 93% rename from packages/components/src/FilterBar/subcomponents/FilterSelect/FilterSelect.spec.tsx rename to packages/components/src/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.spec.tsx index 6eb8b9429cf..1f1c6ac7b7c 100644 --- a/packages/components/src/FilterBar/subcomponents/FilterSelect/FilterSelect.spec.tsx +++ b/packages/components/src/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.spec.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react" import { render, waitFor } from "@testing-library/react" import userEvent from "@testing-library/user-event" import { FilterBarProvider } from "~components/FilterBar/context/FilterBarContext" -import { FilterBarSelect, FilterBarSelectProps } from "./FilterSelect" +import { FilterBarSelect, FilterBarSelectProps } from "./FilterBarSelect" const user = userEvent.setup() @@ -63,14 +63,14 @@ describe("", () => { await user.click(triggerButton) await waitFor(() => { - const listbox = getByRole("listbox") - expect(listbox).toBeInTheDocument() + const popover = getByRole("dialog") + expect(popover).toBeInTheDocument() }) await user.click(document.body) await waitFor(() => { - const listbox = queryByRole("listbox") - expect(listbox).not.toBeInTheDocument() + const popover = queryByRole("dialog") + expect(popover).not.toBeInTheDocument() }) }) diff --git a/packages/components/src/FilterBar/subcomponents/FilterSelect/FilterSelect.tsx b/packages/components/src/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.tsx similarity index 100% rename from packages/components/src/FilterBar/subcomponents/FilterSelect/FilterSelect.tsx rename to packages/components/src/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.tsx diff --git a/packages/components/src/FilterBar/subcomponents/FilterBarSelect/index.ts b/packages/components/src/FilterBar/subcomponents/FilterBarSelect/index.ts new file mode 100644 index 00000000000..328c355e972 --- /dev/null +++ b/packages/components/src/FilterBar/subcomponents/FilterBarSelect/index.ts @@ -0,0 +1 @@ +export * from "./FilterBarSelect" diff --git a/packages/components/src/FilterBar/subcomponents/FilterSelect/index.ts b/packages/components/src/FilterBar/subcomponents/FilterSelect/index.ts deleted file mode 100644 index 59d065d954b..00000000000 --- a/packages/components/src/FilterBar/subcomponents/FilterSelect/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./FilterSelect" diff --git a/packages/components/src/FilterBar/subcomponents/index.ts b/packages/components/src/FilterBar/subcomponents/index.ts index 015451d6035..b20259f565e 100644 --- a/packages/components/src/FilterBar/subcomponents/index.ts +++ b/packages/components/src/FilterBar/subcomponents/index.ts @@ -1,2 +1,2 @@ export * from "./FilterBarDateRangePicker" -export * from "./FilterSelect" +export * from "./FilterBarSelect" From f1b374e719e6b9d991c40b99a914f8b0a3a3624b Mon Sep 17 00:00:00 2001 From: Cassandra Tam Date: Mon, 5 Jun 2023 17:56:20 +1000 Subject: [PATCH 3/6] feat(filterbar): export FilterBarDRP and update docs --- .../components/src/FilterBar/FilterBar.tsx | 3 +- .../src/FilterBar/_docs/FilterBar.mdx | 28 +++++++++++-- .../src/FilterBar/_docs/FilterBar.stories.tsx | 40 ++++++------------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/packages/components/src/FilterBar/FilterBar.tsx b/packages/components/src/FilterBar/FilterBar.tsx index ce4bd801bed..0ce45cbd7e9 100644 --- a/packages/components/src/FilterBar/FilterBar.tsx +++ b/packages/components/src/FilterBar/FilterBar.tsx @@ -6,7 +6,7 @@ import { FilterBarProviderProps, } from "./context/FilterBarContext" import { FiltersValues } from "./context/types" -import { FilterBarSelect } from "./subcomponents/FilterSelect/FilterSelect" +import { FilterBarDateRangePicker, FilterBarSelect } from "./subcomponents" import styles from "./FilterBar.module.scss" export type FilterBarProps = OverrideClassName< @@ -33,4 +33,5 @@ export const FilterBar = ({ FilterBar.displayName = "FilterBar" +FilterBar.DateRangePicker = FilterBarDateRangePicker FilterBar.Select = FilterBarSelect diff --git a/packages/components/src/FilterBar/_docs/FilterBar.mdx b/packages/components/src/FilterBar/_docs/FilterBar.mdx index d26f4333206..c203082cafa 100644 --- a/packages/components/src/FilterBar/_docs/FilterBar.mdx +++ b/packages/components/src/FilterBar/_docs/FilterBar.mdx @@ -1,5 +1,6 @@ import { ArgTypes, Meta } from "@storybook/blocks" import { ResourceLinks, KaioNotification, Installation, NoClipCanvas } from "../../../../../storybook/components" +import { LinkTo } from "../../../../../storybook/components/LinkTo" import * as FilterBarStories from "./FilterBar.stories" @@ -77,15 +78,34 @@ name: string // The name used to label the filter, shown in the trigger button Component: JSX.Element // A Filter component or a custom component consuming the FilterBarContext ``` -##### `Component` - Available Filter components -- `` +##### `Component` + +Components compatible with FilterBar must consume the FilterBarContext. + +Provided Filter components are built on top of their base component, and have the +`id`, `label`, `renderTrigger`, `isOpen`, `setIsOpen`, and `value` (or similar) props omitted as they are filled in for you. + +If the component comes with an `onChange` (or similar) prop, it will be made optional and it can be used for additional actions (eg. adding analytics). + +Available Filter components: +- `` - extends FilterDateRangePicker + - `selectedRange` is omitted + - `onRangeChange` is now optional +- `` - extends FilterSelect + - `selectedKey` is omitted + - `onSelectionChange` remains available as optional #### Usage We also provide a `Filters` type you can use for improved type safety. ```tsx -import { FilterBar, Filters } from "@kaizen/components" +import { FilterBar, Filters, DateRange } from "@kaizen/components" + +type ValuesMap = { + filterId1: string + filterId2: DateRange +} const filters = [ { @@ -96,7 +116,7 @@ const filters = [ { id: "filterId2", name: "Filter 2", - Component: + Component: }, ] satisfies Filters ``` diff --git a/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx b/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx index 15d46361ab6..cbfc3ce541e 100644 --- a/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx +++ b/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx @@ -18,9 +18,9 @@ export default meta const sampleCode = ` type Values = { flavour: string - topping: string sugarLevel: number iceLevel: number + deliveryDates: string } const [activeValues, onActiveValuesChange] = useState>({ @@ -41,19 +41,6 @@ const filters = [ /> ), }, - { - id: "topping", - name: "Topping", - Component: ( - - ), - }, { id: "sugarLevel", name: "Sugar Level", @@ -80,6 +67,11 @@ const filters = [ /> ), }, + { + id: "deliveryDates", + name: "Delivery Dates", + Component: , + }, ] satisfies Filters return ( @@ -92,9 +84,9 @@ return ( type Values = { flavour: string - topping: string sugarLevel: number iceLevel: number + deliveryDates: string } const filters = [ @@ -111,19 +103,6 @@ const filters = [ /> ), }, - { - id: "topping", - name: "Topping", - Component: ( - - ), - }, { id: "sugarLevel", name: "Sugar Level", @@ -150,6 +129,11 @@ const filters = [ /> ), }, + { + id: "deliveryDates", + name: "Delivery Dates", + Component: , + }, ] satisfies Filters export const BasicImplementation: StoryFn = () => { From 6cfda399c0b0012de5a1f51907c20041ce151fe9 Mon Sep 17 00:00:00 2001 From: Cassandra Tam Date: Tue, 6 Jun 2023 11:59:05 +1000 Subject: [PATCH 4/6] chore: add changeset --- .changeset/tasty-ducks-flash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tasty-ducks-flash.md diff --git a/.changeset/tasty-ducks-flash.md b/.changeset/tasty-ducks-flash.md new file mode 100644 index 00000000000..7432803317c --- /dev/null +++ b/.changeset/tasty-ducks-flash.md @@ -0,0 +1,5 @@ +--- +"@kaizen/components": minor +--- + +Retrofit FilterDateRangePicker to FilterBar From 2165ac1147d906928f67676eb72335a1e90050b4 Mon Sep 17 00:00:00 2001 From: Cassandra Tam Date: Wed, 7 Jun 2023 11:52:40 +1000 Subject: [PATCH 5/6] docs(filter-bar): fix FilterBarDRP type in example --- .../components/src/FilterBar/_docs/FilterBar.stories.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx b/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx index cbfc3ce541e..7d227c43b52 100644 --- a/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx +++ b/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx @@ -1,7 +1,9 @@ import React, { useState } from "react" import { Meta, StoryFn } from "@storybook/react" import Highlight from "react-highlight" +import { DateRange } from "~components/index" import { FilterBar, Filters } from "../index" + const meta = { title: "Components/Filter Bar", component: FilterBar, @@ -20,7 +22,7 @@ type Values = { flavour: string sugarLevel: number iceLevel: number - deliveryDates: string + deliveryDates: DateRange } const [activeValues, onActiveValuesChange] = useState>({ @@ -86,7 +88,7 @@ type Values = { flavour: string sugarLevel: number iceLevel: number - deliveryDates: string + deliveryDates: DateRange } const filters = [ From 59deecdf142af07bbdd30e292119ba3764ed7069 Mon Sep 17 00:00:00 2001 From: Cassandra Tam Date: Wed, 7 Jun 2023 12:22:28 +1000 Subject: [PATCH 6/6] feat(filterbar-drp): make locale optional --- packages/components/src/FilterBar/_docs/FilterBar.mdx | 1 + .../components/src/FilterBar/_docs/FilterBar.stories.tsx | 4 ++-- .../FilterBarDateRangePicker.spec.tsx | 8 +------- .../FilterBarDateRangePicker/FilterBarDateRangePicker.tsx | 6 +++++- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/components/src/FilterBar/_docs/FilterBar.mdx b/packages/components/src/FilterBar/_docs/FilterBar.mdx index 92c654ef073..ff8dac0f0a8 100644 --- a/packages/components/src/FilterBar/_docs/FilterBar.mdx +++ b/packages/components/src/FilterBar/_docs/FilterBar.mdx @@ -95,6 +95,7 @@ Available Filter components: - `` - extends FilterDateRangePicker - `selectedRange` is omitted - `onRangeChange` is now optional + - `locale` is now optional (defaults to `"en-AU"`) - `` - extends FilterSelect - `selectedKey` is omitted - `onSelectionChange` remains available as optional diff --git a/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx b/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx index cc00d37097f..51a1f365d82 100644 --- a/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx +++ b/packages/components/src/FilterBar/_docs/FilterBar.stories.tsx @@ -59,7 +59,7 @@ const filters = [ { id: "deliveryDates", name: "Delivery Dates", - Component: , + Component: , }, { id: "drank", @@ -113,7 +113,7 @@ const filters = [ { id: "deliveryDates", name: "Delivery Dates", - Component: , + Component: , }, { id: "drank", diff --git a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx index 5cbabf79110..2674e78c88d 100644 --- a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx +++ b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.spec.tsx @@ -27,13 +27,7 @@ const FilterBarDateRangePickerWrapper = ({ { id: "range", name: "Dates", - Component: ( - - ), + Component: , }, ]} values={values} diff --git a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx index 61f56f5d4a4..9e4807aa754 100644 --- a/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx +++ b/packages/components/src/FilterBar/subcomponents/FilterBarDateRangePicker/FilterBarDateRangePicker.tsx @@ -1,10 +1,10 @@ import React from "react" import { FilterButton } from "~components/FilterButton" import { - DateRange, FilterDateRangePicker, FilterDateRangePickerProps, } from "~components/FilterDateRangePicker" +import { DateRange } from "~types/DatePicker" import { useFilterBarContext } from "../../context/FilterBarContext" export type FilterBarDateRangePickerProps = Omit< @@ -16,14 +16,17 @@ export type FilterBarDateRangePickerProps = Omit< | "setIsOpen" | "selectedRange" | "onRangeChange" + | "locale" > & { id?: string onRangeChange?: FilterDateRangePickerProps["onRangeChange"] + locale?: FilterDateRangePickerProps["locale"] } export const FilterBarDateRangePicker = ({ id, onRangeChange, + locale = "en-AU", ...props }: FilterBarDateRangePickerProps): JSX.Element => { const { getFilterState, toggleOpenFilter, updateValue } = useFilterBarContext< @@ -49,6 +52,7 @@ export const FilterBarDateRangePicker = ({ updateValue(id, range) onRangeChange?.(range) }} + locale={locale} /> ) }