{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kbd",
  "title": "Kbd",
  "description": "Keyboard keys that sink and spring back when pressed, one after another for a combination.",
  "dependencies": [],
  "registryDependencies": [
    "@keyframery/motion"
  ],
  "files": [
    {
      "path": "registry/components/keyframery/primitives/kbd.tsx",
      "content": "\"use client\"\n\n/* Animated keyboard keys. UI SDK has no counterpart, so these are plain elements: `Kbd` is a <kbd>,\n   `KbdCombo` a row of them for a shortcut. This file adds motion only. Colours, radii, shadows and\n   spacing stay yours (style `data-pressed` for the pressed look).\n\n   - Press: a key sinks while `data-pressed` is on it (1px down, a touch smaller) and pops back on\n     release with `ease-pop`.\n   - In a combo pressed through `pressed`, the keys sink one after another, left to right.\n   - `listen` follows the real keyboard: each key sinks while you hold it. */\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst isApple = () => typeof navigator !== \"undefined\" && /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent)\n\n/* Names you can pass in `keys`, how they show, and how the keyboard reports them. */\nconst named: Record<string, { label: string | ((apple: boolean) => string); test: (e: KeyboardEvent) => boolean | null }> = {\n  mod: { label: (apple) => (apple ? \"⌘\" : \"Ctrl\"), test: (e) => (isApple() ? e.metaKey : e.ctrlKey) },\n  meta: { label: \"⌘\", test: (e) => e.metaKey },\n  cmd: { label: \"⌘\", test: (e) => e.metaKey },\n  ctrl: { label: (apple) => (apple ? \"⌃\" : \"Ctrl\"), test: (e) => e.ctrlKey },\n  shift: { label: \"⇧\", test: (e) => e.shiftKey },\n  alt: { label: (apple) => (apple ? \"⌥\" : \"Alt\"), test: (e) => e.altKey },\n  option: { label: \"⌥\", test: (e) => e.altKey },\n  enter: { label: \"↵\", test: () => null },\n  escape: { label: \"Esc\", test: () => null },\n  esc: { label: \"Esc\", test: () => null },\n  backspace: { label: \"⌫\", test: () => null },\n  tab: { label: \"⇥\", test: () => null },\n  space: { label: \"Space\", test: () => null },\n  up: { label: \"↑\", test: () => null },\n  down: { label: \"↓\", test: () => null },\n  left: { label: \"←\", test: () => null },\n  right: { label: \"→\", test: () => null },\n}\n\n/* The value of `KeyboardEvent.key` for each non-modifier name. */\nconst eventKey: Record<string, string> = {\n  enter: \"enter\",\n  escape: \"escape\",\n  esc: \"escape\",\n  backspace: \"backspace\",\n  tab: \"tab\",\n  space: \" \",\n  up: \"arrowup\",\n  down: \"arrowdown\",\n  left: \"arrowleft\",\n  right: \"arrowright\",\n}\n\nfunction keyLabel(key: string, apple = isApple()) {\n  const entry = named[key.toLowerCase()]\n  if (!entry) return key.length === 1 ? key.toUpperCase() : key\n  return typeof entry.label === \"function\" ? entry.label(apple) : entry.label\n}\n\n/* Server-rendered pages can't know the platform, so they show the Windows labels and the\n   client switches to ⌘ after hydration instead of rendering a mismatch. */\nconst noop = () => () => {}\nfunction useApple() {\n  return React.useSyncExternalStore(noop, isApple, () => false)\n}\n\n/* Press: sinks fast, pops back with a little overshoot. */\nconst kbdMotion = cn(\n  \"inline-flex origin-bottom\",\n  \"transition-[translate,scale,box-shadow,background-color] duration-[calc(320ms*var(--kf-speed,1))] ease-pop\",\n  \"data-pressed:translate-y-px data-pressed:scale-[0.93] data-pressed:duration-[calc(90ms*var(--kf-speed,1))] data-pressed:ease-house\",\n  \"motion-reduce:transition-none!\",\n)\n\nfunction Kbd({ className, pressed, ...props }: React.ComponentProps<\"kbd\"> & { pressed?: boolean }) {\n  return <kbd data-slot=\"kbd\" data-pressed={pressed || undefined} className={cn(kbdMotion, className)} {...props} />\n}\n\n/* Which of `keys` the keyboard is holding right now. */\nfunction useHeldKeys(keys: string[], listen: boolean) {\n  const [held, setHeld] = React.useState<boolean[]>(() => keys.map(() => false))\n  const signature = keys.join(\"+\").toLowerCase()\n\n  React.useEffect(() => {\n    if (!listen) return\n    const names = signature.split(\"+\")\n    const down = new Set<string>()\n    const update = (e: KeyboardEvent | null) => {\n      setHeld((prev) => {\n        const next = names.map((name) => {\n          const modifier = named[name]?.test\n          const asModifier = e && modifier ? modifier(e) : null\n          if (asModifier !== null && asModifier !== undefined) return asModifier\n          return down.has(eventKey[name] ?? name)\n        })\n        return next.every((v, i) => v === prev[i]) && next.length === prev.length ? prev : next\n      })\n    }\n    const onDown = (e: KeyboardEvent) => {\n      down.add(e.key.toLowerCase())\n      update(e)\n    }\n    const onUp = (e: KeyboardEvent) => {\n      down.delete(e.key.toLowerCase())\n      /* With Command held, browsers send no keyup for the other keys: let go of them with it. */\n      if (e.key === \"Meta\") down.clear()\n      update(e)\n    }\n    const onBlur = () => {\n      down.clear()\n      setHeld(names.map(() => false))\n    }\n    window.addEventListener(\"keydown\", onDown)\n    window.addEventListener(\"keyup\", onUp)\n    window.addEventListener(\"blur\", onBlur)\n    return () => {\n      window.removeEventListener(\"keydown\", onDown)\n      window.removeEventListener(\"keyup\", onUp)\n      window.removeEventListener(\"blur\", onBlur)\n    }\n  }, [listen, signature])\n\n  return held\n}\n\nfunction KbdCombo({\n  keys,\n  pressed = false,\n  listen = false,\n  className,\n  kbdClassName,\n  ...props\n}: Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  /** The keys, in order: names like \"mod\", \"shift\", \"alt\", \"enter\", \"escape\", or a character. */\n  keys: string[]\n  /** Sinks every key, one after another. */\n  pressed?: boolean\n  /** Sinks each key while it is held on the real keyboard. */\n  listen?: boolean\n  /** Class names for each key. */\n  kbdClassName?: string\n}) {\n  const apple = useApple()\n  const held = useHeldKeys(keys, listen)\n  return (\n    <span\n      data-slot=\"kbd-combo\"\n      data-pressed={pressed || undefined}\n      aria-keyshortcuts={keys.join(\"+\")}\n      className={cn(\n        \"inline-flex items-center\",\n        /* In sequence: each key sinks a beat after the one before it; all pop back together. */\n        \"data-pressed:[&>[data-slot=kbd]]:[transition-delay:calc((min(sibling-index(),6)-1)*calc(70ms*var(--kf-speed,1)))]\",\n        className,\n      )}\n      {...props}\n    >\n      {keys.map((key, i) => (\n        <Kbd key={`${key}-${i}`} pressed={pressed || held[i]} className={kbdClassName}>\n          {keyLabel(key, apple)}\n        </Kbd>\n      ))}\n    </span>\n  )\n}\n\nexport { Kbd, KbdCombo, keyLabel }\n",
      "type": "registry:component",
      "target": "components/keyframery/primitives/kbd.tsx"
    },
    {
      "path": "registry/components/keyframery/kbd.tsx",
      "content": "\"use client\"\n\n/* Styled keyboard keys: the animated primitive plus the Keyframery look. A small key with a 1px ledge\n   under it; pressed, it sinks into the ledge and the face takes a soft inner shadow. */\n\nimport * as React from \"react\"\n\nimport { Kbd as KbdPrimitive, KbdCombo as KbdComboPrimitive } from \"@/components/keyframery/primitives/kbd\"\nimport { cn } from \"@/lib/utils\"\n\nconst key = cn(\n  \"h-6 min-w-6 items-center justify-center rounded-[6px] px-1.5 font-sans text-[12px] leading-none font-medium text-muted-foreground tabular-nums select-none\",\n  \"bg-background shadow-[0_0_0_1px_rgb(0_0_0/0.08),0_1px_0_0_rgb(0_0_0/0.1),inset_0_0_0_0_transparent]\",\n  \"dark:bg-accent dark:shadow-[0_0_0_1px_rgb(255_255_255/0.08),0_1px_0_0_rgb(0_0_0/0.6),inset_0_1px_0_0_rgb(255_255_255/0.07)]\",\n  \"data-pressed:bg-muted data-pressed:text-foreground data-pressed:shadow-[0_0_0_1px_rgb(0_0_0/0.1),0_0_0_0_rgb(0_0_0/0.1),inset_0_1px_2px_0_rgb(0_0_0/0.12)]\",\n  \"dark:data-pressed:bg-muted dark:data-pressed:shadow-[0_0_0_1px_rgb(255_255_255/0.06),0_0_0_0_rgb(0_0_0/0.6),inset_0_1px_3px_0_rgb(0_0_0/0.5)]\",\n)\n\nfunction Kbd({ className, ...props }: React.ComponentProps<typeof KbdPrimitive>) {\n  return <KbdPrimitive className={cn(key, className)} {...props} />\n}\n\nfunction KbdCombo({ className, kbdClassName, ...props }: React.ComponentProps<typeof KbdComboPrimitive>) {\n  return <KbdComboPrimitive className={cn(\"gap-1\", className)} kbdClassName={cn(key, kbdClassName)} {...props} />\n}\n\nexport { Kbd, KbdCombo }\nexport { keyLabel } from \"@/components/keyframery/primitives/kbd\"\n",
      "type": "registry:component",
      "target": "components/keyframery/kbd.tsx"
    }
  ],
  "type": "registry:component"
}