Skip to content

Instantly share code, notes, and snippets.

@litewarp
Created March 16, 2021 19:37
Show Gist options
  • Save litewarp/9c1f8ce380367795d0a4dc25c56d7da6 to your computer and use it in GitHub Desktop.
Save litewarp/9c1f8ce380367795d0a4dc25c56d7da6 to your computer and use it in GitHub Desktop.
Downshift Combobox Compound Component
import React from 'react'
import {
useCombobox,
UseComboboxProps,
UseComboboxReturnValue,
} from 'downshift'
type Item = Record<string, any>
const ComboboxContext = React.createContext<UseComboboxReturnValue<Item>>(null)
export const useComboboxContext = (): UseComboboxReturnValue<Item> =>
React.useContext(ComboboxContext)
interface IComboboxComposition {
Container: React.FC<ComboboxContainerProps>
Button: React.FC<ComboboxButtonProps>
Label: React.FC<ComboboxLabelProps>
Menu: React.FC<ComboboxMenuProps>
Input: React.FC<ComboboxInputProps>
Option: React.FC<ComboboxOptionProps>
}
const Combobox: React.FC<UseComboboxProps<Item>> & IComboboxComposition = ({
children,
...props
}) => {
const downshiftReturnValue = useCombobox(props)
const context = React.useMemo(() => downshiftReturnValue, [
downshiftReturnValue,
])
return (
<ComboboxContext.Provider value={context}>
{children}
</ComboboxContext.Provider>
)
}
type ComboboxButtonProps = {
[key: string]: unknown
} & React.HTMLProps<HTMLButtonElement>
const ComboboxButton: React.FC<ComboboxButtonProps> = ({
children,
...props
}) => {
const { getToggleButtonProps } = useComboboxContext()
return (
<button
{...getToggleButtonProps(props)}
type="button"
aria-label={`toggle menu`}
>
{children}
</button>
)
}
type ComboboxMenuProps = {
as?: 'div'
[key: string]: unknown
} & React.HTMLProps<HTMLDivElement | HTMLUListElement>
const ComboboxMenu: React.FC<ComboboxMenuProps> = ({
as,
children,
...props
}) => {
const { getMenuProps } = useComboboxContext()
if (as === 'div') {
return <div {...getMenuProps(props)}>{children}</div>
} else {
return <ul {...getMenuProps(props)}>{children}</ul>
}
}
type ComboboxInputProps = {
[key: string]: unknown
} & React.HTMLProps<HTMLInputElement>
const ComboboxInput: React.FC<ComboboxInputProps> = ({
children,
...props
}) => {
const { getInputProps } = useComboboxContext()
return <div {...getInputProps(props)}>{children}</div>
}
type ComboboxLabelProps = {
[key: string]: unknown
} & React.HTMLProps<HTMLLabelElement>
const ComboboxLabel: React.FC<ComboboxLabelProps> = ({
children,
...props
}) => {
const { getLabelProps } = useComboboxContext()
return <label {...getLabelProps(props)}>{children}</label>
}
type ComboboxContainerProps = {
[key: string]: unknown
} & React.HTMLProps<HTMLDivElement>
const ComboboxContainer: React.FC<ComboboxContainerProps> = ({
children,
...props
}) => {
const { getComboboxProps } = useComboboxContext()
return <div {...getComboboxProps(props)}>{children}</div>
}
type ComboboxOptionProps = {
[key: string]: unknown
as?: 'div'
item: Item
index: number
} & React.HTMLProps<HTMLDivElement | HTMLLIElement>
const ComboboxOption: React.FC<ComboboxOptionProps> = ({
as,
children,
...props
}) => {
const { getItemProps } = useComboboxContext()
if (as === 'div') {
return <div {...getItemProps(props)}>{children}</div>
} else {
return <li {...getItemProps(props)}>{children}</li>
}
}
// ----
Combobox.Container = ComboboxContainer
Combobox.Button = ComboboxButton
Combobox.Label = ComboboxLabel
Combobox.Menu = ComboboxMenu
Combobox.Input = ComboboxInput
Combobox.Option = ComboboxOption
export { Combobox }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment