Combobox
Autocomplete input and command palette with a list of suggestions.
'use client'
import Button from '@dinui/react/button'import Command from '@dinui/react/command'import Popover from '@dinui/react/popover'import { cn } from '@dinui/react/utils'import { IconCheck, IconSelector } from '@tabler/icons-react'import * as React from 'react'
const frameworks = [ { value: 'next.js', label: 'Next.js', }, { value: 'sveltekit', label: 'SvelteKit', }, { value: 'nuxt.js', label: 'Nuxt.js', }, { value: 'remix', label: 'Remix', }, { value: 'astro', label: 'Astro', },]
export default function ComboboxDemo() { const [open, setOpen] = React.useState(false) const [value, setValue] = React.useState('')
return ( <Popover open={open} onOpenChange={setOpen}> <Popover.Trigger asChild> <Button variant="outline" role="combobox" aria-expanded={open} className="w-[200px] justify-between" > {value ? frameworks.find((framework) => framework.value === value)?.label : 'Select framework...'}
<Button.RightIcon className="text-fg-weaker"> <IconSelector /> </Button.RightIcon> </Button> </Popover.Trigger> <Popover.Content className="w-[200px] p-0"> <Command> <Command.Input placeholder="Search framework..." className="h-9" /> <Command.List> <Command.Empty>No framework found.</Command.Empty> <Command.Group> {frameworks.map((framework) => ( <Command.Item key={framework.value} value={framework.value} onSelect={(currentValue) => { setValue(currentValue === value ? '' : currentValue) setOpen(false) }} > {framework.label} <IconCheck className={cn( 'ml-auto h-4 w-4', value === framework.value ? 'opacity-100' : 'opacity-0', )} /> </Command.Item> ))} </Command.Group> </Command.List> </Command> </Popover.Content> </Popover> )}Popover
Status
'use client'
import Button from '@dinui/react/button'import Command from '@dinui/react/command'import Popover from '@dinui/react/popover'import * as React from 'react'
type Status = { value: string label: string}
const statuses: Status[] = [ { value: 'backlog', label: 'Backlog', }, { value: 'todo', label: 'Todo', }, { value: 'in progress', label: 'In Progress', }, { value: 'done', label: 'Done', }, { value: 'canceled', label: 'Canceled', },]
export default function ComboboxPopover() { const [open, setOpen] = React.useState(false) const [selectedStatus, setSelectedStatus] = React.useState<Status | null>(null)
return ( <div className="flex items-center space-x-4"> <p className="text-sm text-fg-weaker">Status</p> <Popover open={open} onOpenChange={setOpen}> <Popover.Trigger asChild> <Button variant="outline" className="w-[150px] justify-start"> {selectedStatus ? <>{selectedStatus.label}</> : <>+ Set status</>} </Button> </Popover.Trigger> <Popover.Content className="p-0" side="right" align="start"> <Command> <Command.Input placeholder="Change status..." /> <Command.List> <Command.Empty>No results found.</Command.Empty> <Command.Group> {statuses.map((status) => ( <Command.Item key={status.value} value={status.value} onSelect={(value) => { setSelectedStatus( statuses.find((priority) => priority.value === value) || null, ) setOpen(false) }} > {status.label} </Command.Item> ))} </Command.Group> </Command.List> </Command> </Popover.Content> </Popover> </div> )}Dropdown menu
feature
Create a new project'use client'
import Badge from '@dinui/react/badge'import Button from '@dinui/react/button'import Command from '@dinui/react/command'import DropdownMenu from '@dinui/react/dropdown-menu'import { IconDots } from '@tabler/icons-react'import * as React from 'react'
const labels = [ 'feature', 'bug', 'enhancement', 'documentation', 'design', 'question', 'maintenance',]
export default function ComboboxDropdownMenu() { const [label, setLabel] = React.useState('feature') const [open, setOpen] = React.useState(false)
return ( <div className="flex w-full flex-col items-start justify-between rounded-md border px-4 py-3 sm:flex-row sm:items-center"> <p className="text-sm font-medium leading-none"> <Badge className="mr-2">{label}</Badge> <span className="text-fg-weaker">Create a new project</span> </p> <DropdownMenu open={open} onOpenChange={setOpen}> <DropdownMenu.Trigger asChild> <Button variant="ghost" size="sm" icon> <Button.Icon> <IconDots /> </Button.Icon> </Button> </DropdownMenu.Trigger> <DropdownMenu.Content align="end" className="w-[200px]"> <DropdownMenu.Label>Actions</DropdownMenu.Label> <DropdownMenu.Group> <DropdownMenu.Item>Assign to...</DropdownMenu.Item> <DropdownMenu.Item>Set due date...</DropdownMenu.Item> <DropdownMenu.Separator /> <DropdownMenu.Sub> <DropdownMenu.Sub.Trigger>Apply label</DropdownMenu.Sub.Trigger> <DropdownMenu.Sub.Content className="p-0"> <Command> <Command.Input placeholder="Filter label..." autoFocus={true} className="h-9" /> <Command.List> <Command.Empty>No label found.</Command.Empty> <Command.Group> {labels.map((label) => ( <Command.Item key={label} value={label} onSelect={(value) => { setLabel(value) setOpen(false) }} > {label} </Command.Item> ))} </Command.Group> </Command.List> </Command> </DropdownMenu.Sub.Content> </DropdownMenu.Sub> <DropdownMenu.Separator /> <DropdownMenu.Item className="text-red-600"> Delete <DropdownMenu.Item.Shortcut>⌘⌫</DropdownMenu.Item.Shortcut> </DropdownMenu.Item> </DropdownMenu.Group> </DropdownMenu.Content> </DropdownMenu> </div> )}Form
'use client'
import Button from '@dinui/react/button'import Command from '@dinui/react/command'import Form, { useForm } from '@dinui/react/form'import Popover from '@dinui/react/popover'import { cn } from '@dinui/react/utils'import { zodResolver } from '@hookform/resolvers/zod'import { IconCheck, IconSelector } from '@tabler/icons-react'import { z } from 'zod'
const languages = [ { label: 'English', value: 'en' }, { label: 'French', value: 'fr' }, { label: 'German', value: 'de' }, { label: 'Spanish', value: 'es' }, { label: 'Portuguese', value: 'pt' }, { label: 'Russian', value: 'ru' }, { label: 'Japanese', value: 'ja' }, { label: 'Korean', value: 'ko' }, { label: 'Chinese', value: 'zh' },] as const
const FormSchema = z.object({ language: z.string({ required_error: 'Please select a language.', }),})
export default function ComboboxForm() { const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), })
function onSubmit(data: z.infer<typeof FormSchema>) { alert(`You submitted the following values: ${JSON.stringify(data, null, 2)}`) }
return ( <Form form={form} onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <Form.Field control={form.control} name="language" render={({ field }) => ( <Form.Item className="flex flex-col"> <Form.Label>Language</Form.Label> <Popover> <Popover.Trigger asChild> <Form.Control> <Button variant="outline" role="combobox" className={cn('w-[200px] justify-between', !field.value && 'text-fg-weaker')} > {field.value ? languages.find((language) => language.value === field.value)?.label : 'Select language'}
<Button.RightIcon className="size-4 text-fg-weaker"> <IconSelector /> </Button.RightIcon> </Button> </Form.Control> </Popover.Trigger> <Popover.Content className="w-[200px] p-0"> <Command> <Command.Input placeholder="Search framework..." className="h-9" /> <Command.List> <Command.Empty>No framework found.</Command.Empty>
<Command.Group> {languages.map((language) => ( <Command.Item value={language.label} key={language.value} onSelect={() => { form.setValue('language', language.value) }} > {language.label} <IconCheck className={cn( 'ml-auto h-4 w-4', language.value === field.value ? 'opacity-100' : 'opacity-0', )} /> </Command.Item> ))} </Command.Group> </Command.List> </Command> </Popover.Content> </Popover> <Form.Description> This is the language that will be used in the dashboard. </Form.Description> <Form.ErrorMessage /> </Form.Item> )} />
<Button type="submit">Submit</Button> </Form> )}Installation
The Combobox is built using a composition of the <Popover /> and the <Command /> components.
See installation instructions for the Popover and the Command components.