{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"select","type":"registry:ui","title":"Select","description":"A custom dropdown for choosing one option from a list.","dependencies":["lucide-react","radix-ui"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-portal.json","https://dashral.com/r/field.json"],"files":[{"path":"registry/dashral/select.tsx","type":"registry:file","target":"components/dashral/select.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"radix-ui\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\n\nimport { cn } from \"./lib/utils\"\nimport { usePortalContainer } from \"./lib/portal\"\nimport { FieldWrapper, splitFieldProps, useFieldControl, type FieldClassNames, type FieldOwnProps } from \"./field\"\n\ntype SelectOption =\n  | string\n  | { value: string; label?: React.ReactNode; description?: React.ReactNode; icon?: React.ReactNode; disabled?: boolean }\ntype SelectOptionGroup = { label?: React.ReactNode; options: SelectOption[] }\n\ntype SelectProps = Omit<React.ComponentProps<typeof SelectPrimitive.Root>, \"children\"> &\n  FieldOwnProps & {\n    /**\n     * The choices: strings, { value, label, description, icon, disabled }, or groups of them ({ label, options }). A\n     * line separates groups; a group without a label is just set apart.\n     */\n    options: (SelectOption | SelectOptionGroup)[]\n    /** Shown in the trigger while nothing is picked. */\n    placeholder?: React.ReactNode\n    /** Inside the trigger before the value, e.g. an icon. */\n    prefix?: React.ReactNode\n    size?: \"sm\" | \"default\"\n    /** Which side of the trigger the list opens on. */\n    side?: \"top\" | \"bottom\"\n    id?: string\n    /** The trigger's classes. */\n    className?: string\n    \"aria-label\"?: string\n    classNames?: FieldClassNames & { trigger?: string; content?: string; item?: string }\n  }\n\n/** A select with a styled list, in one tag. Give it a label, description or error and it becomes a whole field. */\nfunction Select(props: SelectProps) {\n  const [field, { options, placeholder, prefix, size, side, id, className, classNames, \"aria-label\": ariaLabel, \"aria-invalid\": invalid, ...root }] =\n    splitFieldProps(props)\n  return (\n    <FieldWrapper {...field}>\n      <SelectPrimitive.Root data-slot=\"select\" {...root}>\n        <SelectTrigger id={id} size={size} aria-label={ariaLabel} aria-invalid={invalid} className={cn(\"w-full\", className, classNames?.trigger)}>\n          {prefix && (\n            <span data-slot=\"select-prefix\" className=\"flex shrink-0 items-center text-muted-foreground\">\n              {prefix}\n            </span>\n          )}\n          {/* With a prefix, the value sits right after it rather than in the middle. */}\n          <SelectValue placeholder={placeholder} className={prefix ? \"flex-1 text-start\" : undefined} />\n        </SelectTrigger>\n        <SelectContent side={side} className={classNames?.content}>\n          {options.map((option, i) =>\n            typeof option === \"object\" && \"options\" in option ? (\n              <React.Fragment key={i}>\n                {i > 0 && <SelectSeparator />}\n                <SelectGroup>\n                  {option.label && <SelectLabel>{option.label}</SelectLabel>}\n                  {option.options.map((item) => renderOption(item, classNames?.item))}\n                </SelectGroup>\n              </React.Fragment>\n            ) : (\n              renderOption(option, classNames?.item)\n            )\n          )}\n        </SelectContent>\n      </SelectPrimitive.Root>\n    </FieldWrapper>\n  )\n}\n\nfunction renderOption(option: SelectOption, className?: string) {\n  const { value, label = value, description, icon, disabled } = typeof option === \"string\" ? { value: option } : option\n  return (\n    <SelectItem key={value} value={value} disabled={disabled} description={description} className={className}>\n      {icon}\n      {label}\n    </SelectItem>\n  )\n}\n\nfunction SelectGroup(props: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />\n}\n\nfunction SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>) {\n  return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />\n}\n\nfunction SelectTrigger({\n  className,\n  size = \"default\",\n  children,\n  ...rest\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: \"sm\" | \"default\" }) {\n  // A button can't be required: the Select root takes that.\n  const { required: _required, ...props } = useFieldControl(rest as typeof rest & { required?: boolean })\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/15 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/15 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"size-4 opacity-50\" />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  )\n}\n\nfunction SelectContent({\n  className,\n  children,\n  position = \"popper\",\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n  const portalContainer = usePortalContainer()\n  return (\n    <SelectPrimitive.Portal container={portalContainer}>\n      <SelectPrimitive.Content\n        data-slot=\"select-content\"\n        className={cn(\n          \"relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:blur-in-[2px] data-[state=open]:duration-(--duration-standard) data-[state=open]:ease-spring data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:duration-(--duration-fast) data-[state=closed]:ease-exit motion-reduce:blur-in-0\",\n          position === \"popper\" &&\n            \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n          className\n        )}\n        position={position}\n        {...props}\n      >\n        <SelectScrollUpButton />\n        <SelectPrimitive.Viewport\n          className={cn(\n            \"p-1\",\n            position === \"popper\" &&\n              \"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1\"\n          )}\n        >\n          {children}\n        </SelectPrimitive.Viewport>\n        <SelectScrollDownButton />\n      </SelectPrimitive.Content>\n    </SelectPrimitive.Portal>\n  )\n}\n\nfunction SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {\n  return (\n    <SelectPrimitive.Label\n      data-slot=\"select-label\"\n      className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectItem({\n  className,\n  children,\n  description,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item> & { description?: React.ReactNode }) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pe-8 ps-2 text-sm outline-hidden select-none [&_[id]]:flex [&_[id]]:items-center [&_[id]]:gap-2 focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"absolute end-2 flex size-3.5 items-center justify-center\">\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      {/* The item text (Radix gives it an id and drops any class) lays an icon beside the label, as in the trigger. */}\n      {description ? (\n        // Only the label goes in the trigger; the description stays in the list.\n        <span className=\"grid gap-0.5\">\n          <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n          <span className=\"text-xs text-muted-foreground\">{description}</span>\n        </span>\n      ) : (\n        <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n      )}\n    </SelectPrimitive.Item>\n  )\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n  return (\n    <SelectPrimitive.ScrollUpButton\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n      {...props}\n    >\n      <ChevronUpIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollUpButton>\n  )\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n  return (\n    <SelectPrimitive.ScrollDownButton\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollDownButton>\n  )\n}\n\nexport { Select, type SelectProps, type SelectOption, type SelectOptionGroup }\n"}],"cssVars":{"theme":{"ease-standard":"var(--ease-standard)","ease-enter":"var(--ease-enter)","ease-exit":"var(--ease-exit)","ease-spring":"var(--ease-spring)"},"light":{"duration-fast":"160ms","duration-standard":"240ms","duration-considered":"480ms","ease-standard":"cubic-bezier(0.2, 0, 0, 1)","ease-enter":"cubic-bezier(0, 0, 0, 1)","ease-exit":"cubic-bezier(0.3, 0, 1, 1)","ease-spring":"linear(0, 0.071 4%, 0.234 8%, 0.433 12%, 0.626 17%, 0.791 21%, 0.917 25%, 1.004 29%, 1.055 33%, 1.079 38%, 1.083 42%, 1.075 46%, 1.06 50%, 1.043 54%, 1.027 58%, 1.015 62%, 1.005 67%, 0.998 71%, 0.995 75%, 0.993 79%, 0.993 83%, 0.994 88%, 0.995 92%, 0.997 96%, 1)"},"dark":{}}}