HeadlessUI has an awesome Combobox component. Check out this example that uses the active (highlighted) option. I've successfully converted this example to typescript:
activeOption
import * as Headless from '@headlessui/react'
import { useState } from 'react'
interface Person {
id: number
name: string
}
const people: Person[] = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
]
function Example() {
const [selectedPerson, setSelectedPerson] = useState<Person | null>(people[0])
const [query, setQuery] = useState<string>('')
const filteredPeople: Person[] =
query === ''
? people
: people.filter((person) => {
return person.name.toLowerCase().includes(query.toLowerCase())
})
return (
<Headless.Combobox
value={selectedPerson}
onChange={setSelectedPerson}
onClose={() => setQuery('')}
>
{({ activeOption }) => (
<>
<Headless.ComboboxInput
aria-label="Assignee"
displayValue={(person: Person | null) => person?.name ?? ''}
onChange={(event) => setQuery(event.target.value)}
/>
<Headless.ComboboxOptions
anchor="bottom"
className="border empty:invisible"
>
{filteredPeople.map((person) => (
<Headless.ComboboxOption
key={person.id}
value={person}
className="data-[focus]:bg-blue-100"
>
{person.name}
</Headless.ComboboxOption>
))}
</Headless.ComboboxOptions>
{activeOption && <div>The currently focused user is: {activeOption.name}</div>}
</>
)}
</Headless.Combobox>
)
}
Now, my goal is to create my own "SuperBox" component that is basically just Combobox
with a few additional props. Here's my attempt...
import * as Headless from '@headlessui/react'
import { ComponentProps, ReactNode } from 'react'
interface SuperBoxProps extends ComponentProps<typeof Headless.Combobox> {
className?: string
children?: ReactNode
}
export function SuperBox({ className, children, ...props }: SuperBoxProps) {
return (
<Headless.Combobox
data-slot="control"
className={className}
{...props}
>
<div className="relative">{children}</div>
</Headless.Combobox>
)
}
Unfortunately, my when I replace Headless.Combobox
with SuperBox
in the example above, I get the following typescript error:
Type '({ activeOption }: { activeOption: any; }) => Element' is not assignable to type 'ReactNode'
I understand that children
in this case is a function, not a ReactNode
, but I can't figure out how to adjust my type definitions to support this.
i think this is what you have to do.
import * as Headless from '@headlessui/react';
import {
ComponentProps,
JSXElementConstructor,
ReactElement,
ReactNode,
} from 'react';
type ComboboxRenderPropArg = {
activeOption: unknown; // Update with the correct type if you know it
open: boolean;
};
interface SuperBoxProps extends ComponentProps<typeof Headless.Combobox> {
className?: string;
children?:
| ReactNode
| ((
bag: ComboboxRenderPropArg
) => ReactElement<any, string | JSXElementConstructor<any>>);
}
export function SuperBox({ className, children, ...props }: SuperBoxProps) {
return (
<Headless.Combobox data-slot="control" className={className} {...props}>
<div className="relative">
{typeof children === 'function'
? children({ activeOption: null, open: false })
: children}
</div>
</Headless.Combobox>
);
}
and then, you are good to go.