{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "multi-select",
  "title": "Multi Select",
  "description": "A select component for allowing multiple selections.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://optics.agusmayol.com.ar/r/button.json",
    "https://optics.agusmayol.com.ar/r/badge.json",
    "https://optics.agusmayol.com.ar/r/checkbox.json",
    "https://optics.agusmayol.com.ar/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/optics/multi-select.jsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\";\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\";\nimport { Button, buttonVariants } from \"@/registry/optics/button\";\nimport { cn } from \"@/registry/optics/lib/utils\";\nimport { Badge } from \"@/registry/optics/badge\";\nimport { Checkbox } from \"@/registry/optics/checkbox\";\n\n// Contexto para compartir el estado de selección entre todos los items\nconst MultiSelectContext = React.createContext({\n\tvalues: {},\n\tsetValue: () => {},\n\tonValuesChange: () => {},\n\titemCount: 0,\n\tcolors: {},\n\tregisterColor: () => {},\n});\n\nfunction Select({\n\tonValuesChange = undefined,\n\tonOpenChange = undefined,\n\t...props\n}) {\n\tconst [values, setValues] = React.useState({});\n\tconst [colors, setColors] = React.useState({});\n\tconst [open, setOpen] = React.useState(false);\n\tconst registeredItemsRef = React.useRef(new Set());\n\n\t// Función para registrar un item\n\tconst registerItem = React.useCallback((value) => {\n\t\tif (value && !registeredItemsRef.current.has(value)) {\n\t\t\tregisteredItemsRef.current.add(value);\n\t\t\tsetValues((prev) => {\n\t\t\t\tif (!(value in prev)) {\n\t\t\t\t\treturn { ...prev, [value]: false };\n\t\t\t\t}\n\t\t\t\treturn prev;\n\t\t\t});\n\t\t}\n\t}, []);\n\n\t// Función para registrar el color de un item\n\tconst registerColor = React.useCallback((value, color) => {\n\t\tif (value) {\n\t\t\tsetColors((prev) => ({ ...prev, [value]: color }));\n\t\t}\n\t}, []);\n\n\t// Función para actualizar un valor\n\tconst setValue = React.useCallback(\n\t\t(value, checked) => {\n\t\t\tsetValues((prev) => {\n\t\t\t\tconst newValues = { ...prev, [value]: checked };\n\t\t\t\t// Llamar al callback con el array de valores\n\t\t\t\tif (onValuesChange) {\n\t\t\t\t\tconst valuesArray = Object.entries(newValues).map(([key, val]) => ({\n\t\t\t\t\t\tvalue: key,\n\t\t\t\t\t\tchecked: val,\n\t\t\t\t\t}));\n\t\t\t\t\tonValuesChange(valuesArray);\n\t\t\t\t}\n\t\t\t\treturn newValues;\n\t\t\t});\n\t\t},\n\t\t[onValuesChange],\n\t);\n\n\tconst handleOpenChange = React.useCallback(\n\t\t(newOpen) => {\n\t\t\tsetOpen(newOpen);\n\t\t\tif (onOpenChange) {\n\t\t\t\tonOpenChange(newOpen);\n\t\t\t}\n\t\t},\n\t\t[onOpenChange],\n\t);\n\n\tconst contextValue = React.useMemo(\n\t\t() => ({\n\t\t\tvalues,\n\t\t\tsetValue,\n\t\t\tregisterItem,\n\t\t\titemCount: registeredItemsRef.current.size,\n\t\t\tcolors,\n\t\t\tregisterColor,\n\t\t}),\n\t\t[values, setValue, registerItem, colors, registerColor],\n\t);\n\n\treturn (\n\t\t<MultiSelectContext.Provider value={contextValue}>\n\t\t\t<SelectPrimitive.Root\n\t\t\t\tdata-slot=\"select\"\n\t\t\t\topen={open}\n\t\t\t\tonOpenChange={handleOpenChange}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</MultiSelectContext.Provider>\n\t);\n}\n\nfunction SelectGroup({ ...props } = {}) {\n\treturn <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue({ ...props } = {}) {\n\treturn <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({\n\tclassName = \"\",\n\tsize = \"default\",\n\tchildren = null,\n\tvariant = undefined,\n\t...props\n}) {\n\tconst { values, colors, itemCount } = React.useContext(MultiSelectContext);\n\n\t// Obtener los items seleccionados con sus colores\n\tconst selectedItems = React.useMemo(() => {\n\t\treturn Object.entries(values)\n\t\t\t.filter(([_, checked]) => checked)\n\t\t\t.map(([value, _]) => ({\n\t\t\t\tvalue,\n\t\t\t\tcolor: colors[value] || \"bg-gray-200\",\n\t\t\t}));\n\t}, [values, colors]);\n\n\tconst selectedCount = selectedItems.length;\n\tconst totalCount = itemCount;\n\n\treturn (\n\t\t<SelectPrimitive.Trigger\n\t\t\tdata-slot=\"select-trigger\"\n\t\t\tdata-size={size}\n\t\t\tclassName={cn(\n\t\t\t\t\"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit min-w-3xs items-center justify-between gap-2 rounded-lg border bg-transparent px-4 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 cursor-pointer\",\n\t\t\t\tbuttonVariants({\n\t\t\t\t\tvariant: variant,\n\t\t\t\t\tsize: \"icon-lg\",\n\t\t\t\t\tanimation: \"colors\",\n\t\t\t\t}),\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<div className=\"flex items-center gap-2 flex-1 min-w-0\">\n\t\t\t\t{selectedCount > 0 && (\n\t\t\t\t\t<div className=\"flex items-center -space-x-0.25 -ml-1\">\n\t\t\t\t\t\t{selectedItems.map((item, index) => (\n\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\tkey={item.value}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"rounded-full squircle-none aspect-square size-3 p-0 border border-background -ml-1 first:ml-0\",\n\t\t\t\t\t\t\t\t\titem.color,\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tzIndex: selectedItems.length - index,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t\t{totalCount > 0 && (\n\t\t\t\t<span className=\"text-xs text-muted-foreground ml-2 shrink-0\">\n\t\t\t\t\t{selectedCount}/{totalCount}\n\t\t\t\t</span>\n\t\t\t)}\n\t\t\t<SelectPrimitive.Icon\n\t\t\t\trender={<ChevronDownIcon className=\"size-4 opacity-50 shrink-0\" />}\n\t\t\t/>\n\t\t</SelectPrimitive.Trigger>\n\t);\n}\n\nfunction SelectContent({\n\tclassName = \"\",\n\tchildren = null,\n\tside = \"bottom\",\n\t...props\n}) {\n\treturn (\n\t\t<SelectPrimitive.Portal>\n\t\t\t<SelectPrimitive.Positioner\n\t\t\t\tside={side}\n\t\t\t\tsideOffset={4}\n\t\t\t\tclassName=\"isolate z-50\"\n\t\t\t>\n\t\t\t\t<SelectPrimitive.Popup\n\t\t\t\t\tdata-slot=\"select-content\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--available-height) min-w-[8rem] origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border shadow-md\",\n\t\t\t\t\t\tclassName,\n\t\t\t\t\t)}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t<SelectScrollUpButton />\n\t\t\t\t\t<SelectPrimitive.List className=\"p-1\">\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</SelectPrimitive.List>\n\t\t\t\t\t<SelectScrollDownButton />\n\t\t\t\t</SelectPrimitive.Popup>\n\t\t\t</SelectPrimitive.Positioner>\n\t\t</SelectPrimitive.Portal>\n\t);\n}\n\nfunction SelectLabel({ className = \"\", ...props }) {\n\treturn (\n\t\t<SelectPrimitive.GroupLabel\n\t\t\tdata-slot=\"select-label\"\n\t\t\tclassName={cn(\"text-muted-foreground p-1.5 text-xs\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction SelectItem({\n\tclassName = \"\",\n\tchildren = null,\n\tvalue,\n\tcolor = undefined,\n\t...props\n}) {\n\tconst { values, setValue, registerItem, registerColor } =\n\t\tReact.useContext(MultiSelectContext);\n\tconst [isHoveringCheckbox, setIsHoveringCheckbox] = React.useState(false);\n\tconst [isHoveringText, setIsHoveringText] = React.useState(false);\n\n\t// Registrar el item cuando se monta\n\tReact.useEffect(() => {\n\t\tif (value) {\n\t\t\tregisterItem(value);\n\t\t}\n\t}, [value, registerItem]);\n\n\t// Registrar el color cuando se monta o cambia\n\tReact.useEffect(() => {\n\t\tif (value && color) {\n\t\t\tregisterColor(value, color);\n\t\t}\n\t}, [value, color, registerColor]);\n\n\tconst checked = value ? (values[value] ?? false) : false;\n\n\t// Calcular estados\n\tconst allChecked = Object.values(values).every((v) => v === true);\n\tconst someChecked = Object.values(values).some((v) => v === true);\n\tconst noneChecked = !someChecked;\n\n\t// Determinar qué texto mostrar según los comentarios\n\tconst getActionText = () => {\n\t\tif (checked && isHoveringCheckbox) return \"Uncheck\";\n\t\tif (noneChecked && (isHoveringCheckbox || isHoveringText)) return \"Check\";\n\t\tif (someChecked && !checked && isHoveringCheckbox) return \"Check\";\n\t\tif (!checked && someChecked && isHoveringText) return \"Only\";\n\t\tif (checked && allChecked && isHoveringText) return \"Only\";\n\t\tif (checked && someChecked && !allChecked && isHoveringText)\n\t\t\treturn \"Check All\";\n\t\treturn \"\";\n\t};\n\n\tconst handleCheckboxClick = (e) => {\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\t\tif (value) {\n\t\t\tsetValue(value, !checked);\n\t\t}\n\t};\n\n\tconst handleTextClick = (e) => {\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\t\tif (!value) return;\n\n\t\tif (checked && someChecked && !allChecked && isHoveringText) {\n\t\t\tObject.keys(values).forEach((key) => {\n\t\t\t\tsetValue(key, true);\n\t\t\t});\n\t\t} else if (\n\t\t\tisHoveringText &&\n\t\t\t((!checked && someChecked) || (checked && allChecked))\n\t\t) {\n\t\t\tObject.keys(values).forEach((key) => {\n\t\t\t\tsetValue(key, key === value);\n\t\t\t});\n\t\t} else {\n\t\t\tsetValue(value, true);\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className=\"w-full min-w-0 flex items-center gap-0.5 group overflow-hidden\">\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\tbuttonVariants({\n\t\t\t\t\t\tvariant: \"ghost\",\n\t\t\t\t\t\tsize: \"icon\",\n\t\t\t\t\t\tanimation: \"none\",\n\t\t\t\t\t\tclassName: \"hover:bg-accent/75\",\n\t\t\t\t\t}),\n\t\t\t\t\t\"aspect-square [&_svg]:!size-3.5 shrink-0\",\n\t\t\t\t)}\n\t\t\t\tonClick={handleCheckboxClick}\n\t\t\t\tonMouseEnter={() => setIsHoveringCheckbox(true)}\n\t\t\t\tonMouseLeave={() => setIsHoveringCheckbox(false)}\n\t\t\t>\n\t\t\t\t<Checkbox checked={checked} />\n\t\t\t</div>\n\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\tbuttonVariants({\n\t\t\t\t\t\tvariant: \"ghost\",\n\t\t\t\t\t\tsize: \"\",\n\t\t\t\t\t\tanimation: \"none\",\n\t\t\t\t\t\tclassName: \"hover:bg-accent/75\",\n\t\t\t\t\t}),\n\t\t\t\t\t\"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex items-center justify-between gap-2 flex-1 min-w-0 cursor-pointer rounded-lg py-1.5 pr-2 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 overflow-hidden\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tonClick={handleTextClick}\n\t\t\t\tonMouseEnter={() => setIsHoveringText(true)}\n\t\t\t\tonMouseLeave={() => setIsHoveringText(false)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t<div className=\"flex items-center gap-2 min-w-0 flex-1 overflow-hidden\">\n\t\t\t\t\t<Badge\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"rounded-full squircle-none aspect-square size-3 p-0 bg-gray-200 border border-background shrink-0\",\n\t\t\t\t\t\t\tcolor,\n\t\t\t\t\t\t)}\n\t\t\t\t\t/>\n\t\t\t\t\t<span className=\"truncate min-w-0\">{children}</span>\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"flex items-center justify-center text-xs text-muted-foreground font-medium opacity-0 group-hover:opacity-100 transition-opacity duration-50 shrink-0 ml-1\">\n\t\t\t\t\t{getActionText()}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SelectSeparator({ className, ...props }) {\n\treturn (\n\t\t<SelectPrimitive.Separator\n\t\t\tdata-slot=\"select-separator\"\n\t\t\tclassName={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction SelectScrollUpButton({ className, ...props }) {\n\treturn (\n\t\t<SelectPrimitive.ScrollUpArrow\n\t\t\tdata-slot=\"select-scroll-up-button\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex cursor-default items-center justify-center py-1\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<ChevronUpIcon className=\"size-4\" />\n\t\t</SelectPrimitive.ScrollUpArrow>\n\t);\n}\n\nfunction SelectScrollDownButton({ className, ...props }) {\n\treturn (\n\t\t<SelectPrimitive.ScrollDownArrow\n\t\t\tdata-slot=\"select-scroll-down-button\"\n\t\t\tclassName={cn(\n\t\t\t\t\"flex cursor-default items-center justify-center py-1\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<ChevronDownIcon className=\"size-4\" />\n\t\t</SelectPrimitive.ScrollDownArrow>\n\t);\n}\n\nSelect.displayName = \"MultiSelect\";\nSelectContent.displayName = \"MultiSelectContent\";\nSelectGroup.displayName = \"MultiSelectGroup\";\nSelectItem.displayName = \"MultiSelectItem\";\nSelectLabel.displayName = \"MultiSelectLabel\";\nSelectScrollDownButton.displayName = \"MultiSelectScrollDownButton\";\nSelectScrollUpButton.displayName = \"MultiSelectScrollUpButton\";\nSelectSeparator.displayName = \"MultiSelectSeparator\";\nSelectTrigger.displayName = \"MultiSelectTrigger\";\nSelectValue.displayName = \"MultiSelectValue\";\n\nexport {\n\tSelect,\n\tSelectContent,\n\tSelectGroup,\n\tSelectItem,\n\tSelectLabel,\n\tSelectScrollDownButton,\n\tSelectScrollUpButton,\n\tSelectSeparator,\n\tSelectTrigger,\n\tSelectValue,\n};\n",
      "type": "registry:component",
      "target": "components/optics/multi-select.jsx"
    }
  ],
  "type": "registry:component"
}