{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "list-box",
  "title": "List Box",
  "description": "Accessible single- or multi-select list of options.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/list-box/list-box.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Check } from \"lucide-react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/** Selection behaviour for a ListBox. */\nexport type ListBoxSelectionMode = \"multiple\" | \"single\";\n\ntype ListBoxContextValue = {\n  disabled: boolean;\n  select: (value: string) => void;\n  selectedValues: string[];\n};\n\nconst ListBoxContext = React.createContext<ListBoxContextValue | null>(null);\n\nfunction useListBoxContext(): ListBoxContextValue {\n  const context = React.use(ListBoxContext);\n  if (!context) {\n    throw new Error(\"ListBoxItem must be used within a ListBox\");\n  }\n  return context;\n}\n\ntype ListBoxSelectionOptions = {\n  defaultValue: string[];\n  onValueChange?: (value: string[]) => void;\n  selectionMode: ListBoxSelectionMode;\n  value?: string[];\n};\n\nfunction nextSelection(\n  current: string[],\n  item: string,\n  selectionMode: ListBoxSelectionMode,\n): string[] {\n  if (selectionMode === \"single\") {\n    return [item];\n  }\n\n  return current.includes(item)\n    ? current.filter((entry) => entry !== item)\n    : [...current, item];\n}\n\nfunction useListBoxSelection({\n  defaultValue,\n  onValueChange,\n  selectionMode,\n  value,\n}: ListBoxSelectionOptions) {\n  const [internalValue, setInternalValue] = React.useState(defaultValue);\n  const selectedValues = value ?? internalValue;\n\n  const select = (item: string) => {\n    const next = nextSelection(selectedValues, item, selectionMode);\n    if (value === undefined) {\n      setInternalValue(next);\n    }\n\n    onValueChange?.(next);\n  };\n\n  return { select, selectedValues };\n}\n\n/** Accessible single- or multi-select list of options. */\nexport type ListBoxProps = {\n  children: React.ReactNode;\n  className?: string;\n  defaultValue?: string[];\n  disabled?: boolean;\n  label?: string;\n  onValueChange?: (value: string[]) => void;\n  selectionMode?: ListBoxSelectionMode;\n  value?: string[];\n};\n\nconst ListBox = ({\n  children,\n  className,\n  defaultValue = [],\n  disabled = false,\n  label,\n  onValueChange,\n  ref,\n  selectionMode = \"single\",\n  value,\n}: ListBoxProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const { select, selectedValues } = useListBoxSelection({\n    defaultValue,\n    onValueChange,\n    selectionMode,\n    value,\n  });\n  const context = React.useMemo<ListBoxContextValue>(\n    () => ({ disabled, select, selectedValues }),\n    [disabled, select, selectedValues],\n  );\n\n  return (\n    <ListBoxContext.Provider value={context}>\n      <div\n        aria-label={label}\n        aria-multiselectable={selectionMode === \"multiple\"}\n        className={cn(\n          \"flex flex-col gap-0.5 rounded-md border border-input bg-background p-1\",\n          className,\n        )}\n        ref={ref}\n        role=\"listbox\"\n      >\n        {children}\n      </div>\n    </ListBoxContext.Provider>\n  );\n};\nListBox.displayName = \"ListBox\";\n\n/** Single option within a ListBox. */\nexport type ListBoxItemProps = {\n  children: React.ReactNode;\n  className?: string;\n  disabled?: boolean;\n  value: string;\n};\n\nconst ListBoxItem = ({\n  children,\n  className,\n  disabled = false,\n  ref,\n  value,\n}: ListBoxItemProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const group = useListBoxContext();\n  const selected = group.selectedValues.includes(value);\n  const isDisabled = disabled || group.disabled;\n\n  const activate = () => {\n    if (!isDisabled) {\n      group.select(value);\n    }\n  };\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    if (event.key === \"Enter\" || event.key === \" \") {\n      event.preventDefault();\n      activate();\n    }\n  };\n\n  return (\n    <div\n      aria-disabled={isDisabled || undefined}\n      aria-selected={selected}\n      className={cn(\n        \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n        selected && \"bg-accent text-accent-foreground\",\n        isDisabled && \"pointer-events-none opacity-50\",\n        className,\n      )}\n      onClick={activate}\n      onKeyDown={handleKeyDown}\n      ref={ref}\n      role=\"option\"\n      tabIndex={isDisabled ? -1 : 0}\n    >\n      <Check\n        className={cn(\n          \"size-4 shrink-0\",\n          selected ? \"opacity-100\" : \"opacity-0\",\n        )}\n      />\n      <span className=\"flex-1\">{children}</span>\n    </div>\n  );\n};\nListBoxItem.displayName = \"ListBoxItem\";\n\nexport { ListBox, ListBoxItem };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.2.1",
  "stability": "stable"
}
