{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"autocomplete","title":"Autocomplete","description":"Text input with suggestions and no selection state.","dependencies":["@base-ui/react@1.7.0","lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button","@siteplane/input","@siteplane/scroll-area","@siteplane/separator"],"files":[{"path":"packages/ui/src/components/autocomplete/autocomplete.tsx","content":"\"use client\";\n\nimport { Autocomplete as AutocompletePrimitive } from \"@base-ui/react/autocomplete\";\nimport { ChevronsUpDownIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useFormReset } from \"../../system/form/use-form-reset\";\n\nimport {\n  type SiteplaneControlSize,\n  SiteplaneUIPortalScope,\n  useResolvedControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport {\n  type FormControlSurface,\n  surfaceClassNames,\n} from \"../../system/styles/surface\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { baseClass, slotClass } from \"../../system/utils/slot-class\";\nimport { Button } from \"../button\";\nimport { Separator } from \"../separator\";\nimport { Input } from \"../input\";\nimport { KitInput } from \"../input/input-internal\";\nimport { ExternalInputOwner } from \"../input/input\";\nimport { ScrollArea } from \"../scroll-area\";\nimport type {\n  KitAutocompleteInputProps,\n  KitAutocompleteItemProps,\n} from \"./autocomplete-internal\";\n\nexport const Autocomplete: typeof AutocompletePrimitive.Root = <ItemValue,>(\n  props: Omit<AutocompletePrimitive.Root.Props<ItemValue>, \"items\"> & {\n    items?: readonly ItemValue[] | undefined;\n  },\n) => {\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      <AutocompletePrimitive.Root {...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\ntype AutocompleteInputProps = Omit<\n  WithStringClassName<AutocompletePrimitive.Input.Props>,\n  \"size\"\n> & {\n  showTrigger?: boolean;\n  showClear?: boolean;\n  startAddon?: React.ReactNode;\n  size?: SiteplaneControlSize | number | undefined;\n  variant?: FormControlSurface | undefined;\n  unstyled?: boolean;\n  ref?: React.Ref<HTMLInputElement>;\n  triggerProps?: WithStringClassName<AutocompletePrimitive.Trigger.Props>;\n  clearProps?: WithStringClassName<AutocompletePrimitive.Clear.Props>;\n};\n\nexport function AutocompleteInput(\n  inputProps: AutocompleteInputProps,\n): React.ReactElement {\n  // Command passes the internal command profile through\n  // KitAutocompleteInput (autocomplete-internal.ts), the same function.\n  const {\n    className,\n    disabled,\n    showTrigger = false,\n    showClear = false,\n    startAddon,\n    size,\n    unstyled = false,\n    profile = \"combobox\",\n    variant = \"default\",\n    triggerProps,\n    clearProps,\n    ...props\n  } = inputProps as KitAutocompleteInputProps;\n  const visualSize = typeof size === \"number\" ? undefined : size;\n  const nativeSize = typeof size === \"number\" ? size : undefined;\n  const resolvedSize = useResolvedControlSize(visualSize);\n\n  return (\n    <AutocompletePrimitive.InputGroup\n      className={cn(\n        \"sp-autocomplete-input-group\",\n        \"relative w-full min-w-0 text-foreground\",\n        profile !== \"command\" && \"not-has-[>*.w-full]:w-fit\",\n      )}\n      data-slot=\"autocomplete-input-group\"\n    >\n      {startAddon && (\n        <div\n          aria-hidden=\"true\"\n          className=\"sp-autocomplete-start-addon pointer-events-none absolute inset-y-0 start-px z-10 flex items-center [&_svg]:-mx-0.5\"\n          data-slot=\"autocomplete-start-addon\"\n        >\n          {startAddon}\n        </div>\n      )}\n      {/* Autocomplete.Input already connects the input to Field. */}\n      <ExternalInputOwner>\n        <AutocompletePrimitive.Input\n          className={cn(slotClass(\"autocomplete-input\", props), className)}\n          data-inset-end={showTrigger || showClear ? \"\" : undefined}\n          data-inset-start={startAddon ? \"\" : undefined}\n          data-size={resolvedSize}\n          data-slot=\"autocomplete-input\"\n          render={\n            <KitInput\n              insetEnd={showTrigger || showClear}\n              insetStart={Boolean(startAddon)}\n              nativeInput\n              profile={profile}\n              size={nativeSize ?? resolvedSize}\n              unstyled={unstyled}\n              variant={variant}\n            />\n          }\n          disabled={disabled}\n          {...props}\n        />\n      </ExternalInputOwner>\n      {showTrigger && (\n        <AutocompleteTrigger\n          {...triggerProps}\n          aria-label={triggerProps?.[\"aria-label\"] ?? \"Show suggestions\"}\n          className={cn(\n            \"absolute top-1/2 -translate-y-1/2 has-[+[data-slot=autocomplete-clear]]:hidden\",\n            triggerProps?.className,\n          )}\n          disabled={disabled || triggerProps?.disabled}\n          render={\n            triggerProps?.render ?? (\n              <Button\n                profile=\"field-action\"\n                size={resolvedSize === \"xl\" ? \"icon-sm\" : \"icon-xs\"}\n                variant=\"ghost\"\n              />\n            )\n          }\n        >\n          <AutocompletePrimitive.Icon\n            className=\"sp-autocomplete-icon\"\n            data-slot=\"autocomplete-icon\"\n          >\n            <ChevronsUpDownIcon className=\"size-4\" />\n          </AutocompletePrimitive.Icon>\n        </AutocompleteTrigger>\n      )}\n      {showClear && (\n        <AutocompleteClear\n          {...clearProps}\n          aria-label={clearProps?.[\"aria-label\"] ?? \"Clear input\"}\n          // end-1 replaces AutocompleteClear's own end-0.5 in the merge, so\n          // the clear sits 4 px in, like the trigger.\n          className={cn(\n            \"absolute end-1 top-1/2 -translate-y-1/2 has-[+[data-slot=autocomplete-clear]]:hidden\",\n            clearProps?.className,\n          )}\n          disabled={disabled || clearProps?.disabled}\n          render={\n            clearProps?.render ?? (\n              <Button\n                profile=\"field-action\"\n                size={resolvedSize === \"xl\" ? \"icon-sm\" : \"icon-xs\"}\n                variant=\"ghost\"\n              />\n            )\n          }\n        >\n          <XIcon className=\"size-3\" />\n        </AutocompleteClear>\n      )}\n    </AutocompletePrimitive.InputGroup>\n  );\n}\n\nexport function AutocompletePopup({\n  className,\n  children,\n  side = \"bottom\",\n  sideOffset = 4,\n  alignOffset,\n  align = \"start\",\n  anchor,\n  portalProps,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Popup.Props> & {\n  align?: AutocompletePrimitive.Positioner.Props[\"align\"];\n  sideOffset?: AutocompletePrimitive.Positioner.Props[\"sideOffset\"];\n  alignOffset?: AutocompletePrimitive.Positioner.Props[\"alignOffset\"];\n  side?: AutocompletePrimitive.Positioner.Props[\"side\"];\n  anchor?: AutocompletePrimitive.Positioner.Props[\"anchor\"];\n  portalProps?: WithStringClassName<AutocompletePrimitive.Portal.Props>;\n}): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Portal {...portalProps}>\n      <SiteplaneUIPortalScope>\n        <AutocompletePrimitive.Positioner\n          align={align}\n          alignOffset={alignOffset}\n          anchor={anchor}\n          className=\"sp-autocomplete-positioner z-50 select-none\"\n          data-slot=\"autocomplete-positioner\"\n          side={side}\n          sideOffset={sideOffset}\n        >\n          <AutocompletePrimitive.Popup\n            className={cn(\n              slotClass(\"autocomplete-popup\", props),\n              surfaceClassNames.floatingOverlay,\n              \"flex max-h-[min(var(--available-height),23rem)] min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) flex-col text-foreground before:rounded-[inherit]\",\n              className,\n            )}\n            data-slot=\"autocomplete-popup\"\n            {...props}\n          >\n            {children}\n          </AutocompletePrimitive.Popup>\n        </AutocompletePrimitive.Positioner>\n      </SiteplaneUIPortalScope>\n    </AutocompletePrimitive.Portal>\n  );\n}\n\nexport function AutocompleteItem(\n  itemProps: WithStringClassName<AutocompletePrimitive.Item.Props>,\n): React.ReactElement {\n  // Command passes the internal command profile through KitAutocompleteItem\n  // (autocomplete-internal.ts), the same function.\n  const {\n    profile = \"default\",\n    className,\n    children,\n    ...props\n  } = itemProps as KitAutocompleteItemProps;\n  return (\n    <AutocompletePrimitive.Item\n      className={cn(\n        slotClass(\"autocomplete-item\", props),\n        baseClass(\"autocomplete-item\", props),\n        \"flex cursor-pointer select-none items-center outline-none data-disabled:pointer-events-none data-disabled:opacity-(--sp-rw-disabled-opacity-action) [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        // Rulebook I5: a leading icon sits on the first line of a label that\n        // wraps. Such a row lays out as one grid track centred in the\n        // min-height, so a one-line label stays centred, and the icon's box\n        // is one line high. Command items share it; the command profile puts\n        // their shortcut on the first line too.\n        \"has-[>svg:first-child]:grid has-[>svg:first-child]:auto-cols-[auto_minmax(0,1fr)] has-[>svg:first-child]:grid-flow-col has-[>svg:first-child]:content-center has-[>svg:first-child]:items-start [&>svg:first-child]:min-h-lh\",\n        className,\n      )}\n      data-foundation=\"autocomplete-item\"\n      data-profile={profile}\n      data-slot=\"autocomplete-item\"\n      {...props}\n    >\n      {children}\n    </AutocompletePrimitive.Item>\n  );\n}\n\nexport function AutocompleteSeparator({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Separator.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Separator\n      className={cn(\n        slotClass(\"autocomplete-separator\", props),\n        \"mx-2 my-1 last:hidden\",\n        className,\n      )}\n      data-slot=\"autocomplete-separator\"\n      render={<Separator />}\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteGroup({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Group.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Group\n      className={cn(\n        slotClass(\"autocomplete-group\", props),\n        \"[[role=group]+&]:mt-1.5\",\n        className,\n      )}\n      data-slot=\"autocomplete-group\"\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteGroupLabel({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.GroupLabel.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.GroupLabel\n      className={cn(\n        slotClass(\"autocomplete-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=\"autocomplete-group-label\"\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteEmpty({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Empty.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Empty\n      className={cn(\n        slotClass(\"autocomplete-empty\", props),\n        \"not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm\",\n        className,\n      )}\n      data-slot=\"autocomplete-empty\"\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteRow({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Row.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Row\n      className={cn(slotClass(\"autocomplete-row\", props), className)}\n      data-slot=\"autocomplete-row\"\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteValue({\n  ...props\n}: AutocompletePrimitive.Value.Props): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Value data-slot=\"autocomplete-value\" {...props} />\n  );\n}\n\nexport function AutocompleteList({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.List.Props>): React.ReactElement {\n  return (\n    <ScrollArea scrollbarGutter scrollFade>\n      <AutocompletePrimitive.List\n        className={cn(\n          slotClass(\"autocomplete-list\", props),\n          \"not-empty:scroll-py-1 not-empty:p-1 in-data-has-overflow-y:pe-3\",\n          className,\n        )}\n        data-slot=\"autocomplete-list\"\n        {...props}\n      />\n    </ScrollArea>\n  );\n}\n\nexport function AutocompleteClear({\n  className,\n  render,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Clear.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Clear\n      className={cn(\n        slotClass(\"autocomplete-clear\", props, render),\n        \"absolute end-0.5 top-1/2 -translate-y-1/2\",\n        className,\n      )}\n      data-slot=\"autocomplete-clear\"\n      render={\n        render ?? (\n          <Button profile=\"field-action\" shape=\"square\" variant=\"ghost\" />\n        )\n      }\n      {...props}\n    >\n      <XIcon className=\"size-3\" />\n    </AutocompletePrimitive.Clear>\n  );\n}\n\nexport function AutocompleteStatus({\n  className,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Status.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Status\n      className={cn(\n        slotClass(\"autocomplete-status\", props),\n        \"px-3 py-2 font-(--sp-rw-font-weight-label) text-muted-foreground text-xs empty:m-0 empty:p-0\",\n        className,\n      )}\n      data-slot=\"autocomplete-status\"\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteCollection({\n  ...props\n}: AutocompletePrimitive.Collection.Props): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Collection\n      data-slot=\"autocomplete-collection\"\n      {...props}\n    />\n  );\n}\n\nexport function AutocompleteTrigger({\n  className,\n  children,\n  render,\n  ...props\n}: WithStringClassName<AutocompletePrimitive.Trigger.Props>): React.ReactElement {\n  return (\n    <AutocompletePrimitive.Trigger\n      className={cn(\n        slotClass(\"autocomplete-trigger\", props, render),\n        className,\n      )}\n      data-slot=\"autocomplete-trigger\"\n      render={\n        render ?? (\n          <Button profile=\"field-action\" shape=\"square\" variant=\"ghost\" />\n        )\n      }\n      {...props}\n    >\n      {children}\n    </AutocompletePrimitive.Trigger>\n  );\n}\n\nexport const useAutocompleteFilter: typeof AutocompletePrimitive.useFilter =\n  AutocompletePrimitive.useFilter;\n\nexport { AutocompletePrimitive };\n","type":"registry:ui","target":"~/src/siteplane/components/autocomplete/autocomplete.tsx"},{"path":"packages/ui/src/components/autocomplete/autocomplete-internal.ts","content":"import type * as React from \"react\";\n\nimport { AutocompleteInput, AutocompleteItem } from \"./autocomplete\";\n\n/*\n * Kit-internal Autocomplete parts. Not public API: the autocomplete index does\n * not export this module, and its names and profiles may change in any\n * release. Command renders its search input and items with the command\n * profile.\n */\nexport type KitAutocompleteInputProps = React.ComponentProps<\n  typeof AutocompleteInput\n> & { profile?: \"combobox\" | \"command\" };\n\nexport type KitAutocompleteItemProps = React.ComponentProps<\n  typeof AutocompleteItem\n> & { profile?: \"default\" | \"command\" };\n\n/** AutocompleteInput with the kit's composition profiles; the same component. */\nexport const KitAutocompleteInput = AutocompleteInput as (\n  props: KitAutocompleteInputProps,\n) => React.ReactElement;\n\n/** AutocompleteItem with the kit's composition profile; the same component. */\nexport const KitAutocompleteItem = AutocompleteItem as (\n  props: KitAutocompleteItemProps,\n) => React.ReactElement;\n","type":"registry:ui","target":"~/src/siteplane/components/autocomplete/autocomplete-internal.ts"},{"path":"packages/ui/src/components/autocomplete/index.ts","content":"export * from \"./autocomplete\";\n","type":"registry:ui","target":"~/src/siteplane/components/autocomplete/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/autocomplete.md","categories":["Forms & selection"],"meta":{"avoidWhen":["Avoid when the value must be one of the options; use Combobox.","Avoid when there is no typing and the options are fixed; use Select."],"description":"Text input with suggestions and no selection state.","knownLimitations":["The value is the text itself and there is no selected item. Use Combobox when the value must come from the list.","Fetching, stale results and pending or error states for async suggestions belong to the application."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["autocomplete"],"useWhen":["Use for a text input that suggests completions while it still accepts free text, such as a search box or an address line."]}}