From 63496dd506db30dfbf37cfc8e784afdf8bc0ba2d Mon Sep 17 00:00:00 2001 From: Andrew Bain Date: Tue, 5 Dec 2023 11:35:43 +0000 Subject: [PATCH] feat(filterable-select): add disableFiltering prop --- .../filterable-select.component.tsx | 62 ++++++++++-------- .../filterable-select.spec.tsx | 32 ++++++++++ .../filterable-select.stories.mdx | 11 ++++ .../filterable-select.stories.tsx | 64 ++++++++++++++++++- 4 files changed, 141 insertions(+), 28 deletions(-) diff --git a/src/components/select/filterable-select/filterable-select.component.tsx b/src/components/select/filterable-select/filterable-select.component.tsx index d3ffb9dbd2..957dc51b4c 100644 --- a/src/components/select/filterable-select/filterable-select.component.tsx +++ b/src/components/select/filterable-select/filterable-select.component.tsx @@ -84,6 +84,9 @@ export interface FilterableSelectProps * Higher values make for smoother scrolling but may impact performance. * Only used if the `enableVirtualScroll` prop is set. */ virtualScrollOverscan?: number; + /** Boolean to disable automatic filtering and highlighting of options. + * This allows custom filtering and option styling to be performed outside of the component when the filter text changes. */ + disableFiltering?: boolean; } export const FilterableSelect = React.forwardRef( @@ -125,6 +128,7 @@ export const FilterableSelect = React.forwardRef( inputRef, enableVirtualScroll, virtualScrollOverscan, + disableFiltering = false, ...textboxProps }: FilterableSelectProps, ref @@ -619,33 +623,37 @@ export const FilterableSelect = React.forwardRef( }; } - const selectList = ( - + const selectListProps = { + ref: listboxRef, + id: selectListId.current, + labelId, + anchorElement: textboxRef?.parentElement || undefined, + onSelect: onSelectOption, + onSelectListClose, + onMouseDown: handleListMouseDown, + filterText, + highlightedValue, + noResultsMessage, + disablePortal, + listActionButton, + listMaxHeight, + onListAction: handleOnListAction, + isLoading, + onListScrollBottom, + tableHeader, + multiColumn, + loaderDataRole: "filterable-select-list-loader", + listPlacement, + flipEnabled, + isOpen, + enableVirtualScroll, + virtualScrollOverscan, + }; + + const selectList = disableFiltering ? ( + {children} + ) : ( + {children} ); diff --git a/src/components/select/filterable-select/filterable-select.spec.tsx b/src/components/select/filterable-select/filterable-select.spec.tsx index 49e71fa5d1..2f2989db0e 100644 --- a/src/components/select/filterable-select/filterable-select.spec.tsx +++ b/src/components/select/filterable-select/filterable-select.spec.tsx @@ -1320,4 +1320,36 @@ describe("when maxWidth is passed", () => { wrapper.find(InputPresentation) ); }); + + describe("when the disableFiltering prop is set", () => { + it('shows all options when "disableFiltering" is true', () => { + const wrapper = renderSelect({ disableFiltering: true }); + + wrapper.find("input").simulate("change", { target: { value: "red" } }); + wrapper.update(); + + expect(wrapper.find(Option)).toHaveLength(4); + wrapper.unmount(); + }); + + it('hides filtered options when "disableFiltering" is false', () => { + const wrapper = renderSelect({ disableFiltering: false }); + + wrapper.find("input").simulate("change", { target: { value: "red" } }); + wrapper.update(); + + expect(wrapper.find(Option)).toHaveLength(1); + wrapper.unmount(); + }); + + it('hides filtered options when "disableFiltering" is unspecified', () => { + const wrapper = renderSelect(); + + wrapper.find("input").simulate("change", { target: { value: "red" } }); + wrapper.update(); + + expect(wrapper.find(Option)).toHaveLength(1); + wrapper.unmount(); + }); + }); }); diff --git a/src/components/select/filterable-select/filterable-select.stories.mdx b/src/components/select/filterable-select/filterable-select.stories.mdx index 63c4047edd..c22ec17706 100644 --- a/src/components/select/filterable-select/filterable-select.stories.mdx +++ b/src/components/select/filterable-select/filterable-select.stories.mdx @@ -177,6 +177,17 @@ a `selectionConfirmed` property on the emitted event when the enter key is press +### Custom filtering and option styles + +By default, filtering and highlighting of options is handled by the component itself. In order to use custom filtering behaviour, or to use custom styling of option values, the +default filtering can be disabled using the `disableFiltering` prop. + +This allows use-cases like server-side filtering of options, or rich formatting of options. + + + + + ## Props ### Filterable Select diff --git a/src/components/select/filterable-select/filterable-select.stories.tsx b/src/components/select/filterable-select/filterable-select.stories.tsx index 2156c13099..74d7379b05 100644 --- a/src/components/select/filterable-select/filterable-select.stories.tsx +++ b/src/components/select/filterable-select/filterable-select.stories.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useMemo, useCallback } from "react"; import { CustomSelectChangeEvent, FilterableSelect, @@ -753,3 +753,65 @@ export const SelectionConfirmedStory = () => { }; SelectionConfirmedStory.parameters = { chromatic: { disableSnapshot: true } }; + +export const CustomFilterAndOptionStyle = () => { + const [filterText, setFilterText] = useState(""); + const [selectedColor, setSelectedColor] = useState(); + + const data = useMemo( + () => + [ + { text: "Amber", color: "#FFBF00" }, + { text: "Black", color: "#000000" }, + { text: "Blue", color: "#0000FF" }, + { text: "Brown", color: "#A52A2A" }, + { text: "Green", color: "#008000" }, + { text: "Orange", color: "#FFA500" }, + { text: "Pink", color: "#FFC0CB" }, + { text: "Purple", color: "#800080" }, + { text: "Red", color: "#FF0000" }, + { text: "White", color: "#FFFFFF" }, + { text: "Yellow", color: "#FFFF00" }, + ].filter( + ({ text }) => + !filterText || text.toLowerCase().includes(filterText.toLowerCase()) + ), + [filterText] + ); + + const handleChange = useCallback((e: CustomSelectChangeEvent) => { + if (e.selectionConfirmed && e.target?.value) { + setSelectedColor(e.target.value as string); + } else { + setSelectedColor(undefined); + } + }, []); + + return ( + + + Selected Color:{" "} + {selectedColor ? ( + + ) : ( + "[none]" + )} + + + {data.map(({ text, color }) => ( + + ))} + + + ); +};