{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "listbox",
  "title": "Listbox",
  "description": "One highlight glides between options and checks pop in as you choose.",
  "dependencies": [
    "@ui-sdk/core@0.1.0-beta.2",
    "@ui-sdk/react@0.1.0-beta.2"
  ],
  "registryDependencies": [
    "@keyframery/motion"
  ],
  "files": [
    {
      "path": "registry/components/keyframery/primitives/listbox.tsx",
      "content": "\"use client\"\n\n/* Animated drop-in for `@ui-sdk/react/listbox`: same parts, same props. Swap the import and keep your code.\n   This file adds motion only. Colours, radii, shadows and spacing stay yours.\n\n   - Glide: one highlight springs from row to row (CSS anchor positioning): to the row under the\n     pointer, or to the focused row while the list has focus. Its neutral surface follows the text\n     colour; give `before:bg-…` and `before:rounded-…` to restyle it.\n   - Draw: a chosen row's check draws itself, and its indicator pops in (`ease-pop`, 0.6 to 1). */\n\nimport {\n  Listbox as ListboxPart,\n  ListboxItem as ListboxItemPart,\n  ListboxItemIndicator as ListboxItemIndicatorPart,\n  type ListboxProps,\n} from \"@ui-sdk/react/listbox\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport * from \"@ui-sdk/react/listbox\"\n\nconst keyframes = \"@keyframes kf-pop-in{from{scale:0.6}}\"\n\nfunction Listbox<T = string, Multiple extends boolean | undefined = false>({\n  className,\n  ...props\n}: ListboxProps<T, Multiple>) {\n  /* Hold the pop until after the first paint, so chosen rows don't pop on load. */\n  const [ready, setReady] = React.useState(false)\n  React.useEffect(() => {\n    const timer = setTimeout(() => setReady(true), 150)\n    return () => clearTimeout(timer)\n  }, [])\n  return (\n    <>\n      <style href=\"kf-listbox-pop\" precedence=\"default\">\n        {keyframes}\n      </style>\n      <ListboxPart<T, Multiple>\n        {...({\n          ...props,\n          \"data-kf-ready\": ready || undefined,\n          className: cn(\n            \"group/kf-lb relative isolate [anchor-scope:--kf-hl]\",\n            \"before:pointer-events-none before:absolute before:-z-10 before:hidden before:bg-[color-mix(in_oklch,currentColor_9%,transparent)]\",\n            \"before:[position-anchor:--kf-hl] before:top-[anchor(top)] before:right-[anchor(right)] before:bottom-[anchor(bottom)] before:left-[anchor(left)]\",\n            \"before:[transition:top_calc(260ms*var(--kf-speed,1))_var(--ease-spring),right_calc(260ms*var(--kf-speed,1))_var(--ease-spring),bottom_calc(260ms*var(--kf-speed,1))_var(--ease-spring),left_calc(260ms*var(--kf-speed,1))_var(--ease-spring),opacity_calc(150ms*var(--kf-speed,1))_var(--ease-house)]\",\n            \"has-[[data-slot=listbox-item]:hover]:before:block focus-within:has-[[data-highlighted]]:before:block starting:before:opacity-0\",\n            \"motion-reduce:before:[transition:none]!\",\n            className,\n          ),\n        } as ListboxProps<T, Multiple>)}\n      />\n    </>\n  )\n}\n\n/* The highlight follows the pointer; without one, the focused row. */\nfunction ListboxItem<T = string>({ className, ...props }: React.ComponentProps<typeof ListboxItemPart<T>>) {\n  return (\n    <ListboxItemPart<T>\n      className={cn(\n        \"hover:[anchor-name:--kf-hl] data-highlighted:[anchor-name:--kf-hl]\",\n        \"group-has-[[data-slot=listbox-item]:hover]/kf-lb:data-highlighted:not-hover:[anchor-name:none]\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/* Draw: the check's stroke draws in as its row is chosen, and the indicator pops.\n   Works with any check path up to 24 units long. */\nfunction ListboxItemIndicator({ className, ...props }: React.ComponentProps<typeof ListboxItemIndicatorPart>) {\n  return (\n    <ListboxItemIndicatorPart\n      className={cn(\n        \"[&_:is(path,polyline)]:[stroke-dasharray:24] [&_:is(path,polyline)]:[stroke-dashoffset:24]\",\n        \"[&_:is(path,polyline)]:transition-[stroke-dashoffset] [&_:is(path,polyline)]:duration-[calc(260ms*var(--kf-speed,1))] [&_:is(path,polyline)]:ease-house\",\n        \"data-selected:[&_:is(path,polyline)]:[stroke-dashoffset:0] data-selected:[&_:is(path,polyline)]:delay-[calc(75ms*var(--kf-speed,1))]\",\n        \"data-selected:[&_:is(path,polyline)]:starting:[stroke-dashoffset:24]\",\n        \"motion-reduce:animate-none! group-data-kf-ready/kf-lb:data-selected:[animation:kf-pop-in_calc(380ms*var(--kf-speed,1))_var(--ease-pop)]\",\n        \"motion-reduce:[&_:is(path,polyline)]:transition-none motion-reduce:animate-none!\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Listbox, ListboxItem, ListboxItemIndicator }\n",
      "type": "registry:component",
      "target": "components/keyframery/primitives/listbox.tsx"
    },
    {
      "path": "registry/components/keyframery/listbox.tsx",
      "content": "\"use client\"\n\n/* Styled listbox: the animated primitive plus the Keyframery look. */\n\nimport {\n  createListboxController,\n  type ListData,\n  Listbox as ListboxRoot,\n  ListboxGroup as ListboxGroupPart,\n  ListboxGroupLabel as ListboxGroupLabelPart,\n  ListboxItem as ListboxItemPart,\n  ListboxItemIcon as ListboxItemIconPart,\n  ListboxItemIndicator as ListboxItemIndicatorPart,\n  type ListboxProps,\n  ListboxSeparator as ListboxSeparatorPart,\n} from \"@/components/keyframery/primitives/listbox\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction CheckIcon() {\n  return (\n    <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n      <path d=\"m3.5 8.5 3 3 6-7\" />\n    </svg>\n  )\n}\n\n/* The frame is a field that holds rows. It takes the field's focus ring while a row inside\n   has keyboard focus. */\nfunction Listbox<T = string, Multiple extends boolean | undefined = false>({\n  className,\n  ...props\n}: ListboxProps<T, Multiple>) {\n  return (\n    <ListboxRoot<T, Multiple>\n      {...({\n        ...props,\n        className: cn(\n          \"group/listbox flex max-h-72 flex-col gap-px overflow-y-auto overscroll-contain rounded-[12px] bg-background p-1.5 text-[13px] outline-none scroll-py-1.5\",\n          \"data-[orientation=horizontal]:flex-row data-[orientation=horizontal]:overflow-x-auto data-[orientation=horizontal]:overflow-y-hidden\",\n          \"shadow-[0_0_0_1px_rgb(0_0_0/0.09),0_1px_2px_rgb(0_0_0/0.05)] dark:shadow-[0_0_0_1px_rgb(255_255_255/0.1)]\",\n          \"transition-shadow duration-[calc(150ms*var(--kf-speed,1))] motion-reduce:transition-none!\",\n          \"before:rounded-[8px] before:bg-accent\",\n          \"hover:shadow-[0_0_0_1px_rgb(0_0_0/0.16),0_1px_2px_rgb(0_0_0/0.05)] dark:hover:shadow-[0_0_0_1px_rgb(255_255_255/0.16)]\",\n          \"has-focus-visible:shadow-[0_0_0_1px_var(--ring)] has-focus-visible:ring-[3px] has-focus-visible:ring-ring/25 dark:has-focus-visible:shadow-[0_0_0_1px_var(--ring)]\",\n          \"focus-visible:shadow-[0_0_0_1px_var(--ring)] focus-visible:ring-[3px] focus-visible:ring-ring/25 dark:focus-visible:shadow-[0_0_0_1px_var(--ring)]\",\n          \"aria-invalid:shadow-[0_0_0_1px_var(--destructive)] dark:aria-invalid:shadow-[0_0_0_1px_var(--destructive)]\",\n          \"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-readonly:bg-muted/40\",\n          className,\n        ),\n      } as ListboxProps<T, Multiple>)}\n    />\n  )\n}\n\n/* A row. No background of its own: the primitive's highlight glides under it. */\nfunction ListboxItem<T = string>({\n  className,\n  ...props\n}: React.ComponentProps<typeof ListboxItemPart<T>>) {\n  return (\n    <ListboxItemPart<T>\n      className={cn(\n        \"group/item relative flex min-h-8 shrink-0 cursor-default items-center gap-2 rounded-[8px] px-2 py-1.5 leading-4 text-foreground outline-none select-none\",\n        \"data-disabled:cursor-not-allowed data-disabled:text-muted-foreground/60\",\n        \"group-data-readonly/listbox:cursor-default\",\n        \"[[data-has-icons]_&:not([data-has-icon])]:ps-8\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/* The selection mark. A single list shows a check; a multiple list a small box that fills.\n   The primitive draws the check and pops the mark in. */\nfunction ListboxItemIndicator({\n  className,\n  children,\n  keepMounted = true,\n  ...props\n}: React.ComponentProps<typeof ListboxItemIndicatorPart>) {\n  return (\n    <ListboxItemIndicatorPart\n      keepMounted={keepMounted}\n      className={cn(\n        \"ms-auto grid size-4 shrink-0 place-items-center text-foreground [&_svg]:size-4\",\n        /* Multiple: a small box, empty until chosen. */\n        \"group-aria-multiselectable/listbox:rounded-[5px] group-aria-multiselectable/listbox:text-primary-foreground group-aria-multiselectable/listbox:[&_svg]:size-3\",\n        \"group-aria-multiselectable/listbox:shadow-[inset_0_0_0_1px_rgb(0_0_0/0.18)] dark:group-aria-multiselectable/listbox:shadow-[inset_0_0_0_1px_rgb(255_255_255/0.2)]\",\n        \"group-aria-multiselectable/listbox:transition-[background-color,box-shadow] group-aria-multiselectable/listbox:duration-[calc(150ms*var(--kf-speed,1))]\",\n        \"group-aria-multiselectable/listbox:data-selected:bg-primary group-aria-multiselectable/listbox:data-selected:shadow-[inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.2)]\",\n        \"dark:group-aria-multiselectable/listbox:data-selected:shadow-[inset_0_1px_0_rgb(255_255_255/0.6)]\",\n        \"group-aria-multiselectable/listbox:[&_svg]:[stroke-width:2] group-data-disabled/item:opacity-50\",\n        \"motion-reduce:transition-none!\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? <CheckIcon />}\n    </ListboxItemIndicatorPart>\n  )\n}\n\nfunction ListboxItemIcon({\n  className,\n  ...props\n}: React.ComponentProps<typeof ListboxItemIconPart>) {\n  return (\n    <ListboxItemIconPart\n      className={cn(\n        \"grid size-4 shrink-0 place-items-center text-muted-foreground transition-colors duration-[calc(150ms*var(--kf-speed,1))] [&_svg]:size-4\",\n        \"group-data-highlighted/item:text-current group-data-highlighted/item:duration-0 group-data-selected/item:text-foreground\",\n        \"rtl:data-mirror:-scale-x-100 motion-reduce:transition-none!\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ListboxGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof ListboxGroupPart>) {\n  return <ListboxGroupPart className={cn(\"flex flex-col gap-px\", className)} {...props} />\n}\n\nfunction ListboxGroupLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof ListboxGroupLabelPart>) {\n  return (\n    <ListboxGroupLabelPart\n      className={cn(\n        \"px-2 pt-1.5 pb-1 text-[11px] leading-4 font-medium text-muted-foreground select-none\",\n        \"[[data-has-icons]_&]:ps-8\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ListboxSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof ListboxSeparatorPart>) {\n  return (\n    <ListboxSeparatorPart\n      className={cn(\n        \"-mx-1.5 my-1 h-px shrink-0 bg-border\",\n        \"group-data-[orientation=horizontal]/listbox:mx-1 group-data-[orientation=horizontal]/listbox:my-0 group-data-[orientation=horizontal]/listbox:h-auto group-data-[orientation=horizontal]/listbox:w-px\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/* Renders items passed as data with the styled parts above. */\nfunction ListboxItems({ items }: { items?: ListData<unknown> }) {\n  return (items ?? []).map((entry, index) => {\n    if (typeof entry === \"string\") {\n      return (\n        <ListboxItem key={entry} value={entry}>\n          {entry}\n          <ListboxItemIndicator />\n        </ListboxItem>\n      )\n    }\n    if (\"type\" in entry && entry.type === \"separator\") {\n      return <ListboxSeparator key={`separator-${index}`} />\n    }\n    if (\"type\" in entry && entry.type === \"group\") {\n      return (\n        <ListboxGroup key={`group-${entry.label ?? index}`}>\n          {entry.label && <ListboxGroupLabel>{entry.label}</ListboxGroupLabel>}\n          <ListboxItems items={entry.items} />\n        </ListboxGroup>\n      )\n    }\n    const { value, label, disabled } = entry\n    return (\n      <ListboxItem<unknown>\n        key={typeof value === \"string\" ? value : index}\n        value={value}\n        label={label}\n        disabled={disabled}\n      >\n        {label}\n        <ListboxItemIndicator />\n      </ListboxItem>\n    )\n  })\n}\n\nexport type { ListData } from \"@/components/keyframery/primitives/listbox\"\nexport {\n  createListboxController,\n  Listbox,\n  ListboxGroup,\n  ListboxGroupLabel,\n  ListboxItem,\n  ListboxItemIcon,\n  ListboxItemIndicator,\n  ListboxItems,\n  ListboxSeparator,\n}\n",
      "type": "registry:component",
      "target": "components/keyframery/listbox.tsx"
    }
  ],
  "type": "registry:component"
}