From 615461e1647d287bbeefe5bc37399a5d98b2a222 Mon Sep 17 00:00:00 2001 From: tnagorra Date: Mon, 18 Apr 2022 16:44:40 +0545 Subject: [PATCH] Only sort/filter options when sortFunction is defined - Add prop to not move selected options to the top - Simplify props for SelectInput and MultiSelectInput --- src/components/MultiSelectInput/index.tsx | 5 +- .../SearchMultiSelectInput/index.tsx | 51 +++++++++++-------- src/components/SearchSelectInput/index.tsx | 30 +++++++---- src/components/SelectInput/index.tsx | 47 ++--------------- storybook/stories/Calendar.stories.tsx | 1 + .../SearchMultiSelectInput.stories.tsx | 6 +++ storybook/stories/SelectInput.stories.tsx | 6 +++ 7 files changed, 70 insertions(+), 76 deletions(-) diff --git a/src/components/MultiSelectInput/index.tsx b/src/components/MultiSelectInput/index.tsx index 7a93744e..5d2e1f39 100644 --- a/src/components/MultiSelectInput/index.tsx +++ b/src/components/MultiSelectInput/index.tsx @@ -8,6 +8,8 @@ type Def = { containerClassName?: string }; type NameType = string | number | undefined; type OptionKey = string | number; +// FIXME: the omissions is not correct +// we need multiple omission for SearchMultiSelectInputProps export type Props< T extends OptionKey, K extends NameType, @@ -21,16 +23,13 @@ function MultiSelectInput, ) { const { - name, options, - totalOptionsCount, // eslint-disable-line no-unused-vars, @typescript-eslint/no-unused-vars ...otherProps } = props; return ( void; selectedOptionContainerClassName?: string; selectionListShown?: boolean; + selectedOptionsAtTop?: boolean; + ellipsizeOptions?: boolean; }, OMISSION> & ( SelectInputContainerProps -) & { - ellipsizeOptions?: boolean; -}; +); const emptyList: unknown[] = []; @@ -100,6 +100,7 @@ function SearchMultiSelectInput< readOnly, inputDescription, ellipsizeOptions, + selectedOptionsAtTop = true, ...otherProps } = props; @@ -118,13 +119,6 @@ function SearchMultiSelectInput< [key: string]: boolean, }>({}); - const optionsMap = useMemo( - () => ( - listToMap(options, keySelector, (i) => i) - ), - [options, keySelector], - ); - const optionsLabelMap = useMemo( () => ( listToMap(options, keySelector, labelSelector) @@ -139,6 +133,13 @@ function SearchMultiSelectInput< [value, optionsLabelMap], ); + const optionsMap = useMemo( + () => ( + listToMap(options, keySelector, (i) => i) + ), + [options, keySelector], + ); + // NOTE: we can skip this calculation if optionsShowInitially is false const selectedOptions = useMemo( () => value.map((valueKey) => optionsMap[valueKey]).filter(isDefined), @@ -147,10 +148,18 @@ function SearchMultiSelectInput< const realOptions = useMemo( () => { - const allOptions = unique( - [...searchOptions, ...selectedOptions], - keySelector, - ); + const allOptions = searchInputValue + ? searchOptions + : unique( + [...searchOptions, ...selectedOptions], + keySelector, + ); + + if (!selectedOptionsAtTop) { + return sortFunction + ? sortFunction(allOptions, searchInputValue, labelSelector) + : allOptions; + } const initiallySelected = allOptions .filter((item) => selectedKeys[keySelector(item)]); @@ -159,13 +168,13 @@ function SearchMultiSelectInput< if (sortFunction) { return [ - ...rankedSearchOnList(initiallySelected, searchInputValue, labelSelector), + ...sortFunction(initiallySelected, searchInputValue, labelSelector), ...sortFunction(initiallyNotSelected, searchInputValue, labelSelector), ]; } return [ - ...rankedSearchOnList(initiallySelected, searchInputValue, labelSelector), + ...initiallySelected, ...initiallyNotSelected, ]; }, @@ -177,6 +186,7 @@ function SearchMultiSelectInput< selectedKeys, selectedOptions, sortFunction, + selectedOptionsAtTop, ], ); @@ -218,6 +228,7 @@ function SearchMultiSelectInput< const optionRendererParams = useCallback( (key: OptionKey, option: O) => { + // FIXME: use map const isActive = value.findIndex((item) => item === key) !== -1; return { @@ -228,7 +239,7 @@ function SearchMultiSelectInput< ellipsize: ellipsizeOptions, }; }, - [labelSelector, value, optionLabelSelector, ellipsizeOptions], + [value, labelSelector, optionLabelSelector, ellipsizeOptions], ); // FIXME: value should not be on dependency list @@ -287,9 +298,9 @@ function SearchMultiSelectInput< onFocusedChange={setFocused} focusedKey={focusedKey} onFocusedKeyChange={setFocusedKey} + hasValue={isDefined(value) && value.length > 0} persistentOptionPopup nonClearable={false} - hasValue={isDefined(value) && value.length > 0} disabled={disabled} readOnly={readOnly} inputDescription={( diff --git a/src/components/SearchSelectInput/index.tsx b/src/components/SearchSelectInput/index.tsx index af6fd285..c8f564bd 100644 --- a/src/components/SearchSelectInput/index.tsx +++ b/src/components/SearchSelectInput/index.tsx @@ -11,7 +11,7 @@ import ElementFragments from '../ElementFragments'; import SelectInputContainer, { Props as SelectInputContainerProps, } from '../SelectInputContainer'; -import { rankedSearchOnList, genericMemo } from '../../utils'; +import { genericMemo } from '../../utils'; import styles from './styles.css'; @@ -64,6 +64,8 @@ export type Props< sortFunction?: (options: O[], search: string, labelSelector: (option: O) => string) => O[]; onSearchValueChange?: (value: string) => void; onShowDropdownChange?: (value: boolean) => void; + selectedOptionsAtTop?: boolean; + ellipsizeOptions?: boolean; }, OMISSION> & ( SelectInputContainerProps void; } -) & { - ellipsizeOptions?: boolean; -}; +); const emptyList: unknown[] = []; @@ -125,6 +125,7 @@ function SearchSelectInput< onSearchValueChange, onShowDropdownChange, ellipsizeOptions, + selectedOptionsAtTop = true, ...otherProps } = props; @@ -162,10 +163,18 @@ function SearchSelectInput< const realOptions = useMemo( () => { - const allOptions = unique( - [...searchOptions, ...selectedOptions], - keySelector, - ); + const allOptions = searchInputValue + ? searchOptions + : unique( + [...searchOptions, ...selectedOptions], + keySelector, + ); + + if (!selectedOptionsAtTop) { + return sortFunction + ? sortFunction(allOptions, searchInputValue, labelSelector) + : allOptions; + } const initiallySelected = allOptions .filter((item) => selectedKeys[keySelector(item)]); @@ -174,13 +183,13 @@ function SearchSelectInput< if (sortFunction) { return [ - ...rankedSearchOnList(initiallySelected, searchInputValue, labelSelector), + ...sortFunction(initiallySelected, searchInputValue, labelSelector), ...sortFunction(initiallyNotSelected, searchInputValue, labelSelector), ]; } return [ - ...rankedSearchOnList(initiallySelected, searchInputValue, labelSelector), + ...initiallySelected, ...initiallyNotSelected, ]; }, @@ -192,6 +201,7 @@ function SearchSelectInput< selectedKeys, selectedOptions, sortFunction, + selectedOptionsAtTop, ], ); diff --git a/src/components/SelectInput/index.tsx b/src/components/SelectInput/index.tsx index 00598546..42f128cf 100644 --- a/src/components/SelectInput/index.tsx +++ b/src/components/SelectInput/index.tsx @@ -11,8 +11,8 @@ type Def = { containerClassName?: string }; type OptionKey = string | number; type NameType = string | number | undefined; -const emptyList: unknown[] = []; - +// FIXME: the omissions is not correct +// we need multiple omission for SearchMultiSelectInputProps export type Props< T extends OptionKey, K extends NameType, @@ -26,55 +26,16 @@ function SelectInput, ) { const { - name, options, - labelSelector, - nonClearable, // eslint-disable-line no-unused-vars, @typescript-eslint/no-unused-vars - onChange, // eslint-disable-line no-unused-vars, @typescript-eslint/no-unused-vars - totalOptionsCount, // eslint-disable-line no-unused-vars, @typescript-eslint/no-unused-vars ...otherProps } = props; - const [searchInputValue, setSearchInputValue] = React.useState(''); - - const searchOptions = React.useMemo( - () => rankedSearchOnList(options ?? (emptyList as O[]), searchInputValue, labelSelector), - [options, searchInputValue, labelSelector], - ); - - // NOTE: this looks weird but we need to use typeguard to identify between - // different union types (for onChange and nonClearable) - // eslint-disable-next-line react/destructuring-assignment - if (props.nonClearable) { - return ( - - ); - } return ( ); } diff --git a/storybook/stories/Calendar.stories.tsx b/storybook/stories/Calendar.stories.tsx index 6c5340ef..214164c8 100644 --- a/storybook/stories/Calendar.stories.tsx +++ b/storybook/stories/Calendar.stories.tsx @@ -16,4 +16,5 @@ const Template: Story> = (args) => ( export const Default = Template.bind({}); Default.args = { + initialDate: '2020-10-12', }; diff --git a/storybook/stories/SearchMultiSelectInput.stories.tsx b/storybook/stories/SearchMultiSelectInput.stories.tsx index 885a0f67..b39d9618 100644 --- a/storybook/stories/SearchMultiSelectInput.stories.tsx +++ b/storybook/stories/SearchMultiSelectInput.stories.tsx @@ -84,6 +84,12 @@ Default.args = { value: ['1', '3'], }; +export const NormalSorting = Template.bind({}); +NormalSorting.args = { + value: ['1', '3'], + selectedOptionsAtTop: false, +}; + export const Disabled = Template.bind({}); Disabled.args = { value: ['1', '3'], diff --git a/storybook/stories/SelectInput.stories.tsx b/storybook/stories/SelectInput.stories.tsx index 557a68b6..dcf2606a 100644 --- a/storybook/stories/SelectInput.stories.tsx +++ b/storybook/stories/SelectInput.stories.tsx @@ -59,6 +59,12 @@ Default.args = { ellipsizeOptions: true, }; +export const NormalSorting = Template.bind({}); +NormalSorting.args = { + value: '1', + selectedOptionsAtTop: false, +}; + export const Disabled = Template.bind({}); Disabled.args = { value: '1',