{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "multi-select",
  "title": "Multi Select",
  "description": "Searchable multi-value select with badges, built on cmdk.",
  "dependencies": [
    "cmdk",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "command"
  ],
  "files": [
    {
      "path": "components/ui/multi-select.tsx",
      "content": "'use client'\n\nimport { Badge } from '@/components/ui/badge'\nimport {\n  Command,\n  CommandItem,\n  CommandEmpty,\n  CommandList,\n} from '@/components/ui/command'\nimport { cn } from '@/lib/utils'\nimport { Command as CommandPrimitive } from 'cmdk'\nimport { X as RemoveIcon, Check } from 'lucide-react'\nimport React, {\n  KeyboardEvent,\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useState,\n} from 'react'\n\ninterface MultiSelectorProps\n  extends React.ComponentPropsWithoutRef<typeof CommandPrimitive> {\n  values: string[]\n  onValuesChange: (value: string[]) => void\n  loop?: boolean\n}\n\ninterface MultiSelectContextProps {\n  value: string[]\n  onValueChange: (value: any) => void\n  open: boolean\n  setOpen: (value: boolean) => void\n  inputValue: string\n  setInputValue: React.Dispatch<React.SetStateAction<string>>\n  activeIndex: number\n  setActiveIndex: React.Dispatch<React.SetStateAction<number>>\n  ref: React.RefObject<HTMLInputElement | null>\n  handleSelect: (e: React.SyntheticEvent<HTMLInputElement>) => void\n}\n\nconst MultiSelectContext = createContext<MultiSelectContextProps | null>(null)\n\nconst useMultiSelect = () => {\n  const context = useContext(MultiSelectContext)\n  if (!context) {\n    throw new Error('useMultiSelect must be used within MultiSelectProvider')\n  }\n  return context\n}\n\n/**\n * MultiSelect Docs: {@link: https://shadcn-extension.vercel.app/docs/multi-select}\n */\n\n// TODO : expose the visibility of the popup\n\nconst MultiSelector = ({\n  values: value,\n  onValuesChange: onValueChange,\n  loop = false,\n  className,\n  children,\n  dir,\n  ...props\n}: MultiSelectorProps) => {\n  const [inputValue, setInputValue] = useState('')\n  const [open, setOpen] = useState<boolean>(false)\n  const [activeIndex, setActiveIndex] = useState<number>(-1)\n  const inputRef = React.useRef<HTMLInputElement | null>(null)\n  const [isValueSelected, setIsValueSelected] = React.useState(false)\n  const [selectedValue, setSelectedValue] = React.useState('')\n\n  const onValueChangeHandler = useCallback(\n    (val: string) => {\n      if (value.includes(val)) {\n        onValueChange(value.filter((item) => item !== val))\n      } else {\n        onValueChange([...value, val])\n      }\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [value],\n  )\n\n  const handleSelect = React.useCallback(\n    (e: React.SyntheticEvent<HTMLInputElement>) => {\n      e.preventDefault()\n      const target = e.currentTarget\n      const selection = target.value.substring(\n        target.selectionStart ?? 0,\n        target.selectionEnd ?? 0,\n      )\n\n      setSelectedValue(selection)\n      setIsValueSelected(selection === inputValue)\n    },\n    [inputValue],\n  )\n\n  const handleKeyDown = useCallback(\n    (e: KeyboardEvent<HTMLDivElement>) => {\n      e.stopPropagation()\n      const target = inputRef.current\n\n      if (!target) return\n\n      const moveNext = () => {\n        const nextIndex = activeIndex + 1\n        setActiveIndex(\n          nextIndex > value.length - 1 ? (loop ? 0 : -1) : nextIndex,\n        )\n      }\n\n      const movePrev = () => {\n        const prevIndex = activeIndex - 1\n        setActiveIndex(prevIndex < 0 ? value.length - 1 : prevIndex)\n      }\n\n      const moveCurrent = () => {\n        const newIndex =\n          activeIndex - 1 <= 0\n            ? value.length - 1 === 0\n              ? -1\n              : 0\n            : activeIndex - 1\n        setActiveIndex(newIndex)\n      }\n\n      switch (e.key) {\n        case 'ArrowLeft':\n          if (dir === 'rtl') {\n            if (value.length > 0 && (activeIndex !== -1 || loop)) {\n              moveNext()\n            }\n          } else {\n            if (value.length > 0 && target.selectionStart === 0) {\n              movePrev()\n            }\n          }\n          break\n\n        case 'ArrowRight':\n          if (dir === 'rtl') {\n            if (value.length > 0 && target.selectionStart === 0) {\n              movePrev()\n            }\n          } else {\n            if (value.length > 0 && (activeIndex !== -1 || loop)) {\n              moveNext()\n            }\n          }\n          break\n\n        case 'Backspace':\n        case 'Delete':\n          if (value.length > 0) {\n            if (activeIndex !== -1 && activeIndex < value.length) {\n              onValueChangeHandler(value[activeIndex])\n              moveCurrent()\n            } else {\n              if (target.selectionStart === 0) {\n                if (selectedValue === inputValue || isValueSelected) {\n                  onValueChangeHandler(value[value.length - 1])\n                }\n              }\n            }\n          }\n          break\n\n        case 'Enter':\n          setOpen(true)\n          break\n\n        case 'Escape':\n          if (activeIndex !== -1) {\n            setActiveIndex(-1)\n          } else if (open) {\n            setOpen(false)\n          }\n          break\n      }\n    },\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [value, inputValue, activeIndex, loop],\n  )\n\n  return (\n    <MultiSelectContext.Provider\n      value={{\n        value,\n        onValueChange: onValueChangeHandler,\n        open,\n        setOpen,\n        inputValue,\n        setInputValue,\n        activeIndex,\n        setActiveIndex,\n        ref: inputRef,\n        handleSelect,\n      }}\n    >\n      <Command\n        onKeyDown={handleKeyDown}\n        className={cn(\n          'overflow-visible bg-transparent flex flex-col space-y-2',\n          className,\n        )}\n        dir={dir}\n        {...props}\n      >\n        {children}\n      </Command>\n    </MultiSelectContext.Provider>\n  )\n}\n\nconst MultiSelectorTrigger = forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => {\n  const { value, onValueChange, activeIndex } = useMultiSelect()\n\n  const mousePreventDefault = useCallback((e: React.MouseEvent) => {\n    e.preventDefault()\n    e.stopPropagation()\n  }, [])\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        'flex flex-wrap gap-1 p-1 py-2 ring-1 ring-muted rounded-lg bg-background',\n        {\n          'ring-1 focus-within:ring-ring': activeIndex === -1,\n        },\n        className,\n      )}\n      {...props}\n    >\n      {value.map((item, index) => (\n        <Badge\n          key={item}\n          className={cn(\n            'px-1 rounded-xl flex items-center gap-1',\n            activeIndex === index && 'ring-2 ring-muted-foreground ',\n          )}\n          variant={'secondary'}\n        >\n          <span className=\"text-xs\">{item}</span>\n          <button\n            aria-label={`Remove ${item} option`}\n            aria-roledescription=\"button to remove option\"\n            type=\"button\"\n            onMouseDown={mousePreventDefault}\n            onClick={() => onValueChange(item)}\n          >\n            <span className=\"sr-only\">Remove {item} option</span>\n            <RemoveIcon className=\"h-4 w-4 hover:stroke-destructive\" />\n          </button>\n        </Badge>\n      ))}\n      {children}\n    </div>\n  )\n})\n\nMultiSelectorTrigger.displayName = 'MultiSelectorTrigger'\n\nconst MultiSelectorInput = forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Input>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>\n>(({ className, ...props }, ref) => {\n  const {\n    setOpen,\n    inputValue,\n    setInputValue,\n    activeIndex,\n    setActiveIndex,\n    handleSelect,\n    ref: contextInputRef,\n  } = useMultiSelect()\n\n  return (\n    <CommandPrimitive.Input\n      {...props}\n      tabIndex={0}\n      ref={(node) => {\n        if (contextInputRef) {\n          ;(contextInputRef as React.MutableRefObject<HTMLInputElement | null>).current = node\n        }\n        if (typeof ref === 'function') {\n          ref(node)\n        } else if (ref) {\n          ref.current = node\n        }\n      }}\n      value={inputValue}\n      onValueChange={activeIndex === -1 ? setInputValue : undefined}\n      onSelect={handleSelect}\n      onBlur={() => setOpen(false)}\n      onFocus={() => setOpen(true)}\n      onClick={() => setActiveIndex(-1)}\n      className={cn(\n        'ml-2 bg-transparent outline-none placeholder:text-muted-foreground placeholder:text-sm flex-1',\n        className,\n        activeIndex !== -1 && 'caret-transparent',\n      )}\n    />\n  )\n})\n\nMultiSelectorInput.displayName = 'MultiSelectorInput'\n\nconst MultiSelectorContent = forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ children }, ref) => {\n  const { open } = useMultiSelect()\n  return (\n    <div ref={ref} className=\"relative\">\n      {open && children}\n    </div>\n  )\n})\n\nMultiSelectorContent.displayName = 'MultiSelectorContent'\n\nconst MultiSelectorList = forwardRef<\n  React.ElementRef<typeof CommandPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>\n>(({ className, children }, ref) => {\n  return (\n    <CommandList\n      ref={ref}\n      className={cn(\n        'p-2 flex flex-col gap-2 rounded-md scrollbar-thin scrollbar-track-transparent transition-colors scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted scrollbar-thumb-rounded-lg w-full absolute bg-background shadow-md z-10 border border-muted top-0',\n        className,\n      )}\n    >\n      {children}\n      <CommandEmpty>\n        <span className=\"text-muted-foreground\">No results found</span>\n      </CommandEmpty>\n    </CommandList>\n  )\n})\n\nMultiSelectorList.displayName = 'MultiSelectorList'\n\nconst MultiSelectorItem = forwardRef<\n  React.ElementRef<typeof CommandPrimitive.Item>,\n  { value: string } & React.ComponentPropsWithoutRef<\n    typeof CommandPrimitive.Item\n  >\n>(({ className, value, children, ...props }, ref) => {\n  const { value: Options, onValueChange, setInputValue } = useMultiSelect()\n\n  const mousePreventDefault = useCallback((e: React.MouseEvent) => {\n    e.preventDefault()\n    e.stopPropagation()\n  }, [])\n\n  const isIncluded = Options.includes(value)\n  return (\n    <CommandItem\n      ref={ref}\n      {...props}\n      onSelect={() => {\n        onValueChange(value)\n        setInputValue('')\n      }}\n      className={cn(\n        'rounded-md cursor-pointer px-2 py-1 transition-colors flex justify-between ',\n        className,\n        isIncluded && 'opacity-50 cursor-default',\n        props.disabled && 'opacity-50 cursor-not-allowed',\n      )}\n      onMouseDown={mousePreventDefault}\n    >\n      {children}\n      {isIncluded && <Check className=\"h-4 w-4\" />}\n    </CommandItem>\n  )\n})\n\nMultiSelectorItem.displayName = 'MultiSelectorItem'\n\nexport {\n  MultiSelector,\n  MultiSelectorTrigger,\n  MultiSelectorInput,\n  MultiSelectorContent,\n  MultiSelectorList,\n  MultiSelectorItem,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "input",
    "picker"
  ],
  "type": "registry:ui"
}