Skip to content

Instantly share code, notes, and snippets.

@Pagebakers
Created September 25, 2024 13:43
Show Gist options
  • Save Pagebakers/ef76cd17d91448e156e428ce8818c2cd to your computer and use it in GitHub Desktop.
Save Pagebakers/ef76cd17d91448e156e428ce8818c2cd to your computer and use it in GitHub Desktop.
Chakra UI Combobox
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,
},
)
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