import { type JSX, For, Show, splitProps } from 'solid-js'; import { cn } from '../utils/cn'; export type KbdPlatform = 'auto' | 'mac' | 'other'; export interface KbdProps extends JSX.HTMLAttributes { /** Shortcut spec — tokens joined by `+` (e.g. `Mod+Shift+K`). When omitted, * children render verbatim. Display only; `kai-kbd` does not bind keys. */ keys?: string; /** Glyph platform. `mac` uses ⌘/⌥; `other` uses Ctrl. `auto` (default for the * facade) sniffs the OS, SSR-safe (falls back to `other` on the server). */ platform?: KbdPlatform; /** Cap size. Defaults to `md`. */ size?: 'sm' | 'md'; } /** SSR-safe mac check: prefers UA-CH `platform`, falls back to `navigator.platform`. */ function isMacOS(): boolean { if (typeof navigator === 'undefined') return false; const nav = navigator as Navigator & { userAgentData?: { platform?: string } }; const platform = nav.userAgentData?.platform ?? nav.platform ?? ''; return /mac/i.test(platform); } /** Resolve `auto` to a concrete platform; `mac`/`other` pass through. */ function resolvePlatform(platform: KbdPlatform): 'mac' | 'other' { if (platform === 'mac') return 'mac'; if (platform === 'other') return 'other'; return isMacOS() ? 'mac' : 'other'; } /** Map a single token to its display glyph. Unknown tokens uppercase (so `k` → `K`). */ function glyph(token: string, mac: boolean): string { const t = token.trim(); switch (t.toLowerCase()) { case 'mod': return mac ? '⌘' : 'Ctrl'; case 'cmd': case 'command': case 'meta': return '⌘'; case 'ctrl': case 'control': return 'Ctrl'; case 'shift': return '⇧'; case 'alt': case 'opt': case 'option': return '⌥'; case 'arrowup': return '↑'; case 'arrowdown': return '↓'; case 'arrowleft': return '←'; case 'arrowright': return '→'; case 'enter': case 'return': return '⏎'; case 'esc': case 'escape': return 'Esc'; case 'space': return 'Space'; default: return t.toUpperCase(); } } const SIZE: Record<'sm' | 'md', string> = { sm: 'h-5 min-w-5 px-1 text-[11px]', md: 'h-6 min-w-6 px-1.5 text-xs', }; /** * `Kbd`: a keyboard-shortcut display. Splits `keys` on `+`, maps each token to a * platform glyph (`Mod` → ⌘ on mac else Ctrl, `Shift` → ⇧, `ArrowUp` → ↑, …) and * renders each as an inset `part="key"` cap, with an empty `part="separator"` hook * between caps. Token-driven, light + dark. Display only — it does not bind keys. * * When `keys` is omitted, the children render verbatim (use any custom content). */ export function Kbd(props: KbdProps): JSX.Element { const [local, rest] = splitProps(props, ['keys', 'platform', 'size', 'class', 'children']); const mac = () => resolvePlatform(local.platform ?? 'other') === 'mac'; const tokens = () => (local.keys ?? '') .split('+') .map((t) => t.trim()) .filter(Boolean); return ( {(token, i) => ( <> 0}> {glyph(token, mac())} )} ); }