{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shortcut-recorder",
  "title": "Shortcut Recorder",
  "description": "Records a key combination and renders it with platform symbols.",
  "registryDependencies": [
    "utils",
    "@drift-ui/use-controllable-state"
  ],
  "files": [
    {
      "path": "registry/default/ui/shortcut-recorder.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/registry/default/lib/utils\"\nimport {\n  isFunctionKey,\n  isModifierKey,\n  modifiersFromEvent,\n  shortcutFromEvent,\n  shortcutKeyLabel,\n  splitShortcut,\n  type ShortcutModifier,\n} from \"@/registry/default/lib/shortcut\"\nimport { useControllableState } from \"@/registry/default/hooks/use-controllable-state\"\n\nfunction subscribeToNothing() {\n  return () => {}\n}\n\nfunction readIsMac() {\n  return /Mac|iPhone|iPad|iPod/.test(navigator.userAgent)\n}\n\nfunction useIsMac() {\n  return React.useSyncExternalStore(subscribeToNothing, readIsMac, () => false)\n}\n\nfunction ShortcutKeys({\n  shortcut,\n  className,\n  ...props\n}: React.ComponentProps<\"span\"> & { shortcut: string }) {\n  const isMac = useIsMac()\n\n  return (\n    <span\n      data-slot=\"shortcut-keys\"\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      {...props}\n    >\n      {splitShortcut(shortcut).map((part, index) => (\n        <kbd\n          key={`${part}-${index}`}\n          data-slot=\"shortcut-key\"\n          className=\"inline-flex h-5 min-w-5 items-center justify-center rounded border bg-muted px-1 font-sans text-[11px] font-medium text-muted-foreground\"\n        >\n          {shortcutKeyLabel(part, isMac)}\n        </kbd>\n      ))}\n    </span>\n  )\n}\n\nfunction ShortcutRecorder({\n  value,\n  defaultValue = \"\",\n  onValueChange,\n  placeholder = \"Record shortcut\",\n  requireModifier = true,\n  className,\n  ...props\n}: Omit<\n  React.ComponentProps<\"button\">,\n  \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n  value?: string\n  defaultValue?: string\n  onValueChange?: (shortcut: string) => void\n  placeholder?: string\n  requireModifier?: boolean\n}) {\n  const isMac = useIsMac()\n  const { value: shortcut, setValue: setShortcut } = useControllableState({\n    value,\n    defaultValue,\n    onChange: onValueChange,\n  })\n  const [isRecording, setIsRecording] = React.useState(false)\n  const [heldModifiers, setHeldModifiers] = React.useState<ShortcutModifier[]>(\n    []\n  )\n\n  function stopRecording() {\n    setIsRecording(false)\n    setHeldModifiers([])\n  }\n\n  function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {\n    if (!isRecording) return\n    const modifiers = modifiersFromEvent(event, isMac)\n    const hasModifier = modifiers.length > 0\n    const isLeavingWithTab =\n      event.key === \"Tab\" && modifiers.every((modifier) => modifier === \"Shift\")\n    if (isLeavingWithTab) return stopRecording()\n\n    event.preventDefault()\n    event.stopPropagation()\n    if (isModifierKey(event.key)) return setHeldModifiers(modifiers)\n    if (!hasModifier && event.key === \"Escape\") return stopRecording()\n    if (!hasModifier && [\"Backspace\", \"Delete\"].includes(event.key)) {\n      setShortcut(\"\")\n      return stopRecording()\n    }\n    if (requireModifier && !hasModifier && !isFunctionKey(event.key)) return\n\n    setShortcut(shortcutFromEvent(event, isMac) ?? \"\")\n    stopRecording()\n  }\n\n  function handleKeyUp(event: React.KeyboardEvent<HTMLButtonElement>) {\n    if (!isRecording) return\n    setHeldModifiers(modifiersFromEvent(event, isMac))\n  }\n\n  return (\n    <button\n      type=\"button\"\n      {...props}\n      data-slot=\"shortcut-recorder\"\n      data-recording={isRecording || undefined}\n      aria-pressed={isRecording}\n      onClick={() => (isRecording ? stopRecording() : setIsRecording(true))}\n      onKeyDown={handleKeyDown}\n      onKeyUp={handleKeyUp}\n      onBlur={stopRecording}\n      className={cn(\n        \"inline-flex h-9 min-w-40 items-center justify-between gap-3 rounded-md border border-input bg-transparent px-3 text-sm shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30\",\n        \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        \"data-recording:border-ring data-recording:ring-[3px] data-recording:ring-ring/50\",\n        \"disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n    >\n      <ShortcutRecorderDisplay\n        isRecording={isRecording}\n        heldModifiers={heldModifiers}\n        shortcut={shortcut}\n        placeholder={placeholder}\n      />\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {isRecording &&\n          \"Recording. Press a key combination, Escape to cancel, Backspace to clear.\"}\n      </span>\n    </button>\n  )\n}\n\nfunction ShortcutRecorderDisplay({\n  isRecording,\n  heldModifiers,\n  shortcut,\n  placeholder,\n}: {\n  isRecording: boolean\n  heldModifiers: ShortcutModifier[]\n  shortcut: string\n  placeholder: string\n}) {\n  if (isRecording && heldModifiers.length > 0)\n    return <ShortcutKeys shortcut={heldModifiers.join(\"+\")} />\n  if (isRecording)\n    return (\n      <span className=\"flex items-center gap-2 text-muted-foreground\">\n        <span className=\"size-1.5 animate-pulse rounded-full bg-destructive\" />\n        Press keys\n      </span>\n    )\n  if (shortcut) return <ShortcutKeys shortcut={shortcut} />\n  return <span className=\"text-muted-foreground\">{placeholder}</span>\n}\n\nexport { ShortcutKeys, ShortcutRecorder }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/default/lib/shortcut.ts",
      "content": "export type ShortcutModifier = \"Ctrl\" | \"Alt\" | \"Shift\" | \"Mod\" | \"Meta\"\n\ntype KeyboardEventLike = Pick<\n  KeyboardEvent,\n  \"key\" | \"code\" | \"ctrlKey\" | \"altKey\" | \"shiftKey\" | \"metaKey\"\n>\n\nconst MODIFIER_KEYS = new Set([\n  \"Control\",\n  \"Alt\",\n  \"AltGraph\",\n  \"Shift\",\n  \"Meta\",\n  \"OS\",\n  \"CapsLock\",\n  \"Fn\",\n])\n\nconst NAMED_KEYS: Record<string, string> = {\n  \" \": \"Space\",\n  \"+\": \"Plus\",\n  Esc: \"Escape\",\n}\n\nconst MAC_LABELS: Record<string, string> = {\n  Ctrl: \"⌃\",\n  Alt: \"⌥\",\n  Shift: \"⇧\",\n  Mod: \"⌘\",\n  Meta: \"⌘\",\n  Enter: \"↩\",\n  Backspace: \"⌫\",\n  Delete: \"⌦\",\n  Escape: \"⎋\",\n  Tab: \"⇥\",\n  ArrowUp: \"↑\",\n  ArrowDown: \"↓\",\n  ArrowLeft: \"←\",\n  ArrowRight: \"→\",\n}\n\nconst PC_LABELS: Record<string, string> = {\n  Mod: \"Ctrl\",\n  Meta: \"Win\",\n  ArrowUp: \"↑\",\n  ArrowDown: \"↓\",\n  ArrowLeft: \"←\",\n  ArrowRight: \"→\",\n}\n\nexport function isModifierKey(key: string) {\n  return MODIFIER_KEYS.has(key)\n}\n\nexport function isFunctionKey(key: string) {\n  return /^F\\d{1,2}$/.test(key)\n}\n\nexport function modifiersFromEvent(event: KeyboardEventLike, isMac: boolean) {\n  const modifiers: ShortcutModifier[] = []\n  if (event.ctrlKey) modifiers.push(isMac ? \"Ctrl\" : \"Mod\")\n  if (event.altKey) modifiers.push(\"Alt\")\n  if (event.shiftKey) modifiers.push(\"Shift\")\n  if (event.metaKey) modifiers.push(isMac ? \"Mod\" : \"Meta\")\n  return modifiers\n}\n\nexport function keyFromEvent({ key, code }: KeyboardEventLike) {\n  if (code.startsWith(\"Key\")) return code.slice(3)\n  if (code.startsWith(\"Digit\")) return code.slice(5)\n  if (key.length === 1) return NAMED_KEYS[key] ?? key.toUpperCase()\n  return NAMED_KEYS[key] ?? key\n}\n\nexport function shortcutFromEvent(event: KeyboardEventLike, isMac: boolean) {\n  if (isModifierKey(event.key)) return null\n  return [...modifiersFromEvent(event, isMac), keyFromEvent(event)].join(\"+\")\n}\n\nexport function matchesShortcut(\n  event: KeyboardEventLike,\n  shortcut: string,\n  isMac: boolean\n) {\n  return shortcutFromEvent(event, isMac) === shortcut\n}\n\nexport function splitShortcut(shortcut: string) {\n  return shortcut ? shortcut.split(\"+\") : []\n}\n\nexport function shortcutKeyLabel(part: string, isMac: boolean) {\n  const labels = isMac ? MAC_LABELS : PC_LABELS\n  return labels[part] ?? part\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}