From 3c164fe7b7cb11b53cd6ef5b9cf013b71f6d5934 Mon Sep 17 00:00:00 2001 From: Jake Laderman Date: Thu, 18 Jul 2024 14:59:28 -0400 Subject: [PATCH] feat: add new options to TagMultiSelect (#625) --- src/components/ComboBox.tsx | 3 + src/components/Flyover.tsx | 2 +- src/components/ListBoxItemChipList.tsx | 1 - src/components/TagMultiSelect.tsx | 262 ++++++++++++++----------- 4 files changed, 149 insertions(+), 119 deletions(-) diff --git a/src/components/ComboBox.tsx b/src/components/ComboBox.tsx index 4b33a9aa..1bb01c2c 100644 --- a/src/components/ComboBox.tsx +++ b/src/components/ComboBox.tsx @@ -57,6 +57,7 @@ type ComboBoxProps = Exclude & { onHeaderClick?: () => unknown onFooterClick?: () => unknown startIcon?: ReactNode + endIcon?: ReactNode placement?: Placement width?: string | number maxHeight?: string | number @@ -241,6 +242,7 @@ function ComboBox({ onFooterClick, showArrow, startIcon, + endIcon, placement, width, maxHeight, @@ -537,6 +539,7 @@ function ComboBox({ showClearButton={showClearButton} setIsOpen={setIsOpen} startIcon={startIcon} + endIcon={endIcon} outerInputProps={outerInputProps} loading={loading} hasChips={!!chips} diff --git a/src/components/Flyover.tsx b/src/components/Flyover.tsx index f849033e..324c84fc 100644 --- a/src/components/Flyover.tsx +++ b/src/components/Flyover.tsx @@ -56,7 +56,7 @@ const FlyoverHeaderWrapSC = styled.div(({ theme }) => ({ gap: theme.spacing.small, height: 56, borderBottom: `1px solid ${theme.colors.border}`, - backgroundColor: theme.colors.grey[950], + backgroundColor: theme.colors['fill-zero'], display: 'flex', padding: `${theme.spacing.small}px ${theme.spacing.medium}px`, })) diff --git a/src/components/ListBoxItemChipList.tsx b/src/components/ListBoxItemChipList.tsx index 4edf93da..4ea9ec7e 100644 --- a/src/components/ListBoxItemChipList.tsx +++ b/src/components/ListBoxItemChipList.tsx @@ -73,7 +73,6 @@ const ChipListUnstyled = forwardRef( const ChipList = styled(ChipListUnstyled)(({ theme }) => ({ display: 'flex', - flexDiretion: 'row', flexWrap: 'wrap', justifyContent: 'end', gap: theme.spacing.xxsmall, diff --git a/src/components/TagMultiSelect.tsx b/src/components/TagMultiSelect.tsx index a97dc84f..97bbf721 100644 --- a/src/components/TagMultiSelect.tsx +++ b/src/components/TagMultiSelect.tsx @@ -1,15 +1,21 @@ -import { Flex } from 'honorable' -import { - type ComponentProps, - type Key, - useEffect, - useMemo, - useState, -} from 'react' +import { type ComponentProps, type Key, useMemo, useState } from 'react' -import styled, { useTheme } from 'styled-components' +import styled, { + type DefaultTheme, + type StyledComponent, + useTheme, +} from 'styled-components' -import { Chip, ComboBox, ListBoxItem, Select, SelectButton } from '..' +import { + Chip, + ChipList, + ComboBox, + Flex, + ListBoxItem, + Select, + SelectButton, + type SelectPropsSingle, +} from '..' import { isNonNullable } from '../utils/isNonNullable' @@ -26,24 +32,31 @@ const matchOptions = [ type TagMultiSelectProps = { options: string[] loading: boolean + innerChips?: boolean selectedMatchType?: 'AND' | 'OR' onSelectedTagsChange?: (keys: Set) => void onFilterChange?: (value: string) => void onChangeMatchType?: (value: 'AND' | 'OR') => void + comboBoxProps?: Omit, 'children'> + selectProps?: Omit } function TagMultiSelect({ options, loading, + innerChips = true, selectedMatchType, - onSelectedTagsChange, - onFilterChange, + selectedTagKeys, + setSelectedTagKeys, + inputValue, + setInputValue, onChangeMatchType, -}: TagMultiSelectProps) { + comboBoxProps, + selectProps, + ...props +}: TagMultiSelectProps & ComponentProps>) { const theme = useTheme() - const [selectedTagKeys, setSelectedTagKeys] = useState(new Set()) const selectedTagArr = useMemo(() => [...selectedTagKeys], [selectedTagKeys]) - const [inputValue, setInputValue] = useState('') const [isOpen, setIsOpen] = useState(false) const [searchLogic, setSearchLogic] = useState( selectedMatchType || matchOptions[0].value @@ -58,118 +71,133 @@ function TagMultiSelect({ } } - useEffect(() => { - onSelectedTagsChange?.(selectedTagKeys) - }, [selectedTagKeys, onSelectedTagsChange]) - - useEffect(() => { - onFilterChange?.(inputValue) - }, [inputValue, onFilterChange]) - - const onInputChange: ComponentProps['onInputChange'] = ( - value - ) => { - setInputValue(value) - } - return ( - - - ({ - key, - children: key, - }))} - onDeleteChip={(chipKey) => { - const newKeys = new Set(selectedTagKeys) - - newKeys.delete(chipKey) - setSelectedTagKeys(newKeys) - }} - inputProps={{ - placeholder: 'Search Tags...', - style: { - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - backgroundColor: theme.colors['fill-one'], - }, - }} - onOpenChange={(isOpen, _trigger) => { - setIsOpen(isOpen) - }} - maxHeight={232} - allowsEmptyCollection - loading={loading} - containerProps={{ style: { flexGrow: 1 } }} - showArrow={false} - > - {options - .map((tagStr) => { - if (selectedTagKeys.has(tagStr)) { - return null - } - - return ( - - {tagStr} - - } - textValue={tagStr} - /> - ) - }) - .filter(isNonNullable)} - - + + + + setInputValue(value)} + chips={ + innerChips + ? selectedTagArr.map((key) => ({ + key, + children: key, + })) + : undefined + } + onDeleteChip={(chipKey) => { + const newKeys = new Set(selectedTagKeys) + + newKeys.delete(chipKey) + setSelectedTagKeys(newKeys) + }} + onOpenChange={(isOpen, _trigger) => { + setIsOpen(isOpen) + }} + maxHeight={232} + allowsEmptyCollection + loading={loading} + containerProps={{ style: { flexGrow: 1 } }} + showArrow={false} + {...comboBoxProps} + inputProps={{ + placeholder: 'Search Tags...', + ...comboBoxProps?.inputProps, + style: { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + backgroundColor: theme.colors['fill-one'], + ...comboBoxProps?.inputProps?.style, + }, + }} + > + {options + .map((tagStr) => { + if (selectedTagKeys.has(tagStr)) { + return null + } + + return ( + + {tagStr} + + } + textValue={tagStr} + /> + ) + }) + .filter(isNonNullable)} + + + {!(innerChips || selectedTagArr.length === 0) && ( + true} + onClick={(key: Key) => { + const newKeys = new Set(selectedTagArr) + + newKeys.delete(key) + setSelectedTagKeys(newKeys) + }} + /> + )} + ) } -export type { TagMultiSelectProps } export { TagMultiSelect } +export type { TagMultiSelectProps } + +const TagMultiSelectWrapperSC = styled.div(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing.xsmall, +})) const MultiSelectMatchButtonContainer = styled.div` > div { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; - width: 150px; + minwidth: fit-content; + text-wrap: nowrap; } `