{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "multi-select",
  "description": "A multi-select input that emulates the Shadcn Select component as closely as possible.",
  "registryDependencies": [
    "popover",
    "command",
    "badge",
    "button"
  ],
  "files": [
    {
      "path": "src/registry/new-york/items/multi-select/components/multi-select.tsx",
      "content": "\"use client\"\n\nimport { CheckIcon, ChevronsUpDownIcon, XIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"@/components/ui/command\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n  type ComponentPropsWithoutRef,\n  type ReactNode,\n} from \"react\"\nimport { Badge } from \"@/components/ui/badge\"\n\ntype MultiSelectContextType = {\n  open: boolean\n  setOpen: (open: boolean) => void\n  selectedValues: Set<string>\n  toggleValue: (value: string) => void\n  items: Map<string, ReactNode>\n  single: boolean\n  onItemAdded: (value: string, label: ReactNode) => void\n}\nconst MultiSelectContext = createContext<MultiSelectContextType | null>(null)\n\nexport function MultiSelect({\n  children,\n  values,\n  defaultValues,\n  onValuesChange,\n  single = false,\n}: {\n  children: ReactNode\n  values?: string[]\n  defaultValues?: string[]\n  onValuesChange?: (values: string[]) => void\n  single?: boolean\n}) {\n  const [open, setOpen] = useState(false)\n  const [internalValues, setInternalValues] = useState(\n    new Set<string>(values ?? defaultValues),\n  )\n  const selectedValues = values ? new Set(values) : internalValues\n  const [items, setItems] = useState<Map<string, ReactNode>>(new Map())\n\n  function toggleValue(value: string) {\n    const getNewSet = (prev: Set<string>) => {\n      if (single) {\n        return prev.has(value) ? new Set<string>() : new Set<string>([value])\n      }\n      const newSet = new Set(prev)\n      if (newSet.has(value)) {\n        newSet.delete(value)\n      } else {\n        newSet.add(value)\n      }\n      return newSet\n    }\n    setInternalValues(getNewSet)\n    onValuesChange?.([...getNewSet(selectedValues)])\n    if (single) setOpen(false)\n  }\n\n  const onItemAdded = useCallback((value: string, label: ReactNode) => {\n    setItems(prev => {\n      if (prev.get(value) === label) return prev\n      return new Map(prev).set(value, label)\n    })\n  }, [])\n\n  return (\n    <MultiSelectContext\n      value={{\n        open,\n        setOpen,\n        selectedValues,\n        single,\n        toggleValue,\n        items,\n        onItemAdded,\n      }}\n    >\n      <Popover open={open} onOpenChange={setOpen} modal={true}>\n        {children}\n      </Popover>\n    </MultiSelectContext>\n  )\n}\n\nexport function MultiSelectTrigger({\n  className,\n  children,\n  ...props\n}: {\n  className?: string\n  children?: ReactNode\n} & ComponentPropsWithoutRef<typeof Button>) {\n  const { open } = useMultiSelectContext()\n\n  return (\n    <PopoverTrigger asChild>\n      <Button\n        {...props}\n        variant={props.variant ?? \"outline\"}\n        role={props.role ?? \"combobox\"}\n        aria-expanded={props[\"aria-expanded\"] ?? open}\n        className={cn(\n          \"flex h-auto min-h-9 w-fit items-center justify-between gap-2 overflow-hidden rounded-md border border-input bg-transparent px-3 py-1.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n          className,\n        )}\n      >\n        {children}\n        <ChevronsUpDownIcon className=\"size-4 shrink-0 opacity-50\" />\n      </Button>\n    </PopoverTrigger>\n  )\n}\n\nexport function MultiSelectValue({\n  placeholder,\n  clickToRemove = true,\n  className,\n  overflowBehavior = \"wrap-when-open\",\n  ...props\n}: {\n  placeholder?: string\n  clickToRemove?: boolean\n  overflowBehavior?: \"wrap\" | \"wrap-when-open\" | \"cutoff\"\n} & Omit<ComponentPropsWithoutRef<\"div\">, \"children\">) {\n  const { selectedValues, toggleValue, items, open, single } =\n    useMultiSelectContext()\n  const [overflowAmount, setOverflowAmount] = useState(0)\n  const valueRef = useRef<HTMLDivElement>(null)\n  const overflowRef = useRef<HTMLDivElement>(null)\n\n  const shouldWrap =\n    overflowBehavior === \"wrap\" ||\n    (overflowBehavior === \"wrap-when-open\" && open)\n\n  const checkOverflow = useCallback(() => {\n    if (valueRef.current == null) return\n\n    const containerElement = valueRef.current\n    const overflowElement = overflowRef.current\n    const items = containerElement.querySelectorAll<HTMLElement>(\n      \"[data-selected-item]\",\n    )\n\n    if (overflowElement != null) overflowElement.style.display = \"none\"\n    items.forEach(child => child.style.removeProperty(\"display\"))\n    let amount = 0\n    for (let i = items.length - 1; i >= 0; i--) {\n      const child = items[i]!\n      if (containerElement.scrollWidth <= containerElement.clientWidth) {\n        break\n      }\n      amount = items.length - i\n      child.style.display = \"none\"\n      overflowElement?.style.removeProperty(\"display\")\n    }\n    setOverflowAmount(amount)\n  }, [])\n\n  const handleResize = useCallback(\n    (node: HTMLDivElement) => {\n      valueRef.current = node\n\n      const mutationObserver = new MutationObserver(checkOverflow)\n      const observer = new ResizeObserver(debounce(checkOverflow, 100))\n\n      mutationObserver.observe(node, {\n        childList: true,\n        attributes: true,\n        attributeFilter: [\"class\", \"style\"],\n      })\n      observer.observe(node)\n\n      return () => {\n        observer.disconnect()\n        mutationObserver.disconnect()\n        valueRef.current = null\n      }\n    },\n    [checkOverflow],\n  )\n\n  if (selectedValues.size === 0 && placeholder) {\n    return (\n      <span className=\"min-w-0 overflow-hidden font-normal text-muted-foreground\">\n        {placeholder}\n      </span>\n    )\n  }\n\n  if (single && selectedValues.size > 0) {\n    return (\n      <span className=\"min-w-0 overflow-hidden\">\n        {items.get([...selectedValues][0])}\n      </span>\n    )\n  }\n\n  return (\n    <div\n      {...props}\n      ref={handleResize}\n      className={cn(\n        \"flex w-full gap-1.5 overflow-hidden\",\n        shouldWrap && \"h-full flex-wrap\",\n        className,\n      )}\n    >\n      {[...selectedValues]\n        .filter(value => items.has(value))\n        .map(value => (\n          <Badge\n            variant=\"outline\"\n            data-selected-item\n            className=\"group flex items-center gap-1\"\n            key={value}\n            onClick={\n              clickToRemove\n                ? e => {\n                    e.stopPropagation()\n                    toggleValue(value)\n                  }\n                : undefined\n            }\n          >\n            {items.get(value)}\n            {clickToRemove && (\n              <XIcon className=\"size-2 text-muted-foreground group-hover:text-destructive\" />\n            )}\n          </Badge>\n        ))}\n      <Badge\n        style={{\n          display: overflowAmount > 0 && !shouldWrap ? \"block\" : \"none\",\n        }}\n        variant=\"outline\"\n        ref={overflowRef}\n      >\n        +{overflowAmount}\n      </Badge>\n    </div>\n  )\n}\n\nexport function MultiSelectContent({\n  search = true,\n  children,\n  ...props\n}: {\n  search?: boolean | { placeholder?: string; emptyMessage?: string }\n  children: ReactNode\n} & Omit<ComponentPropsWithoutRef<typeof Command>, \"children\">) {\n  const canSearch = typeof search === \"object\" ? true : search\n\n  return (\n    <>\n      <div style={{ display: \"none\" }}>\n        <Command>\n          <CommandList>{children}</CommandList>\n        </Command>\n      </div>\n      <PopoverContent className=\"min-w-[var(--radix-popover-trigger-width)] p-0\">\n        <Command {...props}>\n          {canSearch ? (\n            <CommandInput\n              placeholder={\n                typeof search === \"object\" ? search.placeholder : undefined\n              }\n            />\n          ) : (\n            <button autoFocus className=\"sr-only\" />\n          )}\n          <CommandList>\n            {canSearch && (\n              <CommandEmpty>\n                {typeof search === \"object\" ? search.emptyMessage : undefined}\n              </CommandEmpty>\n            )}\n            {children}\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </>\n  )\n}\n\nexport function MultiSelectItem({\n  value,\n  children,\n  badgeLabel,\n  onSelect,\n  ...props\n}: {\n  badgeLabel?: ReactNode\n  value: string\n} & Omit<ComponentPropsWithoutRef<typeof CommandItem>, \"value\">) {\n  const { toggleValue, selectedValues, onItemAdded } = useMultiSelectContext()\n  const isSelected = selectedValues.has(value)\n\n  useEffect(() => {\n    onItemAdded(value, badgeLabel ?? children)\n  }, [value, children, onItemAdded, badgeLabel])\n\n  return (\n    <CommandItem\n      {...props}\n      onSelect={() => {\n        toggleValue(value)\n        onSelect?.(value)\n      }}\n    >\n      <CheckIcon\n        className={cn(\"mr-2 size-4\", isSelected ? \"opacity-100\" : \"opacity-0\")}\n      />\n      {children}\n    </CommandItem>\n  )\n}\n\nexport function MultiSelectGroup(\n  props: ComponentPropsWithoutRef<typeof CommandGroup>,\n) {\n  return <CommandGroup {...props} />\n}\n\nexport function MultiSelectSeparator(\n  props: ComponentPropsWithoutRef<typeof CommandSeparator>,\n) {\n  return <CommandSeparator {...props} />\n}\n\nfunction useMultiSelectContext() {\n  const context = useContext(MultiSelectContext)\n  if (context == null) {\n    throw new Error(\n      \"useMultiSelectContext must be used within a MultiSelectContext\",\n    )\n  }\n  return context\n}\n\nfunction debounce<T extends (...args: never[]) => void>(\n  func: T,\n  wait: number,\n): (...args: Parameters<T>) => void {\n  let timeout: ReturnType<typeof setTimeout> | null = null\n  return function (this: unknown, ...args: Parameters<T>) {\n    if (timeout) clearTimeout(timeout)\n    timeout = setTimeout(() => func.apply(this, args), wait)\n  }\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:component"
}