{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "glass-select", "title": "Glass Select", "description": "A wide pill that states its current choice in full, on Radix Select. Solid, glass, ink and hairline materials.", "dependencies": [ "class-variance-authority", "radix-ui", "lucide-react" ], "registryDependencies": [ "http://localhost:5173/r/liquid-theme.json" ], "files": [ { "path": "src/components/ui/glass-select.tsx", "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"radix-ui\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Check, ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * The `Double Shared Room ⌄` / `Deposit — €500 ⌄` control from the booking\n * bar: a wide pill that states the current choice in full rather than\n * abbreviating it behind a label.\n *\n * Built on Radix Select, so the listbox is a real listbox — typeahead,\n * arrow-key navigation, `aria-activedescendant` and focus return all come\n * from the primitive. This file owns the light and the shape only.\n *\n * The trigger is deliberately not a fixed width. A select that says\n * \"Double Shared Room\" is doing the job of a label and a value at once, and\n * truncating it to a tidy column undoes that.\n */\nconst trigger = cva(\n \"group/select inline-flex select-none items-center justify-between gap-3 rounded-full font-sans whitespace-nowrap lq-ease transition-[background-color,border-color,color,transform,box-shadow] duration-300 outline-none focus-visible:ring-3 focus-visible:ring-current/25 disabled:pointer-events-none disabled:opacity-40 data-[placeholder]:text-current/45\",\n {\n variants: {\n material: {\n /** Opaque white pill — the reference booking control. */\n solid: \"lq-solid text-ink dark:text-white hover:brightness-[0.98]\",\n /** Frosted — for placement directly over photography. */\n glass: \"lq-glass text-white\",\n /** Filled black. Pairs with a `solid` sibling as the committed choice. */\n ink: \"bg-ink text-white shadow-[var(--lq-cast)] hover:bg-ink/90\",\n /** Hairline only — the quietest reading, for dense panels. */\n hairline:\n \"border border-current/20 text-current hover:border-current/40 hover:bg-current/5\",\n },\n size: {\n sm: \"h-9 px-4 text-[13px] [&_svg]:size-3.5\",\n md: \"h-11 px-5 text-[14px] [&_svg]:size-4\",\n lg: \"h-13 px-6 text-[15px] [&_svg]:size-[18px]\",\n },\n },\n defaultVariants: { material: \"solid\", size: \"md\" },\n },\n)\n\nconst panel = cva(\n \"relative z-50 overflow-hidden lq-squircle [--lq-corner:20px] p-1.5 origin-(--radix-select-content-transform-origin) data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n {\n variants: {\n material: {\n solid: \"lq-solid text-ink dark:text-white\",\n glass: \"lq-glass text-white\",\n ink: \"bg-ink text-white shadow-[var(--lq-cast)]\",\n hairline:\n \"border border-current/15 bg-paper text-ink shadow-[var(--lq-cast)] dark:bg-ink dark:text-white\",\n },\n },\n defaultVariants: { material: \"solid\" },\n },\n)\n\nexport type GlassSelectOption = {\n value: string\n label: React.ReactNode\n /** Quiet trailing text — a price, a count, a duration. */\n hint?: React.ReactNode\n disabled?: boolean\n}\n\nexport interface GlassSelectProps\n extends Omit, \"children\">,\n VariantProps {\n options: GlassSelectOption[]\n placeholder?: string\n /**\n * Text shown ahead of the value inside the trigger, e.g. `Deposit` in\n * `Deposit — €500`. Not a form label; use `aria-label` for that.\n */\n prefix?: React.ReactNode\n /** Match the panel's material to the trigger's unless told otherwise. */\n panelMaterial?: VariantProps[\"material\"]\n className?: string\n contentClassName?: string\n \"aria-label\"?: string\n}\n\nexport function GlassSelect({\n options,\n placeholder = \"Select\",\n prefix,\n material = \"solid\",\n panelMaterial,\n size = \"md\",\n className,\n contentClassName,\n \"aria-label\": ariaLabel,\n ...props\n}: GlassSelectProps) {\n return (\n \n \n \n {prefix ? (\n {prefix}\n ) : null}\n \n \n \n {/* Rotating the chevron rather than swapping it keeps the open and\n closed states on one continuous motion. */}\n \n \n \n\n \n \n \n {options.map((o) => (\n \n \n \n \n {o.label}\n {o.hint ? (\n \n {o.hint}\n \n ) : null}\n \n ))}\n \n \n \n \n )\n}\n", "type": "registry:ui" } ], "type": "registry:ui" }