{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "combobox-glass", "type": "registry:ui", "title": "Combobox Glass", "description": "Glass-themed combobox (searchable select) combining autocomplete and dropdown functionality.\n * Built on Radix UI Popover and cmdk for robust keyboard navigation and filtering.\n *\n * ## Features", "dependencies": [ "lucide-react", "react", "shadcn-glass-ui" ], "registryDependencies": [ "cn", "variants" ], "files": [ { "path": "components/glass/ui/combobox-glass.tsx", "type": "registry:component", "content": "/**\n * ComboBoxGlass Component\n *\n * Glass-themed combobox (searchable select) combining autocomplete and dropdown functionality.\n * Built on Radix UI Popover and cmdk for robust keyboard navigation and filtering.\n *\n * ## Features\n * - Search/filter functionality with real-time results\n * - Keyboard navigation (Arrow keys, Enter, Escape, Tab)\n * - Optional FormFieldWrapper integration (label, error, success states)\n * - Size variants (sm, md, lg, xl) via InputGlass variants\n * - Icon support for both trigger button and individual options\n * - Glass variant styles (glass, frosted, fluted, crystal)\n * - Clearable selection (click selected item to deselect)\n * - Customizable empty state and search placeholder\n * - Disabled state with visual feedback\n * - Popover positioning (side, align) configuration\n *\n * ## CSS Variables\n * - `--input-bg` - Trigger button background\n * - `--input-border` - Trigger button border\n * - `--input-text` - Trigger button text\n * - `--dropdown-bg` - Content background\n * - `--dropdown-border` - Content border\n * - `--dropdown-item-hover` - Item hover background\n * - `--dropdown-item-text` - Item text color\n * - `--dropdown-icon` - Option icon color\n * - `--dropdown-glow` - Content box shadow\n * - `--text-accent` - Check icon color when selected\n * - `--text-muted` - Search icon and empty state text\n *\n * @example Basic usage\n * ```tsx\n * import { ComboBoxGlass, type ComboBoxOption } from 'shadcn-glass-ui'\n * import { useState } from 'react'\n *\n * const options: ComboBoxOption[] = [\n * { value: 'react', label: 'React' },\n * { value: 'vue', label: 'Vue' },\n * { value: 'angular', label: 'Angular' },\n * ]\n *\n * function FrameworkSelector() {\n * const [value, setValue] = useState()\n * return (\n * \n * )\n * }\n * ```\n *\n * @example With form field wrapper\n * ```tsx\n * \n * ```\n *\n * @example With icons\n * ```tsx\n * import { MapPin, Building } from 'lucide-react'\n *\n * const options: ComboBoxOption[] = [\n * { value: 'us', label: 'United States', icon: MapPin },\n * { value: 'uk', label: 'United Kingdom', icon: MapPin },\n * ]\n *\n * \n * ```\n *\n * @example Size variants\n * ```tsx\n * \n * \n * \n * ```\n *\n * @accessibility\n * - WCAG 2.1 AA compliant via Radix UI and cmdk primitives\n * - Full keyboard navigation: Arrow keys, Enter, Escape, Tab\n * - Proper ARIA roles: `combobox`, `listbox`, `option`\n * - Screen reader announcements for search results and selection\n * - Focus management with visible indicators\n * - Error and success states announced to screen readers\n * - Disabled state properly communicated\n *\n * @since v1.0.0\n */\n\n'use client';\n\nimport { useState, useMemo, useCallback, forwardRef, useId } from 'react';\nimport { CheckIcon, ChevronsUpDownIcon, type LucideIcon } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { ButtonGlass } from './button-glass';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from '@/components/ui/command';\nimport { FormFieldWrapper } from '../primitives/form-field-wrapper';\nimport { inputVariants, type InputGlassSize } from '@/lib/variants/input-glass-variants';\nimport {\n getDropdownContentStyles,\n dropdownContentClasses,\n getDropdownItemClasses,\n} from '@/lib/variants/dropdown-content-styles';\nimport { ICON_SIZES } from '../primitives/style-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\n/**\n * Glass variant style options for the combobox content.\n */\nexport type GlassVariant = 'glass' | 'frosted' | 'fluted' | 'crystal';\n\n/**\n * Option item for ComboBoxGlass.\n *\n * @template T - Value type (defaults to string)\n *\n * @example\n * ```tsx\n * const option: ComboBoxOption = {\n * value: 'react',\n * label: 'React',\n * icon: Code,\n * disabled: false,\n * };\n * ```\n */\nexport interface ComboBoxOption {\n /** Unique value for the option */\n readonly value: T;\n /** Display label for the option */\n readonly label: string;\n /** Whether the option is disabled */\n readonly disabled?: boolean;\n /** Optional icon component displayed before the label */\n readonly icon?: LucideIcon;\n}\n\n/**\n * Props for ComboBoxGlass component.\n *\n * @template T - Value type (defaults to string)\n *\n * @example\n * ```tsx\n * const props: ComboBoxGlassProps = {\n * options: [\n * { value: 'us', label: 'United States' },\n * { value: 'uk', label: 'United Kingdom' },\n * ],\n * value: 'us',\n * onValueChange: (val) => setValue(val),\n * label: 'Country',\n * size: 'md',\n * searchable: true,\n * };\n * ```\n */\nexport interface ComboBoxGlassProps {\n /** Available options to display in the dropdown */\n readonly options: readonly ComboBoxOption[];\n\n /** Currently selected value (controlled mode) */\n readonly value?: T;\n\n /** Callback when value changes\n * @param value - New selected value (undefined if cleared)\n */\n readonly onValueChange?: (value: T | undefined) => void;\n\n /** Placeholder text for trigger button when no value selected\n * @default \"Select option...\"\n */\n readonly placeholder?: string;\n\n /** Text shown when search yields no results\n * @default \"No results found.\"\n */\n readonly emptyText?: string;\n\n /** Placeholder for search input\n * @default \"Search...\"\n */\n readonly searchPlaceholder?: string;\n\n /** Glass variant style for dropdown content\n * @default \"glass\"\n */\n readonly glassVariant?: GlassVariant;\n\n /** Disabled state\n * @default false\n */\n readonly disabled?: boolean;\n\n /** Custom className for trigger button container */\n readonly className?: string;\n\n /** Custom className for popover content */\n readonly popoverClassName?: string;\n\n /** Allow clearing selection by clicking selected item\n * @default false\n */\n readonly clearable?: boolean;\n\n /** Popover placement side\n * @default \"bottom\"\n */\n readonly side?: 'top' | 'right' | 'bottom' | 'left';\n\n /** Popover alignment\n * @default \"start\"\n */\n readonly align?: 'start' | 'center' | 'end';\n\n // ========================================\n // FORM FIELD INTEGRATION\n // ========================================\n\n /** Label text displayed above the field (enables FormFieldWrapper) */\n readonly label?: string;\n\n /** Error message - displays in red below the field */\n readonly error?: string;\n\n /** Success message - displays in green if no error */\n readonly success?: string;\n\n /** Shows required asterisk (*) next to label\n * @default false\n */\n readonly required?: boolean;\n\n /** Size variant (affects trigger button height and padding)\n * @default \"md\"\n */\n readonly size?: InputGlassSize;\n\n /** Enable/disable search functionality\n * @default true\n */\n readonly searchable?: boolean;\n\n /** Optional icon for trigger button (displayed before text) */\n readonly icon?: LucideIcon;\n}\n\n// ========================================\n// COMPONENT\n// ========================================\n\nfunction ComboBoxGlassInner(\n {\n options,\n value,\n onValueChange,\n placeholder = 'Select option...',\n emptyText = 'No results found.',\n searchPlaceholder = 'Search...',\n glassVariant = 'glass',\n disabled = false,\n className,\n popoverClassName,\n clearable = false,\n side = 'bottom',\n align = 'start',\n // NEW PROPS\n label,\n error,\n success,\n required = false,\n size = 'md',\n searchable = true,\n icon: TriggerIcon,\n }: ComboBoxGlassProps,\n ref: React.ForwardedRef\n) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const fieldId = useId();\n\n // Find selected option\n const selectedOption = useMemo(\n () => options.find((opt) => opt.value === value),\n [options, value]\n );\n\n // Filter options based on search\n const filteredOptions = useMemo(() => {\n if (!search) return options;\n const searchLower = search.toLowerCase();\n return options.filter((opt) => opt.label.toLowerCase().includes(searchLower));\n }, [options, search]);\n\n // Handle option selection\n const handleSelect = useCallback(\n (optionValue: T) => {\n if (clearable && value === optionValue) {\n onValueChange?.(undefined);\n } else {\n onValueChange?.(optionValue);\n }\n setOpen(false);\n setSearch('');\n },\n [value, onValueChange, clearable]\n );\n\n // Get glass variant class\n const getGlassClass = () => {\n const variants: Record = {\n glass: 'glass',\n frosted: 'frosted',\n fluted: 'fluted',\n crystal: 'crystal',\n };\n return variants[glassVariant];\n };\n\n // Render content\n const comboboxContent = (\n \n \n \n \n {TriggerIcon && }\n {selectedOption ? selectedOption.label : placeholder}\n \n \n \n \n \n \n {searchable && (\n \n )}\n \n {emptyText}\n \n {filteredOptions.map((option) => {\n const OptionIcon = option.icon;\n const isSelected = value === option.value;\n\n return (\n handleSelect(option.value)}\n className={cn(\n getDropdownItemClasses({ selected: isSelected }),\n option.disabled && 'cursor-not-allowed opacity-50'\n )}\n >\n \n {OptionIcon && (\n \n )}\n {option.label}\n \n );\n })}\n \n \n \n \n \n );\n\n // Wrap with FormFieldWrapper if label/error/success provided\n if (label || error || success) {\n return (\n \n {comboboxContent}\n \n );\n }\n\n return comboboxContent;\n}\n\n// Export with generic type support\nexport const ComboBoxGlass = forwardRef(ComboBoxGlassInner) as (\n props: ComboBoxGlassProps & { ref?: React.ForwardedRef }\n) => ReturnType;\n\n// Add display name for debugging\nComboBoxGlassInner.displayName = 'ComboBoxGlass';\n" } ], "categories": [ "ui" ], "cssVars": { "light": { "--glass-bg": "rgba(255, 255, 255, 0.1)", "--glass-border": "rgba(255, 255, 255, 0.2)", "--blur-sm": "8px", "--blur-md": "16px", "--blur-lg": "24px" }, "dark": { "--glass-bg": "rgba(255, 255, 255, 0.05)", "--glass-border": "rgba(255, 255, 255, 0.1)", "--blur-sm": "8px", "--blur-md": "16px", "--blur-lg": "24px" } } }