@kaizen/components 1.68.2 → 1.68.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/cjs/Filter/FilterBar/context/FilterBarContext.cjs +13 -3
  2. package/dist/cjs/Filter/FilterBar/context/reducer/filterBarStateReducer.cjs +1 -1
  3. package/dist/cjs/Filter/FilterBar/context/reducer/setupFilterBarState.cjs +4 -0
  4. package/dist/cjs/Filter/FilterBar/context/utils/updateDependentFilters.cjs +1 -1
  5. package/dist/cjs/Filter/FilterBar/subcomponents/ClearAllButton/ClearAllButton.cjs +7 -2
  6. package/dist/cjs/Filter/FilterBar/subcomponents/ClearAllButton/ClearAllButton.module.scss.cjs +2 -1
  7. package/dist/cjs/Filter/FilterDateRangePicker/subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.cjs +3 -0
  8. package/dist/cjs/Tile/subcomponents/GenericTile/GenericTile.cjs +16 -3
  9. package/dist/esm/Filter/FilterBar/context/FilterBarContext.mjs +13 -3
  10. package/dist/esm/Filter/FilterBar/context/reducer/filterBarStateReducer.mjs +1 -1
  11. package/dist/esm/Filter/FilterBar/context/reducer/setupFilterBarState.mjs +4 -0
  12. package/dist/esm/Filter/FilterBar/context/utils/updateDependentFilters.mjs +1 -1
  13. package/dist/esm/Filter/FilterBar/subcomponents/ClearAllButton/ClearAllButton.mjs +6 -2
  14. package/dist/esm/Filter/FilterBar/subcomponents/ClearAllButton/ClearAllButton.module.scss.mjs +2 -1
  15. package/dist/esm/Filter/FilterDateRangePicker/subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.mjs +3 -0
  16. package/dist/esm/Tile/subcomponents/GenericTile/GenericTile.mjs +16 -3
  17. package/dist/styles.css +81 -77
  18. package/dist/types/Filter/FilterBar/context/FilterBarContext.d.ts +1 -0
  19. package/dist/types/Filter/FilterBar/context/types.d.ts +1 -0
  20. package/dist/types/Tile/subcomponents/GenericTile/GenericTile.d.ts +3 -1
  21. package/locales/en.json +8 -0
  22. package/package.json +1 -1
  23. package/src/Avatar/_docs/Avatar.stickersheet.stories.tsx +30 -32
  24. package/src/AvatarGroup/_docs/AvatarGroup.stickersheet.stories.tsx +14 -16
  25. package/src/Badge/_docs/Badge.stickersheet.stories.tsx +22 -22
  26. package/src/Brand/_docs/Brand.stickersheet.stories.tsx +24 -26
  27. package/src/BrandMoment/_docs/BrandMoment.stickersheet.stories.tsx +35 -39
  28. package/src/ButtonGroup/_docs/ButtonGroup.stickersheet.stories.tsx +117 -122
  29. package/src/Calendar/CalendarPopover/_docs/CalendarPopover.stickersheet.stories.tsx +4 -4
  30. package/src/Calendar/CalendarRange/_docs/CalendarRange.stickersheet.stories.tsx +22 -30
  31. package/src/Calendar/CalendarSingle/_docs/CalendarSingle.stickersheet.stories.tsx +58 -58
  32. package/src/Card/_docs/Card.stickersheet.stories.tsx +12 -16
  33. package/src/Checkbox/Checkbox/_docs/Checkbox.stickersheet.stories.tsx +24 -27
  34. package/src/Checkbox/CheckboxField/_docs/CheckboxField.stickersheet.stories.tsx +31 -34
  35. package/src/Checkbox/CheckboxGroup/_docs/CheckboxGroup.stickersheet.stories.tsx +25 -29
  36. package/src/ClearButton/_docs/ClearButton.stickersheet.stories.tsx +9 -9
  37. package/src/Collapsible/Collapsible/_docs/Collapsible.stickersheet.stories.tsx +56 -62
  38. package/src/Collapsible/CollapsibleGroup/_docs/CollapsibleGroup.stickersheet.stories.tsx +19 -21
  39. package/src/Collapsible/ExpertAdviceCollapsible/_docs/ExpertAdviceCollapsible.stickersheets.stories.tsx +6 -8
  40. package/src/DatePicker/_docs/DatePicker.stickersheet.stories.tsx +129 -144
  41. package/src/DatePicker/_docs/getLocale.stickersheet.stories.tsx +9 -11
  42. package/src/DateRangePicker/_docs/DateRangePicker.stickersheet.stories.tsx +26 -28
  43. package/src/Divider/_docs/Divider.stickersheet.stories.tsx +13 -10
  44. package/src/EmptyState/_docs/EmptyState.stickersheet.stories.tsx +7 -2
  45. package/src/ErrorPage/_docs/ErrorPage.stickersheet.stories.tsx +16 -20
  46. package/src/FieldGroup/_docs/FieldGroup.stickersheet.stories.tsx +5 -8
  47. package/src/FieldMessage/_docs/FieldMessage.stickersheet.stories.tsx +10 -20
  48. package/src/Filter/Filter/_docs/Filter.stickersheet.stories.tsx +13 -15
  49. package/src/Filter/FilterBar/FilterBar.spec.tsx +0 -64
  50. package/src/Filter/FilterBar/_docs/FilterBar.spec.stories.tsx +249 -0
  51. package/src/Filter/FilterBar/_docs/FilterBar.stickersheet.stories.tsx +34 -48
  52. package/src/Filter/FilterBar/_docs/FilterBar.stories.tsx +1 -1
  53. package/src/Filter/FilterBar/context/FilterBarContext.tsx +17 -5
  54. package/src/Filter/FilterBar/context/reducer/filterBarStateReducer.spec.ts +3 -0
  55. package/src/Filter/FilterBar/context/reducer/filterBarStateReducer.ts +1 -1
  56. package/src/Filter/FilterBar/context/reducer/setupFilterBarState.spec.tsx +40 -0
  57. package/src/Filter/FilterBar/context/reducer/setupFilterBarState.ts +5 -0
  58. package/src/Filter/FilterBar/context/reducer/updateSingleFilter.spec.ts +2 -0
  59. package/src/Filter/FilterBar/context/reducer/updateValues.spec.ts +5 -0
  60. package/src/Filter/FilterBar/context/types.ts +1 -0
  61. package/src/Filter/FilterBar/context/utils/checkShouldUpdateValues.spec.ts +1 -0
  62. package/src/Filter/FilterBar/context/utils/getInactiveFilters.spec.ts +2 -0
  63. package/src/Filter/FilterBar/context/utils/getIsUsableWhenArgs.spec.ts +1 -0
  64. package/src/Filter/FilterBar/context/utils/updateDependentFilters.spec.ts +8 -0
  65. package/src/Filter/FilterBar/context/utils/updateDependentFilters.ts +1 -1
  66. package/src/Filter/FilterBar/subcomponents/ClearAllButton/ClearAllButton.module.scss +4 -0
  67. package/src/Filter/FilterBar/subcomponents/ClearAllButton/ClearAllButton.tsx +5 -2
  68. package/src/Filter/FilterButton/_docs/filter-buttons.stickersheet.stories.tsx +45 -51
  69. package/src/Filter/FilterDatePicker/_docs/FilterDatePicker.stickersheet.stories.tsx +74 -93
  70. package/src/Filter/FilterDateRangePicker/_docs/FilterDateRangePicker.stickersheet.stories.tsx +110 -128
  71. package/src/Filter/FilterDateRangePicker/subcomponents/FilterDateRangePickerField/FilterDateRangePickerField.tsx +4 -0
  72. package/src/Filter/FilterSelect/_docs/FilterSelect.stickersheet.stories.tsx +239 -249
  73. package/src/GuidanceBlock/_docs/GuidanceBlock.stickersheet.stories.tsx +38 -40
  74. package/src/Heading/_docs/Heading.stickersheet.stories.tsx +101 -111
  75. package/src/Icon/_docs/Icon.stickersheet.stories.tsx +13 -19
  76. package/src/Illustration/Scene/BrandMomentCaptureIntro/_docs/BrandMomentCaptureIntro.stickersheet.stories.tsx +8 -8
  77. package/src/Illustration/Scene/_docs/Scene.stickersheet.stories.tsx +139 -185
  78. package/src/Illustration/subcomponents/VideoPlayer/VideoPlayer.stickersheet.stories.tsx +39 -41
  79. package/src/Input/Input/_docs/Input.stickersheet.stories.tsx +30 -33
  80. package/src/Input/InputRange/_docs/InputRange.stickersheet.stories.tsx +21 -20
  81. package/src/Input/InputSearch/_docs/InputSearch.stickersheet.stories.tsx +137 -144
  82. package/src/Label/_docs/Label.stickersheet.stories.tsx +78 -85
  83. package/src/LabelledMessage/_docs/LabelledMessage.stickersheet.stories.tsx +3 -5
  84. package/src/LikertScaleLegacy/_docs/LikertScaleLegacy.stickersheet.stories.tsx +82 -82
  85. package/src/Loading/LoadingGraphic/_docs/LoadingGraphic.stickersheet.stories.tsx +18 -20
  86. package/src/Loading/LoadingGraphic/_docs/LoadingGraphic.stories.tsx +46 -52
  87. package/src/Loading/LoadingHeading/_docs/LoadingHeading.stickersheet.stories.tsx +14 -18
  88. package/src/Loading/LoadingHeading/_docs/LoadingHeading.stories.tsx +7 -13
  89. package/src/Loading/LoadingParagraph/_docs/LoadingParagraph.stickersheet.stories.tsx +14 -16
  90. package/src/Loading/LoadingSpinner/_docs/LoadingSpinner.stickersheet.stories.tsx +13 -15
  91. package/src/MultiSelect/_docs/MultiSelect.stickersheet.stories.tsx +69 -74
  92. package/src/MultiSelect/subcomponents/Checkbox/_docs/Checkbox.stickersheet.stories.tsx +22 -28
  93. package/src/MultiSelect/subcomponents/MultiSelectOptionField/_docs/MultiSelectOptionField.stickersheet.stories.tsx +62 -67
  94. package/src/MultiSelect/subcomponents/MultiSelectOptions/_docs/MultiSelectOptions.stickersheet.stories.tsx +21 -28
  95. package/src/MultiSelect/subcomponents/MultiSelectToggle/_docs/MultiSelectToggle.stickersheet.stories.tsx +69 -93
  96. package/src/Notification/GlobalNotification/_docs/GlobalNotification.stickersheet.stories.tsx +13 -16
  97. package/src/Notification/InlineNotification/_docs/InlineNotification.stickersheet.stories.tsx +13 -16
  98. package/src/Pagination/_docs/Pagination.stickersheet.stories.tsx +60 -74
  99. package/src/Pagination/subcomponents/DirectionalLink/_docs/DirectionalLink.stickersheet.stories.tsx +21 -23
  100. package/src/Pagination/subcomponents/PaginationLink/_docs/PaginationLink.stickersheet.stories.tsx +24 -28
  101. package/src/Popover/_docs/Popover.stickersheet.stories.tsx +33 -45
  102. package/src/ProgressBar/_docs/ProgressBar.stickersheet.stories.tsx +15 -26
  103. package/src/Radio/Radio/_docs/Radio.stickersheet.stories.tsx +46 -54
  104. package/src/Radio/RadioField/_docs/RadioField.stickersheet.stories.tsx +51 -54
  105. package/src/Radio/RadioGroup/_docs/RadioGroup.stickersheet.stories.tsx +80 -86
  106. package/src/RichTextEditor/EditableRichTextContent/_docs/EditableRichTextContent.stickersheet.stories.tsx +42 -58
  107. package/src/RichTextEditor/RichTextEditor/subcomponents/ToggleIconButton/_docs/ToggleIconButton.stickersheet.stories.tsx +34 -57
  108. package/src/SearchField/_docs/SearchField.stickersheet.stories.tsx +32 -31
  109. package/src/Select/_docs/Select.stickersheet.stories.tsx +128 -127
  110. package/src/Slider/_docs/Slider.stickersheet.stories.tsx +85 -96
  111. package/src/SplitButton/_docs/SplitButton.stickersheet.stories.tsx +62 -68
  112. package/src/Tag/_docs/Tag.stickersheet.stories.tsx +49 -57
  113. package/src/Text/_docs/Text.stickersheet.stories.tsx +43 -47
  114. package/src/TextArea/_docs/TextArea.stickersheet.stories.tsx +28 -31
  115. package/src/TextAreaField/_docs/TextAreaField.stickersheet.stories.tsx +71 -89
  116. package/src/TextField/_docs/TextField.stickersheet.stories.tsx +47 -50
  117. package/src/Tile/InformationTile/_docs/InformationTile.mdx +4 -0
  118. package/src/Tile/InformationTile/_docs/InformationTile.stickersheet.stories.tsx +15 -21
  119. package/src/Tile/InformationTile/_docs/InformationTile.stories.tsx +7 -0
  120. package/src/Tile/MultiActionTile/_docs/MultiActionTile.stickersheet.stories.tsx +15 -21
  121. package/src/Tile/TileGrid/_docs/TileGrid.stickersheet.stories.tsx +91 -99
  122. package/src/Tile/subcomponents/GenericTile/GenericTile.spec.stories.tsx +65 -5
  123. package/src/Tile/subcomponents/GenericTile/GenericTile.tsx +20 -2
  124. package/src/Tile/subcomponents/GenericTile/_docs/GenericTile.stickersheet.stories.tsx +15 -21
  125. package/src/TimeField/_docs/TimeField.stickersheet.stories.tsx +74 -83
  126. package/src/TitleBlockZen/_docs/TitleBlockZen.stories.tsx +73 -75
  127. package/src/ToggleSwitch/ToggleSwitch/_docs/ToggleSwitch.stickersheet.stories.tsx +24 -27
  128. package/src/ToggleSwitch/ToggleSwitchField/_docs/ToggleSwitchField.stickersheet.stories.tsx +47 -50
  129. package/src/Well/_docs/Well.stickersheet.stories.tsx +31 -41
  130. package/src/__actions__/Button/v1/Button/_docs/Button.stickersheet.stories.tsx +107 -146
  131. package/src/__actions__/Button/v1/IconButton/_docs/IconButton.stickersheet.stories.tsx +26 -29
  132. package/src/__actions__/Button/v3/_docs/Button.stickersheet.stories.tsx +141 -159
  133. package/src/__actions__/Menu/v1/_docs/Menu.stickersheet.stories.tsx +33 -36
  134. package/src/__future__/Icon/_docs/Icon.docs.stories.tsx +17 -23
  135. package/src/__future__/Icon/_docs/Icon.stickersheet.stories.tsx +63 -72
  136. package/src/__future__/Select/_docs/Select.stickersheet.stories.tsx +287 -309
  137. package/src/__future__/Tag/RemovableTag/_docs/RemovableTag.stickersheet.stories.tsx +42 -46
  138. package/src/__future__/Tag/Tag/_docs/Tag.stickersheet.stories.tsx +14 -17
  139. package/src/__overlays__/Tooltip/v1/_docs/Tooltip.stickersheet.stories.tsx +101 -103
  140. package/src/__overlays__/Tooltip/v3/_docs/Tooltip.stickersheet.stories.tsx +3 -4
@@ -35,23 +35,21 @@ const StickerSheetTemplate: StickerSheetStory = {
35
35
 
36
36
  return (
37
37
  <StickerSheet
38
- heading="Filter"
38
+ title="Filter"
39
39
  style={{ paddingBottom: IS_CHROMATIC ? "6rem" : undefined }}
40
+ headers={["Open"]}
40
41
  >
41
- <StickerSheet.Header headings={["Open"]} />
42
- <StickerSheet.Body>
43
- <StickerSheet.Row>
44
- <Filter
45
- isOpen={isOpen}
46
- setIsOpen={setIsOpen}
47
- renderTrigger={(triggerProps): JSX.Element => (
48
- <FilterButton label="Label" {...triggerProps} />
49
- )}
50
- >
51
- <FilterContents>Filter Contents</FilterContents>
52
- </Filter>
53
- </StickerSheet.Row>
54
- </StickerSheet.Body>
42
+ <StickerSheet.Row>
43
+ <Filter
44
+ isOpen={isOpen}
45
+ setIsOpen={setIsOpen}
46
+ renderTrigger={(triggerProps): JSX.Element => (
47
+ <FilterButton label="Label" {...triggerProps} />
48
+ )}
49
+ >
50
+ <FilterContents>Filter Contents</FilterContents>
51
+ </Filter>
52
+ </StickerSheet.Row>
55
53
  </StickerSheet>
56
54
  )
57
55
  },
@@ -722,70 +722,6 @@ describe("<FilterBar />", () => {
722
722
  })
723
723
  })
724
724
 
725
- describe("Clear all", () => {
726
- it("clears all the values of all the filters", async () => {
727
- const { getByRole } = render(
728
- <FilterBarWrapper<ValuesSimple>
729
- filters={simpleFilters}
730
- defaultValues={{
731
- flavour: "jasmine-milk-tea",
732
- sugarLevel: 50,
733
- iceLevel: 100,
734
- }}
735
- />
736
- )
737
- await waitForI18nContent()
738
-
739
- const flavourButton = getByRole("button", {
740
- name: "Flavour : Jasmine Milk Tea",
741
- })
742
- const sugarLevelButton = getByRole("button", {
743
- name: "Sugar Level : 50%",
744
- })
745
- const iceLevelButton = getByRole("button", { name: "Ice Level : 100%" })
746
-
747
- expect(flavourButton).toHaveAccessibleName("Flavour : Jasmine Milk Tea")
748
- expect(sugarLevelButton).toHaveAccessibleName("Sugar Level : 50%")
749
- expect(iceLevelButton).toHaveAccessibleName("Ice Level : 100%")
750
-
751
- await user.click(getByRole("button", { name: "Clear all filters" }))
752
-
753
- await waitFor(() => {
754
- expect(flavourButton).toHaveAccessibleName("Flavour")
755
- expect(sugarLevelButton).toHaveAccessibleName("Sugar Level")
756
- expect(iceLevelButton).toHaveAccessibleName("Ice Level")
757
- })
758
- })
759
-
760
- it("removes all removable filters", async () => {
761
- const { getByRole } = render(
762
- <FilterBarWrapper<ValuesRemovable>
763
- filters={filtersRemovable}
764
- defaultValues={{
765
- flavour: "jasmine-milk-tea",
766
- topping: "pearls",
767
- }}
768
- />
769
- )
770
- await waitForI18nContent()
771
-
772
- const flavourButton = getByRole("button", {
773
- name: "Flavour : Jasmine Milk Tea",
774
- })
775
- const toppingButton = getByRole("button", { name: "Topping : Pearls" })
776
-
777
- expect(flavourButton).toBeVisible()
778
- expect(toppingButton).toBeVisible()
779
-
780
- await user.click(getByRole("button", { name: "Clear all filters" }))
781
-
782
- await waitFor(() => {
783
- expect(flavourButton).not.toBeInTheDocument()
784
- expect(toppingButton).not.toBeInTheDocument()
785
- })
786
- })
787
- })
788
-
789
725
  describe("External events", () => {
790
726
  it("allows updating the values via an external event", async () => {
791
727
  const Wrapper = (): JSX.Element => {
@@ -0,0 +1,249 @@
1
+ import React, { useState } from "react"
2
+ import { Meta, StoryObj } from "@storybook/react"
3
+ import { expect, userEvent, waitFor, within, fn } from "@storybook/test"
4
+ import { FilterMultiSelect } from "~components/Filter/FilterMultiSelect"
5
+ import { DateRange } from "~components/index"
6
+ import { FilterBar, Filters } from "../index"
7
+
8
+ const meta = {
9
+ title: "Components/FilterBar/FilterBar Tests",
10
+ component: FilterBar,
11
+ argTypes: {
12
+ filters: { control: false },
13
+ values: { control: false },
14
+ onValuesChange: { control: false },
15
+ },
16
+ args: {
17
+ filters: [], // Defined in stories
18
+ values: {}, // Defined in stories
19
+ onValuesChange: fn(),
20
+ },
21
+ } satisfies Meta<typeof FilterBar>
22
+
23
+ export default meta
24
+
25
+ type Story = StoryObj<typeof meta>
26
+
27
+ type Values = {
28
+ flavour: string
29
+ deliveryDates: DateRange
30
+ toppings: string[]
31
+ drank: Date
32
+ }
33
+
34
+ const filters = [
35
+ {
36
+ id: "flavour",
37
+ name: "Flavour",
38
+ Component: (
39
+ <FilterBar.Select
40
+ items={[
41
+ { value: "jasmine-milk-tea", label: "Jasmine Milk Tea" },
42
+ { value: "honey-milk-tea", label: "Honey Milk Tea" },
43
+ { value: "lychee-green-tea", label: "Lychee Green Tea" },
44
+ ]}
45
+ />
46
+ ),
47
+ },
48
+ {
49
+ id: "deliveryDates",
50
+ name: "Delivery Dates",
51
+ Component: <FilterBar.DateRangePicker />,
52
+ },
53
+ {
54
+ id: "toppings",
55
+ name: "Toppings",
56
+ Component: (
57
+ <FilterBar.MultiSelect
58
+ items={[
59
+ { value: "none", label: "None" },
60
+ { value: "pearls", label: "Pearls" },
61
+ { value: "fruit-jelly", label: "Fruit Jelly" },
62
+ { value: "peanuts", label: "Peanuts" },
63
+ { value: "coconut", label: "Coconut" },
64
+ { value: "aloe", label: "Aloe Vera" },
65
+ { value: "mochi", label: "Mini Mochi" },
66
+ { value: "popping-pearls", label: "Popping Pearls" },
67
+ ]}
68
+ >
69
+ {(): JSX.Element => (
70
+ <>
71
+ <FilterMultiSelect.SearchInput />
72
+ <FilterMultiSelect.ListBox>
73
+ {({ allItems }): JSX.Element | JSX.Element[] =>
74
+ allItems.map(item => (
75
+ <FilterMultiSelect.Option key={item.key} item={item} />
76
+ ))
77
+ }
78
+ </FilterMultiSelect.ListBox>
79
+ <FilterMultiSelect.MenuFooter>
80
+ <FilterMultiSelect.SelectAllButton />
81
+ <FilterMultiSelect.ClearButton />
82
+ </FilterMultiSelect.MenuFooter>
83
+ </>
84
+ )}
85
+ </FilterBar.MultiSelect>
86
+ ),
87
+ isRemovable: true,
88
+ },
89
+ {
90
+ id: "drank",
91
+ name: "Drank",
92
+ Component: <FilterBar.DatePicker />,
93
+ isRemovable: true,
94
+ },
95
+ ] satisfies Filters<Values>
96
+
97
+ export const ClearAllFromValue: Story = {
98
+ render: args => {
99
+ const [activeValues, onActiveValuesChange] = useState<Partial<Values>>({})
100
+ return (
101
+ <FilterBar<Values>
102
+ {...args}
103
+ filters={filters}
104
+ values={activeValues}
105
+ onValuesChange={onActiveValuesChange}
106
+ />
107
+ )
108
+ },
109
+ play: async ({ canvasElement, step }) => {
110
+ const canvas = within(canvasElement.parentElement!)
111
+
112
+ await step(
113
+ "Clear all button hidden by default given no values",
114
+ async () => {
115
+ expect(
116
+ canvas.queryByRole("button", {
117
+ name: "Clear all filters",
118
+ })
119
+ ).not.toBeInTheDocument()
120
+ }
121
+ )
122
+
123
+ await step("filter value is added", async () => {
124
+ await userEvent.click(canvas.getByRole("button", { name: "Flavour" }))
125
+ await userEvent.click(
126
+ canvas.getByRole("option", { name: "Jasmine Milk Tea" })
127
+ )
128
+ expect(
129
+ canvas.getByRole("button", { name: "Flavour: Jasmine Milk Tea" })
130
+ ).toBeInTheDocument()
131
+ })
132
+
133
+ await step(
134
+ "'Clear all' press removes the value and hides itself",
135
+ async () => {
136
+ const clearAllButton = canvas.getByRole("button", {
137
+ name: "Clear all filters",
138
+ })
139
+ userEvent.click(clearAllButton)
140
+
141
+ waitFor(() =>
142
+ expect(
143
+ canvas.getByRole("button", { name: "Flavour" })
144
+ ).toBeInTheDocument()
145
+ )
146
+
147
+ waitFor(() =>
148
+ expect(
149
+ canvas.queryByRole("button", {
150
+ name: "Clear all filters",
151
+ })
152
+ ).not.toBeInTheDocument()
153
+ )
154
+ }
155
+ )
156
+ },
157
+ }
158
+
159
+ export const ClearAllFromRemovable: Story = {
160
+ render: args => {
161
+ const [activeValues, onActiveValuesChange] = useState<Partial<Values>>({})
162
+ return (
163
+ <FilterBar<Values>
164
+ {...args}
165
+ filters={filters}
166
+ values={activeValues}
167
+ onValuesChange={onActiveValuesChange}
168
+ />
169
+ )
170
+ },
171
+ play: async ({ canvasElement, step }) => {
172
+ const canvas = within(canvasElement.parentElement!)
173
+
174
+ await step("removable filter is added with no value", async () => {
175
+ await waitFor(() => {
176
+ userEvent.click(canvas.getByRole("button", { name: "Add Filters" }))
177
+ })
178
+
179
+ await waitFor(() => {
180
+ userEvent.click(canvas.getByRole("button", { name: "Toppings" }))
181
+ })
182
+ })
183
+
184
+ await step("'Clear all' press removes removable filter", async () => {
185
+ await waitFor(() => {
186
+ userEvent.click(
187
+ canvas.getByRole("button", {
188
+ name: "Clear all filters",
189
+ })
190
+ )
191
+ })
192
+
193
+ waitFor(() =>
194
+ expect(
195
+ canvas.queryByRole("button", { name: "Toppings" })
196
+ ).not.toBeInTheDocument()
197
+ )
198
+
199
+ waitFor(() =>
200
+ expect(
201
+ canvas.queryByRole("button", {
202
+ name: "Clear all filters",
203
+ })
204
+ ).not.toBeInTheDocument()
205
+ )
206
+ })
207
+ },
208
+ }
209
+
210
+ export const ClearAllRemovesItself: Story = {
211
+ render: args => {
212
+ const [activeValues, onActiveValuesChange] = useState<Partial<Values>>({})
213
+ return (
214
+ <FilterBar<Values>
215
+ {...args}
216
+ filters={filters}
217
+ values={activeValues}
218
+ onValuesChange={onActiveValuesChange}
219
+ />
220
+ )
221
+ },
222
+ play: async ({ canvasElement, step }) => {
223
+ const canvas = within(canvasElement.parentElement!)
224
+
225
+ await step("removable filter is added with no value", async () => {
226
+ await waitFor(() =>
227
+ userEvent.click(canvas.getByRole("button", { name: "Add Filters" }))
228
+ )
229
+ await userEvent.click(canvas.getByRole("button", { name: "Drank" }))
230
+ })
231
+
232
+ await step(
233
+ "Clear all button hides by itself after removing filter",
234
+ async () => {
235
+ await userEvent.click(
236
+ canvas.getByRole("button", { name: "Remove filter - Drank" })
237
+ )
238
+ }
239
+ )
240
+
241
+ waitFor(() =>
242
+ expect(
243
+ canvas.queryByRole("button", {
244
+ name: "Clear all filters",
245
+ })
246
+ ).not.toBeInTheDocument()
247
+ )
248
+ },
249
+ }
@@ -7,7 +7,7 @@ import {
7
7
  import { FilterBar, Filters } from "../index"
8
8
 
9
9
  export default {
10
- title: "Components/Filter Bar",
10
+ title: "Components/FilterBar",
11
11
  parameters: {
12
12
  chromatic: { disable: false },
13
13
  controls: { disable: true },
@@ -122,60 +122,46 @@ const StickerSheetTemplate: StickerSheetStory = {
122
122
 
123
123
  return (
124
124
  <>
125
- <StickerSheet heading="Filter Bar" style={{ width: "100%" }}>
126
- <StickerSheet.Body>
127
- <StickerSheet.Row>
125
+ <StickerSheet title="Filter Bar" layout="stretch">
126
+ <StickerSheet.Row>
127
+ <FilterBar<Values>
128
+ filters={filters}
129
+ values={activeValues}
130
+ onValuesChange={setActiveValues}
131
+ />
132
+ </StickerSheet.Row>
133
+ </StickerSheet>
134
+
135
+ <StickerSheet title="Overflow (container 500px)" layout="stretch">
136
+ <StickerSheet.Row>
137
+ <div style={{ maxWidth: "500px" }}>
128
138
  <FilterBar<Values>
129
139
  filters={filters}
130
- values={activeValues}
131
- onValuesChange={setActiveValues}
140
+ values={activeValuesOverflow}
141
+ onValuesChange={setActiveValuesOverflow}
132
142
  />
133
- </StickerSheet.Row>
134
- </StickerSheet.Body>
135
- </StickerSheet>
136
-
137
- <StickerSheet
138
- heading="Overflow (container 500px)"
139
- style={{ width: "100%" }}
140
- >
141
- <StickerSheet.Body>
142
- <StickerSheet.Row>
143
- <div style={{ maxWidth: "500px" }}>
144
- <FilterBar<Values>
145
- filters={filters}
146
- values={activeValuesOverflow}
147
- onValuesChange={setActiveValuesOverflow}
148
- />
149
- </div>
150
- </StickerSheet.Row>
151
- </StickerSheet.Body>
143
+ </div>
144
+ </StickerSheet.Row>
152
145
  </StickerSheet>
153
146
 
154
- <StickerSheet heading="Removable; All active" style={{ width: "100%" }}>
155
- <StickerSheet.Body>
156
- <StickerSheet.Row>
157
- <FilterBar<ValuesRemovable>
158
- filters={removableFilters}
159
- values={valuesRemovableAllActive}
160
- onValuesChange={setValuesRemovableAllActive}
161
- />
162
- </StickerSheet.Row>
163
- </StickerSheet.Body>
147
+ <StickerSheet title="Removable; All active" layout="stretch">
148
+ <StickerSheet.Row>
149
+ <FilterBar<ValuesRemovable>
150
+ filters={removableFilters}
151
+ values={valuesRemovableAllActive}
152
+ onValuesChange={setValuesRemovableAllActive}
153
+ />
154
+ </StickerSheet.Row>
164
155
  </StickerSheet>
165
156
 
166
- <StickerSheet
167
- heading="Removable; Partial active"
168
- style={{ width: "100%" }}
169
- >
170
- <StickerSheet.Body>
171
- <StickerSheet.Row>
172
- <FilterBar<ValuesRemovable>
173
- filters={removableFilters}
174
- values={valuesRemovablePartialActive}
175
- onValuesChange={setValuesRemovablePartialActive}
176
- />
177
- </StickerSheet.Row>
178
- </StickerSheet.Body>
157
+ <StickerSheet title="Removable; Partial active" layout="stretch">
158
+ <StickerSheet.Row>
159
+ <FilterBar<ValuesRemovable>
160
+ filters={removableFilters}
161
+ values={valuesRemovablePartialActive}
162
+ onValuesChange={setValuesRemovablePartialActive}
163
+ />
164
+ </StickerSheet.Row>
179
165
  </StickerSheet>
180
166
  </>
181
167
  )
@@ -21,7 +21,7 @@ import { FilterBar, Filters, useFilterBarContext } from "../index"
21
21
  import { FilterBarMultiSelectProps } from "../subcomponents"
22
22
 
23
23
  const meta = {
24
- title: "Components/Filter Bar",
24
+ title: "Components/FilterBar",
25
25
  component: FilterBar,
26
26
  argTypes: {
27
27
  filters: { control: false },
@@ -22,6 +22,7 @@ export type FilterBarContextValue<
22
22
  getFilterState: <Id extends keyof ValuesMap>(
23
23
  id: Id
24
24
  ) => FilterState<keyof ValuesMap, ValuesMap[Id]>
25
+ isClearable: boolean
25
26
  getActiveFilterValues: () => Partial<ValuesMap>
26
27
  /**
27
28
  * @deprecated Use `setFilterOpenState` instead.
@@ -90,12 +91,28 @@ export const FilterBarProvider = <ValuesMap extends FiltersValues>({
90
91
  setupFilterBarState<ValuesMap>(filters, values)
91
92
  )
92
93
 
94
+ const activeFilters = Array.from(
95
+ state.activeFilterIds,
96
+ id => mappedFilters[id]
97
+ )
98
+
99
+ // Workaround for DateRangePicker populating the values object before the value is valid
100
+ // (it purposefully persists a state with a 'from' date but no 'to' date, but hides it on the filter button)
101
+ const isDraftDateRange = (v: ValuesMap): boolean =>
102
+ v && v.from !== undefined && v.to === undefined
103
+ const hasDraftDateRangeOnly = Object.values(values).every(isDraftDateRange)
104
+
105
+ const isClearable =
106
+ (Object.keys(values).length > 0 && !hasDraftDateRangeOnly) ||
107
+ (state.hasRemovableFilter && activeFilters.some(f => f.isRemovable))
108
+
93
109
  const value = {
94
110
  getFilterState: <Id extends keyof ValuesMap>(id: Id) => ({
95
111
  ...state.filters[id],
96
112
  isActive: state.activeFilterIds.has(id),
97
113
  value: values[id],
98
114
  }),
115
+ isClearable,
99
116
  getActiveFilterValues: () => values,
100
117
  toggleOpenFilter: <Id extends keyof ValuesMap>(
101
118
  id: Id,
@@ -163,11 +180,6 @@ export const FilterBarProvider = <ValuesMap extends FiltersValues>({
163
180
  }
164
181
  }, [filters])
165
182
 
166
- const activeFilters = Array.from(
167
- state.activeFilterIds,
168
- id => mappedFilters[id]
169
- )
170
-
171
183
  return (
172
184
  <FilterBarContext.Provider
173
185
  // @note: Context object cannot be generic, thus the type-casting to a looser type
@@ -32,6 +32,7 @@ describe("filterBarStateReducer", () => {
32
32
  values: {},
33
33
  dependentFilterIds: new Set(),
34
34
  hasUpdatedValues: true,
35
+ hasRemovableFilter: false,
35
36
  } satisfies FilterBarState<Values>
36
37
 
37
38
  const newState = filterBarStateReducer<Values>(state, {
@@ -50,6 +51,7 @@ describe("filterBarStateReducer", () => {
50
51
  values: {},
51
52
  dependentFilterIds: new Set(),
52
53
  hasUpdatedValues: false,
54
+ hasRemovableFilter: false,
53
55
  } satisfies FilterBarState<Values>
54
56
 
55
57
  const newState = filterBarStateReducer<Values>(state, {
@@ -71,6 +73,7 @@ describe("filterBarStateReducer", () => {
71
73
  values: { flavour: "jasmine" },
72
74
  dependentFilterIds: new Set(),
73
75
  hasUpdatedValues: false,
76
+ hasRemovableFilter: false,
74
77
  } satisfies FilterBarState<Values>
75
78
 
76
79
  const newState = filterBarStateReducer<Values>(state, {
@@ -49,7 +49,7 @@ export const filterBarStateReducer = <ValuesMap extends FiltersValues>(
49
49
 
50
50
  case "deactivate_filter":
51
51
  state.activeFilterIds.delete(action.id)
52
- state.values[action.id] = undefined
52
+ delete state.values[action.id]
53
53
  return {
54
54
  ...updateDependentFilters(state),
55
55
  hasUpdatedValues: true,
@@ -17,6 +17,16 @@ const filters = [
17
17
  },
18
18
  ] satisfies Filters<Values>
19
19
 
20
+ const filtersNoRemovable = [
21
+ { id: "flavour", name: "Flavour", Component: <div /> },
22
+ {
23
+ id: "sugarLevel",
24
+ name: "Sugar Level",
25
+ Component: <div />,
26
+ isRemovable: false,
27
+ },
28
+ ] satisfies Filters<Values>
29
+
20
30
  describe("setupFilterBarState()", () => {
21
31
  it("sets up the base state correctly", () => {
22
32
  const values = { flavour: "jasmine", sugarLevel: 50 }
@@ -41,6 +51,7 @@ describe("setupFilterBarState()", () => {
41
51
  values,
42
52
  dependentFilterIds: new Set<keyof Values>(),
43
53
  hasUpdatedValues: false,
54
+ hasRemovableFilter: true,
44
55
  })
45
56
  })
46
57
 
@@ -65,4 +76,33 @@ describe("setupFilterBarState()", () => {
65
76
  expect(state.hasUpdatedValues).toBe(true)
66
77
  })
67
78
  })
79
+
80
+ describe("Removable filters", () => {
81
+ it("hasRemovableFilter as false when there's no removable filters", () => {
82
+ const values = {}
83
+ expect(setupFilterBarState<Values>(filtersNoRemovable, values)).toEqual({
84
+ filters: {
85
+ flavour: {
86
+ id: "flavour",
87
+ name: "Flavour",
88
+ isRemovable: false,
89
+ isOpen: false,
90
+ isUsable: true,
91
+ },
92
+ sugarLevel: {
93
+ id: "sugarLevel",
94
+ name: "Sugar Level",
95
+ isRemovable: false,
96
+ isOpen: false,
97
+ isUsable: true,
98
+ },
99
+ },
100
+ activeFilterIds: new Set(["flavour", "sugarLevel"]),
101
+ values,
102
+ dependentFilterIds: new Set<keyof Values>(),
103
+ hasUpdatedValues: false,
104
+ hasRemovableFilter: false,
105
+ })
106
+ })
107
+ })
68
108
  })
@@ -27,6 +27,10 @@ export const setupFilterBarState = <ValuesMap extends FiltersValues>(
27
27
  baseState.activeFilterIds.add(id)
28
28
  }
29
29
 
30
+ if (isRemovable) {
31
+ baseState.hasRemovableFilter = true
32
+ }
33
+
30
34
  return baseState
31
35
  },
32
36
  {
@@ -35,6 +39,7 @@ export const setupFilterBarState = <ValuesMap extends FiltersValues>(
35
39
  values,
36
40
  dependentFilterIds: new Set(),
37
41
  hasUpdatedValues: false,
42
+ hasRemovableFilter: false,
38
43
  focusId: undefined,
39
44
  } as FilterBarState<ValuesMap>
40
45
  )
@@ -31,6 +31,7 @@ describe("filterBarStateReducer: update_single_filter", () => {
31
31
  values: {},
32
32
  dependentFilterIds: new Set(),
33
33
  hasUpdatedValues: false,
34
+ hasRemovableFilter: false,
34
35
  } satisfies FilterBarState<Values>
35
36
 
36
37
  const newState = filterBarStateReducer<Values>(state, {
@@ -49,6 +50,7 @@ describe("filterBarStateReducer: update_single_filter", () => {
49
50
  values: {},
50
51
  dependentFilterIds: new Set(),
51
52
  hasUpdatedValues: false,
53
+ hasRemovableFilter: false,
52
54
  } satisfies FilterBarState<Values>
53
55
 
54
56
  expect(state.filters.flavour.isOpen).toBe(false)
@@ -35,6 +35,7 @@ describe("filterBarStateReducer: update_values", () => {
35
35
  values: {},
36
36
  dependentFilterIds: new Set(),
37
37
  hasUpdatedValues: false,
38
+ hasRemovableFilter: false,
38
39
  } satisfies FilterBarState<Values>
39
40
 
40
41
  const newState = filterBarStateReducer<Values>(state, {
@@ -65,6 +66,7 @@ describe("filterBarStateReducer: update_values", () => {
65
66
  values: { sugarLevel: 50 },
66
67
  dependentFilterIds: new Set<keyof Values>(["sugarLevel"]),
67
68
  hasUpdatedValues: false,
69
+ hasRemovableFilter: false,
68
70
  } satisfies FilterBarState<Values>
69
71
 
70
72
  const newState = filterBarStateReducer<Values>(state, {
@@ -93,6 +95,7 @@ describe("filterBarStateReducer: update_values", () => {
93
95
  values: {},
94
96
  dependentFilterIds: new Set<keyof Values>(["sugarLevel"]),
95
97
  hasUpdatedValues: false,
98
+ hasRemovableFilter: false,
96
99
  } satisfies FilterBarState<Values>
97
100
 
98
101
  const newState = filterBarStateReducer<Values>(state, {
@@ -122,6 +125,7 @@ describe("filterBarStateReducer: update_values", () => {
122
125
  values: {},
123
126
  dependentFilterIds: new Set<keyof Values>(["sugarLevel"]),
124
127
  hasUpdatedValues: false,
128
+ hasRemovableFilter: false,
125
129
  } satisfies FilterBarState<Values>
126
130
 
127
131
  const newState = filterBarStateReducer<Values>(state, {
@@ -149,6 +153,7 @@ describe("filterBarStateReducer: update_values", () => {
149
153
  values: {},
150
154
  dependentFilterIds: new Set<keyof Values>(["sugarLevel"]),
151
155
  hasUpdatedValues: false,
156
+ hasRemovableFilter: false,
152
157
  } satisfies FilterBarState<Values>
153
158
 
154
159
  const newState = filterBarStateReducer<Values>(state, {