import * as React from 'react'; import { cn } from '@renderer/lib/utils'; import { Command as CommandPrimitive } from 'cmdk'; import { Check, ChevronsUpDown, X } from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from './popover'; export interface ComboboxOption { value: string; label: string; description?: string; /** Extra data for renderOption (e.g. sessionCount, path). */ meta?: Record; } interface ComboboxProps { options: ComboboxOption[]; value: string; onValueChange: (value: string) => void; placeholder?: string; searchPlaceholder?: string; emptyMessage?: string; disabled?: boolean; className?: string; renderOption?: (option: ComboboxOption, isSelected: boolean, query: string) => React.ReactNode; /** Custom label renderer for the trigger button (closed state). */ renderTriggerLabel?: (option: ComboboxOption) => React.ReactNode; /** Label for the reset item shown at the top of the dropdown. */ resetLabel?: string; /** Called when the user clicks the reset item. */ onReset?: () => void; } export const Combobox = ({ options, value, onValueChange, placeholder = 'Select...', searchPlaceholder = 'Search...', emptyMessage = 'Nothing found.', disabled = false, className, renderOption, renderTriggerLabel, resetLabel, onReset, }: ComboboxProps): React.JSX.Element => { const [open, setOpen] = React.useState(false); const [search, setSearch] = React.useState(''); const listboxId = React.useId(); const selectedOption = options.find((opt) => opt.value === value); return (
e.stopPropagation()} > {emptyMessage} {onReset && value && !search.trim() ? ( { onReset(); setOpen(false); setSearch(''); }} className="relative flex w-full cursor-default select-none items-center rounded-sm px-2 py-1.5 text-xs outline-none data-[selected=true]:bg-[var(--color-surface-raised)] data-[selected=true]:text-[var(--color-text)]" > {resetLabel ?? 'Reset selection'} ) : null} {options .filter((opt) => { if (!search.trim()) return true; const q = search.toLowerCase(); return ( opt.label.toLowerCase().includes(q) || opt.value.toLowerCase().includes(q) || (opt.description?.toLowerCase().includes(q) ?? false) ); }) .map((option) => { const isSelected = option.value === value; return ( { onValueChange(option.value); setOpen(false); setSearch(''); }} className="relative flex w-full cursor-default select-none items-center rounded-sm px-2 py-1.5 text-xs outline-none data-[selected=true]:bg-[var(--color-surface-raised)] data-[selected=true]:text-[var(--color-text)]" > {renderOption ? ( renderOption(option, isSelected, search) ) : ( <> {isSelected ? : null}

{option.label}

{option.description ? (

{option.description}

) : null}
)}
); })}
); };