Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(filterbar-drp): add FilterBarDateRangePicker #3705

Merged
merged 7 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tasty-ducks-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kaizen/components": minor
---

Retrofit FilterDateRangePicker to FilterBar
10 changes: 7 additions & 3 deletions packages/components/src/FilterBar/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import {
FilterBarProviderProps,
} from "./context/FilterBarContext"
import { FiltersValues } from "./context/types"
import { FilterBarDatePicker } from "./subcomponents/FilterBarDatePicker/FilterBarDatePicker"
import { FilterBarSelect } from "./subcomponents/FilterSelect/FilterSelect"
import {
FilterBarDatePicker,
FilterBarDateRangePicker,
FilterBarSelect,
} from "./subcomponents"
import styles from "./FilterBar.module.scss"

export type FilterBarProps<ValuesMap extends FiltersValues> = OverrideClassName<
Expand All @@ -34,5 +37,6 @@ export const FilterBar = <ValuesMap extends FiltersValues>({

FilterBar.displayName = "FilterBar"

FilterBar.Select = FilterBarSelect
FilterBar.DatePicker = FilterBarDatePicker
FilterBar.DateRangePicker = FilterBarDateRangePicker
FilterBar.Select = FilterBarSelect
33 changes: 29 additions & 4 deletions packages/components/src/FilterBar/_docs/FilterBar.mdx
Original file line number Diff line number Diff line change
@@ -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"

<Meta of={FilterBarStories} />
Expand Down Expand Up @@ -77,15 +78,39 @@ 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
- `<FilterBar.Select>`
##### `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:
- `<FilterBar.DatePicker>` - extends <LinkTo pageId="components-filter-date-picker">FilterDatePicker</LinkTo>
- `selectedDate` is omitted
- `onDateChange` is now optional
- `locale` is now optional (defaults to `"en-AU"`)
- `<FilterBar.DateRangePicker>` - extends <LinkTo pageId="components-filter-date-range-picker">FilterDateRangePicker</LinkTo>
- `selectedRange` is omitted
- `onRangeChange` is now optional
- `locale` is now optional (defaults to `"en-AU"`)
- `<FilterBar.Select>` - extends <LinkTo pageId="components-filter-select">FilterSelect</LinkTo>
- `selectedKey` is omitted
- `onSelectionChange` remains available as optional

#### Usage

We also provide a `Filters<ValuesMap>` 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 = [
{
Expand All @@ -96,7 +121,7 @@ const filters = [
{
id: "filterId2",
name: "Filter 2",
Component: <FilterBar.Select {...props} />
Component: <FilterBar.DateRangePicker {...props} />
},
] satisfies Filters<ValuesMap>
```
Expand Down
42 changes: 14 additions & 28 deletions packages/components/src/FilterBar/_docs/FilterBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,8 +20,8 @@ export default meta
const sampleCode = `
type Values = {
flavour: string
topping: string
sugarLevel: number
deliveryDates: DateRange
drank: Date
}

Expand All @@ -41,19 +43,6 @@ const filters = [
/>
),
},
{
id: "topping",
name: "Topping",
Component: (
<FilterBar.Select
items={[
{ value: "none", label: "None" },
{ value: "pearls", label: "Pearls" },
{ value: "fruit-jelly", label: "Fruit Jelly" },
]}
/>
),
},
{
id: "sugarLevel",
name: "Sugar Level",
Expand All @@ -67,6 +56,11 @@ const filters = [
/>
),
},
{
id: "deliveryDates",
name: "Delivery Dates",
Component: <FilterBar.DateRangePicker />,
},
{
id: "drank",
name: "Drank",
Expand All @@ -84,8 +78,8 @@ return (

type Values = {
flavour: string
topping: string
sugarLevel: number
deliveryDates: DateRange
drank: Date
}

Expand All @@ -103,19 +97,6 @@ const filters = [
/>
),
},
{
id: "topping",
name: "Topping",
Component: (
<FilterBar.Select
items={[
{ value: "none", label: "None" },
{ value: "pearls", label: "Pearls" },
{ value: "fruit-jelly", label: "Fruit Jelly" },
]}
/>
),
},
{
id: "sugarLevel",
name: "Sugar Level",
Expand All @@ -129,6 +110,11 @@ const filters = [
/>
),
},
{
id: "deliveryDates",
name: "Delivery Dates",
Component: <FilterBar.DateRangePicker />,
},
{
id: "drank",
name: "Drank",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { useFilterBarContext } from "../../context/FilterBarContext"

export type FilterBarDatePickerProps = Omit<
FilterDatePickerProps,
| "id"
| "label"
| "renderTrigger"
| "isOpen"
| "setIsOpen"
| "renderTrigger"
| "label"
| "selectedDate"
| "id"
| "locale"
| "onDateChange"
| "locale"
> & {
id?: string
locale?: FilterDatePickerProps["locale"]
Expand All @@ -25,7 +25,7 @@ export type FilterBarDatePickerProps = Omit<
export const FilterBarDatePicker = ({
id,
onDateChange,
locale,
locale = "en-AU",
...props
}: FilterBarDatePickerProps): JSX.Element => {
const { getFilterState, toggleOpenFilter, updateValue } = useFilterBarContext<
Expand All @@ -40,7 +40,7 @@ export const FilterBarDatePicker = ({
<FilterDatePicker
{...props}
id={id}
locale={locale || "en-AU"}
locale={locale}
selectedDate={filterState.value || undefined}
label={filterState.name}
renderTrigger={(triggerProps): JSX.Element => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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<Values>
} & Partial<FilterBarDateRangePickerProps>): JSX.Element => {
const [values, setValues] = useState<Partial<Values>>(defaultValues ?? {})
return (
<FilterBarProvider<Values>
filters={[
{
id: "range",
name: "Dates",
Component: <FilterBarDateRangePicker id="range" {...customProps} />,
},
]}
values={values}
onValuesChange={setValues}
>
{filters => (
<>
{Object.values(filters).map(({ id, Component }) => (
<React.Fragment key={id}>{Component}</React.Fragment>
))}
</>
)}
</FilterBarProvider>
)
}

describe("<FilterBarDateRangePicker />", () => {
it("shows the name in the trigger button", () => {
const { getByRole } = render(<FilterBarDateRangePickerWrapper />)
const triggerButton = getByRole("button", { name: "Dates" })
expect(triggerButton).toBeInTheDocument()
})

it("can toggle its open state", async () => {
const { getByRole, queryByRole } = render(
<FilterBarDateRangePickerWrapper />
)
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(
<FilterBarDateRangePickerWrapper
defaultValues={{
range: {
from: new Date("2022-05-01"),
to: new Date("2022-11-25"),
},
}}
/>
)
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(
<FilterBarDateRangePickerWrapper
defaultValues={{
range: {
from: new Date("2022-05-01"),
to: new Date("2022-06-20"),
},
}}
/>
)
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<void, [DateRange | undefined]>()
const { getByRole, getByText } = render(
<FilterBarDateRangePickerWrapper
defaultValues={{
range: {
from: new Date("2022-05-01"),
to: new Date("2022-06-20"),
},
}}
onRangeChange={onChange}
/>
)
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"),
})
})
})
})
Loading