{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kbd",
  "title": "KBD",
  "description": "Keyboard key component for displaying keyboard shortcuts.",
  "dependencies": [
    "react-hotkeys-hook",
    "class-variance-authority",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://optics.agusmayol.com.ar/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/optics/kbd.jsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/registry/optics/lib/utils\";\nimport { cva } from \"class-variance-authority\";\nimport { useHotkeys } from \"react-hotkeys-hook\";\nimport {\n\tuseCallback,\n\tuseRef,\n\tuseState,\n\tuseMemo,\n\tisValidElement,\n\tuseEffect,\n} from \"react\";\n\n/**\n * Extrae texto de children de React\n */\nfunction extractTextFromChildren(children) {\n\tif (!children) return \"\";\n\tif (typeof children === \"string\") return children;\n\tif (typeof children === \"number\") return String(children);\n\tif (Array.isArray(children)) {\n\t\treturn children\n\t\t\t.map((child) => extractTextFromChildren(child))\n\t\t\t.filter(Boolean)\n\t\t\t.join(\"\");\n\t}\n\tif (isValidElement(children)) {\n\t\treturn extractTextFromChildren(children.props.children);\n\t}\n\tif (typeof children === \"object\" && children?.props?.children) {\n\t\treturn extractTextFromChildren(children.props.children);\n\t}\n\treturn \"\";\n}\n\n/**\n * Normaliza un string de hotkey al formato que react-hotkeys-hook espera\n */\nfunction normalizeHotkeyString(str) {\n\tif (!str) return null;\n\tlet normalized = str.trim().toLowerCase();\n\tnormalized = normalized\n\t\t.replace(/⌘|cmd|command|meta/gi, \"mod\")\n\t\t.replace(/ctrl|control/gi, \"ctrl\")\n\t\t.replace(/alt|option/gi, \"alt\")\n\t\t.replace(/shift/gi, \"shift\")\n\t\t.replace(/[\\s,\\-]+/g, \"+\")\n\t\t.replace(/\\++/g, \"+\")\n\t\t.replace(/^\\+|\\+$/g, \"\");\n\treturn normalized || null;\n}\n\n/**\n * Lista de modificadores conocidos\n */\nconst MODIFIER_KEYS = new Set([\n\t\"mod\",\n\t\"ctrl\",\n\t\"alt\",\n\t\"shift\",\n\t\"meta\",\n\t\"cmd\",\n\t\"command\",\n]);\n\n/**\n * Verifica si una tecla es un modificador\n */\nfunction isModifierKey(key) {\n\treturn MODIFIER_KEYS.has(key.toLowerCase());\n}\n\nconst kbdVariants = cva(\n\t\"select-none outline-hidden transition-all duration-150\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault:\n\t\t\t\t\t\"transform-gpu cursor-pointer rounded-lg border border-neutral-500/50 bg-neutral-300 shadow-[-10px_0px_15px_rgba(255,255,255,1),3px_10px_12.5px_rgba(0,0,0,0.1)] active:shadow-none dark:border-neutral-700 dark:bg-neutral-900 dark:shadow-[-10px_0px_15px_rgba(0,0,0,0.3),3px_10px_12.5px_rgba(255,255,255,0.05)]\",\n\t\t\t\tlegacy:\n\t\t\t\t\t\"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium [&_svg:not([class*='size-'])]:size-3 [[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nconst kbdInnerVariants = cva(\n\t\"flex items-center justify-center size-full transform-gpu rounded-[calc(var(--radius)-1px)] transition-all duration-150\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault:\n\t\t\t\t\t\"-translate-y-1 z-10 bg-neutral-100 px-3 py-1 text-neutral-500 shadow-[inset_0px_2px_4px_rgba(255,255,255,0.8)] active:translate-y-0 active:shadow-transparent dark:bg-neutral-800 dark:text-neutral-300 dark:shadow-[inset_0px_2px_4px_rgba(255,255,255,0.05)]\",\n\t\t\t\tlegacy: \"\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\n/**\n * Verifica si las teclas del evento coinciden con el hotkey\n */\nfunction matchesHotkey(hotkey, event) {\n\tif (!hotkey || !event) return false;\n\n\tconst parts = hotkey.split(\"+\");\n\tconst lastPart = parts[parts.length - 1].toLowerCase();\n\tconst isOnlyModifier = parts.length === 1 && isModifierKey(lastPart);\n\n\tif (isOnlyModifier) {\n\t\t// Si es solo un modificador, verificar solo ese modificador\n\t\tif (lastPart === \"mod\") {\n\t\t\treturn event.metaKey || event.ctrlKey;\n\t\t}\n\t\tif (lastPart === \"ctrl\") {\n\t\t\treturn event.ctrlKey;\n\t\t}\n\t\tif (lastPart === \"alt\") {\n\t\t\treturn event.altKey;\n\t\t}\n\t\tif (lastPart === \"shift\") {\n\t\t\treturn event.shiftKey;\n\t\t}\n\t\treturn false;\n\t}\n\n\t// Hotkey con modificadores + tecla principal\n\tconst modifiers = parts.slice(0, -1);\n\tconst key = lastPart;\n\n\t// Verificar modificadores requeridos\n\tconst requiresMod = modifiers.includes(\"mod\");\n\tconst requiresCtrl = modifiers.includes(\"ctrl\");\n\tconst requiresAlt = modifiers.includes(\"alt\");\n\tconst requiresShift = modifiers.includes(\"shift\");\n\n\t// Verificar que los modificadores requeridos estén presionados\n\tconst hasMod = requiresMod ? event.metaKey || event.ctrlKey : true;\n\tconst hasCtrl = requiresCtrl ? event.ctrlKey : true;\n\tconst hasAlt = requiresAlt ? event.altKey : true;\n\tconst hasShift = requiresShift ? event.shiftKey : true;\n\n\t// Verificar que no haya modificadores extra no requeridos\n\tconst noExtraMods =\n\t\t(!requiresMod && !requiresCtrl ? !event.ctrlKey && !event.metaKey : true) &&\n\t\t(!requiresAlt ? !event.altKey : true) &&\n\t\t(!requiresShift ? !event.shiftKey : true);\n\n\t// Verificar la tecla principal\n\tconst eventKey = event.key?.toLowerCase() || \"\";\n\tconst eventCode = event.code?.toLowerCase().replace(\"key\", \"\") || \"\";\n\tconst keyMatch = eventKey === key || eventCode === key;\n\n\t// Para combinaciones, todos los modificadores y la tecla principal deben coincidir\n\treturn hasMod && hasCtrl && hasAlt && hasShift && noExtraMods && keyMatch;\n}\n\nfunction Kbd({\n\tclassName = \"\",\n\tvariant = \"default\",\n\tuseHotkey = false,\n\tanimate = true,\n\tonHotkeyPress = undefined,\n\thotkey: hotkeyProp = undefined,\n\t...props\n}) {\n\tconst [isPressed, setIsPressed] = useState(false);\n\tconst kbdRef = useRef(null);\n\n\t// Parsear hotkey\n\tconst hotkey = useMemo(() => {\n\t\tif (!useHotkey) return null;\n\t\tif (hotkeyProp) return normalizeHotkeyString(hotkeyProp);\n\t\tconst text = extractTextFromChildren(props.children);\n\t\treturn text ? normalizeHotkeyString(text) : null;\n\t}, [useHotkey, hotkeyProp, props.children]);\n\n\t// Detectar si es compuesto\n\tconst isCompound = useMemo(() => {\n\t\tif (hotkey?.includes(\"+\")) return true;\n\t\tif (\n\t\t\thotkeyProp &&\n\t\t\ttypeof hotkeyProp === \"string\" &&\n\t\t\thotkeyProp.includes(\"+\")\n\t\t)\n\t\t\treturn true;\n\t\tconst text = extractTextFromChildren(props.children);\n\t\treturn (\n\t\t\ttext?.includes(\"+\") ||\n\t\t\t(Array.isArray(props.children) && props.children.length > 1)\n\t\t);\n\t}, [hotkey, hotkeyProp, props.children]);\n\n\t// Callback para el hotkey\n\tconst handleHotkeyPress = useCallback(\n\t\t(event) => {\n\t\t\tevent?.preventDefault?.();\n\t\t\tonHotkeyPress?.(event);\n\t\t},\n\t\t[onHotkeyPress],\n\t);\n\n\t// Registrar hotkey con react-hotkeys-hook\n\tuseHotkeys(\n\t\thotkey || \"\",\n\t\thandleHotkeyPress,\n\t\t{\n\t\t\tenabled: useHotkey && !!hotkey,\n\t\t\tpreventDefault: false,\n\t\t},\n\t\t[useHotkey, hotkey, handleHotkeyPress],\n\t);\n\n\t// Manejar animación con keydown/keyup\n\tuseEffect(() => {\n\t\tif (!useHotkey || !hotkey || !animate) return;\n\n\t\tconst parts = hotkey.split(\"+\");\n\t\tconst lastPart = parts[parts.length - 1].toLowerCase();\n\t\tconst isOnlyModifier = parts.length === 1 && isModifierKey(lastPart);\n\t\tconst mainKey = isOnlyModifier ? null : lastPart;\n\t\tconst modifierKeys = isOnlyModifier ? parts : parts.slice(0, -1);\n\n\t\tconst handleKeyDown = (event) => {\n\t\t\t// Verificar si la combinación completa está activa en este momento\n\t\t\t// El evento tiene el estado actual de todas las teclas presionadas\n\t\t\t// Esto funciona incluso si se presiona primero el modificador y luego la tecla principal\n\t\t\tif (matchesHotkey(hotkey, event)) {\n\t\t\t\tsetIsPressed(true);\n\t\t\t}\n\t\t};\n\n\t\tconst handleKeyUp = (event) => {\n\t\t\tconst eventKey = event.key?.toLowerCase() || \"\";\n\t\t\tconst eventCode = event.code?.toLowerCase() || \"\";\n\n\t\t\tif (isOnlyModifier) {\n\t\t\t\t// Si es solo un modificador, verificar si se soltó ese modificador\n\t\t\t\tlet isModifierUp = false;\n\t\t\t\tif (lastPart === \"mod\") {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"meta\" ||\n\t\t\t\t\t\teventKey === \"control\" ||\n\t\t\t\t\t\teventCode.includes(\"meta\") ||\n\t\t\t\t\t\teventCode.includes(\"control\") ||\n\t\t\t\t\t\t(!event.metaKey && !event.ctrlKey);\n\t\t\t\t} else if (lastPart === \"ctrl\") {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"control\" ||\n\t\t\t\t\t\teventCode.includes(\"control\") ||\n\t\t\t\t\t\t!event.ctrlKey;\n\t\t\t\t} else if (lastPart === \"alt\") {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"alt\" || eventCode.includes(\"alt\") || !event.altKey;\n\t\t\t\t} else if (lastPart === \"shift\") {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"shift\" ||\n\t\t\t\t\t\teventCode.includes(\"shift\") ||\n\t\t\t\t\t\t!event.shiftKey;\n\t\t\t\t}\n\n\t\t\t\tif (isModifierUp) {\n\t\t\t\t\tsetIsPressed(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Hotkey con tecla principal\n\t\t\t\tconst eventCodeMain = eventCode.replace(\"key\", \"\");\n\n\t\t\t\t// Verificar si se soltó la tecla principal\n\t\t\t\tconst isMainKey = eventKey === mainKey || eventCodeMain === mainKey;\n\n\t\t\t\t// Verificar si se soltó un modificador requerido\n\t\t\t\tlet isModifierUp = false;\n\t\t\t\tif (modifierKeys.includes(\"mod\")) {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"meta\" ||\n\t\t\t\t\t\teventKey === \"control\" ||\n\t\t\t\t\t\teventCode.includes(\"meta\") ||\n\t\t\t\t\t\teventCode.includes(\"control\") ||\n\t\t\t\t\t\t(!event.metaKey && !event.ctrlKey);\n\t\t\t\t}\n\t\t\t\tif (modifierKeys.includes(\"ctrl\") && !isModifierUp) {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"control\" ||\n\t\t\t\t\t\teventCode.includes(\"control\") ||\n\t\t\t\t\t\t!event.ctrlKey;\n\t\t\t\t}\n\t\t\t\tif (modifierKeys.includes(\"alt\") && !isModifierUp) {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"alt\" || eventCode.includes(\"alt\") || !event.altKey;\n\t\t\t\t}\n\t\t\t\tif (modifierKeys.includes(\"shift\") && !isModifierUp) {\n\t\t\t\t\tisModifierUp =\n\t\t\t\t\t\teventKey === \"shift\" ||\n\t\t\t\t\t\teventCode.includes(\"shift\") ||\n\t\t\t\t\t\t!event.shiftKey;\n\t\t\t\t}\n\n\t\t\t\t// Si se soltó la tecla principal O cualquier modificador, desactivar\n\t\t\t\tif (isMainKey || isModifierUp) {\n\t\t\t\t\tsetIsPressed(false);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"keydown\", handleKeyDown, true);\n\t\twindow.addEventListener(\"keyup\", handleKeyUp, true);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", handleKeyDown, true);\n\t\t\twindow.removeEventListener(\"keyup\", handleKeyUp, true);\n\t\t\tsetIsPressed(false);\n\t\t};\n\t}, [useHotkey, hotkey, animate]);\n\n\tif (variant === \"legacy\") {\n\t\treturn (\n\t\t\t<kbd\n\t\t\t\tref={kbdRef}\n\t\t\t\tdata-slot=\"kbd\"\n\t\t\t\tclassName={cn(kbdVariants({ variant }), className)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{props.children}\n\t\t\t</kbd>\n\t\t);\n\t}\n\n\treturn (\n\t\t<kbd\n\t\t\tref={kbdRef}\n\t\t\tdata-slot=\"kbd\"\n\t\t\tclassName={cn(\n\t\t\t\t!isCompound && \"aspect-square\",\n\t\t\t\tkbdVariants({ variant }),\n\t\t\t\tisPressed && \"shadow-none\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<span\n\t\t\t\tclassName={cn(\n\t\t\t\t\tkbdInnerVariants({ variant }),\n\t\t\t\t\tisPressed && \"translate-y-0 shadow-transparent\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<span className=\"block text-center align-center text-xs\">\n\t\t\t\t\t{props.children}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t</kbd>\n\t);\n}\n\nfunction KbdGroup({ className, ...props }) {\n\treturn (\n\t\t<kbd\n\t\t\tdata-slot=\"kbd-group\"\n\t\t\tclassName={cn(\"inline-flex items-center gap-1\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nKbd.displayName = \"Kbd\";\nKbdGroup.displayName = \"KbdGroup\";\n\nexport { Kbd, KbdGroup };\n",
      "type": "registry:component",
      "target": "components/optics/kbd.jsx"
    }
  ],
  "type": "registry:component"
}