{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"select","title":"Select","description":"Single- and multiple-selection popup with grouped, polymorphic and form-integrated controls.","dependencies":["@base-ui/react@1.7.0","lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button","@siteplane/input","@siteplane/separator"],"files":[{"path":"packages/ui/src/components/select/select.tsx","content":"\"use client\";\n\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { ChevronDownIcon, ChevronUpIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useFormReset } from \"../../system/form/use-form-reset\";\nimport {\n  SiteplaneUIPortalScope,\n  useResolvedControlSize,\n  type SiteplaneControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport { ChoiceMenuItemCheck } from \"../../system/styles/choice-menu-item\";\nimport { surfaceClassNames } from \"../../system/styles/surface\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { dropSlotClass, slotClass } from \"../../system/utils/slot-class\";\nimport { KitButton } from \"../button/button-internal\";\nimport { useFieldLabelPlacement } from \"../field\";\nimport { Separator } from \"../separator\";\nimport { Input } from \"../input\";\n\nfunction SelectCaretIcon(\n  props: React.ComponentProps<\"svg\">,\n): React.ReactElement {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      data-slot=\"select-caret\"\n      fill=\"currentColor\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n      className={cn(slotClass(\"select-caret\", props), props.className)}\n    >\n      <path d=\"M11.5263 15.582C11.8209 15.8223 12.2556 15.8049 12.5302 15.5303L17.5302 10.5303C17.8231 10.2374 17.8231 9.76263 17.5302 9.46973C17.2373 9.17684 16.7626 9.17684 16.4697 9.46973L11.9999 13.9395L7.53022 9.46973C7.23732 9.17684 6.76256 9.17684 6.46967 9.46973C6.17678 9.76263 6.17678 10.2374 6.46967 10.5303L11.4697 15.5303L11.5263 15.582Z\" />\n    </svg>\n  );\n}\n\nexport function Select<Value, Multiple extends boolean | undefined = false>(\n  props: SelectPrimitive.Root.Props<Value, Multiple>,\n): React.ReactElement {\n  const [resetKey, setResetKey] = React.useState(0);\n  const markerRef = useFormReset<HTMLInputElement>(props.form, () => {\n    if (props.value === undefined) setResetKey((current) => current + 1);\n  });\n  return (\n    <>\n      <SelectPrimitive.Root data-slot=\"select\" {...props} key={resetKey} />\n      <Input\n        aria-hidden=\"true\"\n        disabled\n        form={props.form}\n        ref={markerRef}\n        type=\"hidden\"\n      />\n    </>\n  );\n}\n\nexport type SelectTriggerSize = SiteplaneControlSize;\n/** The trigger surface, the same four as the other form controls. */\nexport type SelectTriggerMode = \"default\" | \"elevated\" | \"ghost\" | \"inset\";\n\nexport interface SelectButtonProps extends useRender.ComponentProps<\"button\"> {\n  size?: SelectTriggerSize;\n  variant?: SelectTriggerMode;\n}\n\nexport function SelectButton({\n  className,\n  size,\n  variant,\n  render,\n  children,\n  ...props\n}: SelectButtonProps): React.ReactElement {\n  const resolvedSize = useResolvedControlSize(size);\n  const resolvedMode = variant ?? \"default\";\n  return (\n    <KitButton\n      align=\"between\"\n      className={cn(\"min-w-0\", className)}\n      data-select-trigger=\"\"\n      data-slot=\"select-button\"\n      {...props}\n      profile=\"select-trigger\"\n      profileMode={resolvedMode}\n      render={render}\n      size={resolvedSize}\n      variant=\"outline\"\n      width=\"full\"\n    >\n      <>\n        <span\n          className=\"sp-select-value flex-1 truncate in-data-placeholder:text-muted-foreground/72\"\n          data-slot=\"select-value\"\n        >\n          {children}\n        </span>\n        <span className=\"sp-select-icon\" data-slot=\"select-icon\">\n          <SelectCaretIcon />\n        </span>\n      </>\n    </KitButton>\n  );\n}\n\nexport function SelectTrigger({\n  className,\n  size,\n  variant,\n  children,\n  render,\n  ...props\n}: WithStringClassName<SelectPrimitive.Trigger.Props> & {\n  size?: SelectTriggerSize | undefined;\n  variant?: SelectTriggerMode | undefined;\n}): React.ReactElement {\n  const resolvedSize = useResolvedControlSize(size);\n  const resolvedMode = variant ?? \"default\";\n  // A floating Field lays out the trigger it holds (design-rework.css,\n  // \"Floating label fields\"); the ghost mode draws no field to hold a label.\n  const floating =\n    useFieldLabelPlacement() === \"floating\" && resolvedMode !== \"ghost\";\n\n  return (\n    <SelectPrimitive.Trigger\n      {...props}\n      // The trigger keeps its own slot, so a host's slot class goes (a\n      // Tooltip trigger that renders it).\n      className={cn(\n        slotClass(\"select-trigger\", undefined, render),\n        \"min-w-36\",\n        dropSlotClass(className, props),\n      )}\n      data-label-placement={floating ? \"floating\" : undefined}\n      data-profile=\"select-trigger\"\n      data-profile-mode={resolvedMode}\n      data-select-trigger=\"\"\n      data-size={resolvedSize}\n      data-slot=\"select-trigger\"\n      render={\n        render ?? (\n          <KitButton\n            align=\"between\"\n            profile=\"select-trigger\"\n            profileMode={resolvedMode}\n            size={resolvedSize}\n            variant=\"outline\"\n            width=\"full\"\n          />\n        )\n      }\n    >\n      {children}\n      <SelectPrimitive.Icon className=\"sp-select-icon\" data-slot=\"select-icon\">\n        <SelectCaretIcon />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  );\n}\n\nexport function SelectValue({\n  className,\n  ...props\n}: WithStringClassName<SelectPrimitive.Value.Props>): React.ReactElement {\n  return (\n    <SelectPrimitive.Value\n      className={cn(\n        slotClass(\"select-value\", props),\n        \"flex-1 truncate\",\n        className,\n      )}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nexport function SelectPopup({\n  className,\n  children,\n  side = \"bottom\",\n  // 8 px, measured across the reference's selects; action and filter menus\n  // keep 4 px.\n  sideOffset = 8,\n  align = \"start\",\n  alignOffset = 0,\n  alignItemWithTrigger = false,\n  anchor,\n  portalProps,\n  ...props\n}: WithStringClassName<SelectPrimitive.Popup.Props> & {\n  portalProps?: WithStringClassName<SelectPrimitive.Portal.Props>;\n  side?: SelectPrimitive.Positioner.Props[\"side\"];\n  sideOffset?: SelectPrimitive.Positioner.Props[\"sideOffset\"];\n  align?: SelectPrimitive.Positioner.Props[\"align\"];\n  alignOffset?: SelectPrimitive.Positioner.Props[\"alignOffset\"];\n  alignItemWithTrigger?: SelectPrimitive.Positioner.Props[\"alignItemWithTrigger\"];\n  anchor?: SelectPrimitive.Positioner.Props[\"anchor\"];\n}): React.ReactElement {\n  return (\n    <SelectPrimitive.Portal {...portalProps}>\n      <SiteplaneUIPortalScope>\n        <SelectPrimitive.Positioner\n          align={align}\n          alignItemWithTrigger={alignItemWithTrigger}\n          alignOffset={alignOffset}\n          anchor={anchor}\n          className=\"sp-select-positioner z-50 select-none\"\n          data-slot=\"select-positioner\"\n          side={side}\n          sideOffset={sideOffset}\n        >\n          <SelectPrimitive.Popup\n            className={cn(\n              slotClass(\"select-popup\", props),\n              \"sp-floating-panel origin-(--transform-origin) text-foreground outline-none\",\n              className,\n            )}\n            data-slot=\"select-popup\"\n            {...props}\n          >\n            {/* The scroll arrows are strips inside the panel, with the fade\n                into the list on their ::before (design-rework.css). */}\n            <SelectPrimitive.ScrollUpArrow\n              className=\"sp-select-scroll-up-arrow top-0 z-50 hidden h-6 w-full cursor-pointer items-center justify-center data-[visible]:flex\"\n              data-slot=\"select-scroll-up-arrow\"\n            >\n              <ChevronUpIcon className=\"relative size-4.5 sm:size-4\" />\n            </SelectPrimitive.ScrollUpArrow>\n            {/* The panel: its surface rule names this element alone, so the\n                arrows beside it keep their own look (design-rework.css). */}\n            <div\n              className={cn(\n                \"sp-select-surface\",\n                surfaceClassNames.overlay,\n                \"h-full min-w-(--anchor-width) w-max max-w-(--available-width) before:rounded-[inherit]\",\n              )}\n            >\n              <SelectPrimitive.List\n                className=\"sp-select-list max-h-(--available-height) overflow-y-auto\"\n                data-slot=\"select-list\"\n              >\n                {children}\n              </SelectPrimitive.List>\n            </div>\n            <SelectPrimitive.ScrollDownArrow\n              className=\"sp-select-scroll-down-arrow bottom-0 z-50 hidden h-6 w-full cursor-pointer items-center justify-center data-[visible]:flex\"\n              data-slot=\"select-scroll-down-arrow\"\n            >\n              <ChevronDownIcon className=\"relative size-4.5 sm:size-4\" />\n            </SelectPrimitive.ScrollDownArrow>\n          </SelectPrimitive.Popup>\n        </SelectPrimitive.Positioner>\n      </SiteplaneUIPortalScope>\n    </SelectPrimitive.Portal>\n  );\n}\n\nexport function SelectItem({\n  className,\n  children,\n  ...props\n}: WithStringClassName<SelectPrimitive.Item.Props>): React.ReactElement {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        slotClass(\"select-item\", props),\n        \"grid cursor-pointer items-center outline-none data-disabled:pointer-events-none data-disabled:opacity-(--sp-rw-disabled-opacity-action) [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      {/* The check leads the row, in a slot every option keeps, so every\n          label starts at the same x, as in Combobox and the menus (I8). */}\n      <SelectPrimitive.ItemIndicator\n        className=\"sp-select-item-indicator col-start-1 row-start-1\"\n        data-slot=\"select-item-indicator\"\n        keepMounted\n      >\n        <ChoiceMenuItemCheck />\n      </SelectPrimitive.ItemIndicator>\n      <SelectPrimitive.ItemText\n        className=\"sp-select-item-text col-start-2 row-start-1 min-w-0 truncate\"\n        data-slot=\"select-item-text\"\n      >\n        {children}\n      </SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  );\n}\n\nexport function SelectSeparator({\n  className,\n  ...props\n}: WithStringClassName<SelectPrimitive.Separator.Props>): React.ReactElement {\n  return (\n    <SelectPrimitive.Separator\n      className={cn(\n        slotClass(\"select-separator\", props),\n        \"mx-2 my-1\",\n        className,\n      )}\n      data-slot=\"select-separator\"\n      render={<Separator />}\n      {...props}\n    />\n  );\n}\n\nexport function SelectGroup(\n  props: WithStringClassName<SelectPrimitive.Group.Props>,\n): React.ReactElement {\n  return (\n    <SelectPrimitive.Group\n      data-slot=\"select-group\"\n      {...props}\n      className={cn(slotClass(\"select-group\", props), props.className)}\n    />\n  );\n}\n\n/**\n * The label of a Select. Like FieldLabel and Label it carries no outer\n * margin: the stack around it spaces it, as a Field's gap does (S10). Its\n * colour and disabled state are the labels' shared rules (design-rework.css).\n */\nexport function SelectLabel({\n  className,\n  ...props\n}: WithStringClassName<SelectPrimitive.Label.Props>): React.ReactElement {\n  return (\n    <SelectPrimitive.Label\n      className={cn(\n        slotClass(\"select-label\", props),\n        \"inline-flex items-center gap-2 font-(--sp-rw-font-weight-label) text-base/4.5 sm:text-sm/4\",\n        className,\n      )}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nexport function SelectGroupLabel({\n  className,\n  ...props\n}: WithStringClassName<SelectPrimitive.GroupLabel.Props>): React.ReactElement {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn(\n        slotClass(\"select-group-label\", props),\n        \"px-2 py-1.5 font-(--sp-rw-font-weight-label) text-muted-foreground text-xs\",\n        className,\n      )}\n      data-slot=\"select-group-label\"\n      {...props}\n    />\n  );\n}\n\n// SelectContent is the shadcn name of SelectPopup.\nexport { SelectPrimitive, SelectPopup as SelectContent };\n","type":"registry:ui","target":"~/src/siteplane/components/select/select.tsx"},{"path":"packages/ui/src/components/select/index.ts","content":"export * from \"./select\";\n","type":"registry:ui","target":"~/src/siteplane/components/select/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/select.md","categories":["Forms & selection"],"meta":{"avoidWhen":["Avoid when people need to search the options; use Combobox.","Avoid for text that may differ from the options; use Autocomplete."],"description":"Single- and multiple-selection popup with grouped, polymorphic and form-integrated controls.","knownLimitations":[],"providerNotes":[],"releaseStatus":"stable","searchTerms":["select"],"useWhen":["Use to choose one or several options from a fixed list, without text filtering."]}}