Created
September 25, 2024 13:43
-
-
Save Pagebakers/ef76cd17d91448e156e428ce8818c2cd to your computer and use it in GitHub Desktop.
Chakra UI Combobox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useMemo, useState } from 'react' | |
import { | |
Combobox, | |
ComboboxContent, | |
ComboboxControl, | |
ComboboxInput, | |
ComboboxItem, | |
ComboboxPositioner, | |
type ComboboxProps, | |
ComboboxTrigger, | |
} from './combobox' | |
import { ChevronDownIcon } from '@saas-ui/react' | |
interface ComboboxFieldProps | |
extends Omit< | |
ComboboxProps<{ value: string; label: string }>, | |
'items' | 'value' | 'onValueChange' | 'onChange' | |
> { | |
value?: string | |
onChange?: (value: string) => void | |
options: { value: string; label: string }[] | |
} | |
export const ComboboxField = createField( | |
forwardRef<ComboboxFieldProps, 'input'>((props, ref) => { | |
const { value, onChange, ...rest } = props | |
const [items, setItems] = useState(props.options) | |
const handleInputChange = (value: string) => { | |
const items = props.options.filter((option) => | |
option.label.toLowerCase().includes(value.toLowerCase()), | |
) | |
setItems(items.length > 0 ? items : props.options) | |
} | |
const val = useMemo(() => (value ? [value] : []), [value]) | |
return ( | |
<Combobox | |
openOnClick | |
allowCustomValue={false} | |
inputBehavior="autocomplete" | |
value={val} | |
onOpenChange={() => { | |
setItems(props.options) | |
}} | |
onValueChange={({ value }) => { | |
onChange?.(value[0]) | |
}} | |
onInputValueChange={(event) => { | |
handleInputChange(event.inputValue) | |
}} | |
{...rest} | |
items={items} | |
> | |
<ComboboxControl> | |
<ComboboxInput ref={ref} /> | |
<ComboboxTrigger> | |
<ChevronDownIcon /> | |
</ComboboxTrigger> | |
</ComboboxControl> | |
<ComboboxPositioner> | |
<ComboboxContent> | |
{items.map((option) => ( | |
<ComboboxItem key={option.value} item={option}> | |
{option.label} | |
</ComboboxItem> | |
))} | |
</ComboboxContent> | |
</ComboboxPositioner> | |
</Combobox> | |
) | |
}), | |
{ | |
isControlled: true, | |
}, | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { forwardRef } from 'react' | |
import { | |
Combobox as ArkCombobox, | |
ComboboxContentProps, | |
ComboboxInputProps, | |
ComboboxItemProps, | |
} from '@ark-ui/react' | |
import type { ComboboxRootProps as ArkComboboxProps } from '@ark-ui/react' | |
import type { CollectionItem } from '@ark-ui/react/dist/types' | |
import { | |
HTMLChakraProps, | |
Text, | |
chakra, | |
useMultiStyleConfig, | |
} from '@chakra-ui/react' | |
import { CheckIcon } from '@saas-ui/react' | |
export interface ComboboxProps<T extends CollectionItem> | |
extends ArkComboboxProps<T> {} | |
export const Combobox = chakra(ArkCombobox.Root) | |
export const ComboboxControl = chakra(ArkCombobox.Control, { | |
baseStyle: { | |
display: 'flex', | |
justifyContent: 'space-between', | |
position: 'relative', | |
isolation: 'isolate', | |
}, | |
}) | |
export const ComboboxTrigger = chakra(ArkCombobox.Trigger, { | |
baseStyle: { | |
position: 'absolute', | |
right: 1, | |
top: '50%', | |
transform: 'translateY(-50%)', | |
fontSize: 'sm', | |
display: 'flex', | |
alignItems: 'center', | |
justifyContent: 'center', | |
zIndex: 1, | |
px: 2, | |
}, | |
}) | |
const StyledInput = chakra(ArkCombobox.Input) | |
export const ComboboxInput = forwardRef< | |
HTMLInputElement, | |
ComboboxInputProps & HTMLChakraProps<'input'> | |
>((props, ref) => { | |
const styles = useMultiStyleConfig('Input') | |
return <StyledInput __css={styles['field']} {...props} ref={ref} /> | |
}) | |
const StyledContent = chakra(ArkCombobox.Content) | |
export const ComboboxContent = ( | |
props: ComboboxContentProps & HTMLChakraProps<'div'>, | |
) => { | |
const styles = useMultiStyleConfig('Menu') | |
return ( | |
<StyledContent | |
__css={styles['list']} | |
maxHeight="var(--available-height)" | |
overflowY="auto" | |
{...props} | |
/> | |
) | |
} | |
const StyledItem = chakra(ArkCombobox.Item) | |
export const ComboboxItem = function ( | |
props: ComboboxItemProps & HTMLChakraProps<'div'>, | |
) { | |
const styles = useMultiStyleConfig('Menu') | |
const itemStyles = { | |
...styles['item'], | |
cursor: 'pointer', | |
display: 'flex', | |
justifyContent: 'space-between', | |
/* @ts-expect-error _focus actually exists */ | |
['&[data-highlighted]']: styles['item']['_focus'], | |
} | |
return ( | |
<StyledItem __css={itemStyles} {...props}> | |
<ArkCombobox.ItemText asChild> | |
<Text>{props.children}</Text> | |
</ArkCombobox.ItemText> | |
<ArkCombobox.ItemIndicator> | |
<CheckIcon /> | |
</ArkCombobox.ItemIndicator> | |
</StyledItem> | |
) | |
} | |
export const ComboboxPositioner = chakra(ArkCombobox.Positioner) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment