{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"kbd","title":"Kbd","type":"registry:ui","description":"A Kbd component for keyboard key display, it auto-detects the command key (⌘ on Mac, Ctrl on Windows, etc.) and displays the appropriate symbol.","dependencies":["class-variance-authority"],"registryDependencies":["utils"],"files":[{"path":"fysk/kbd.tsx","type":"registry:ui","content":"\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst kbdVariants = cva(\r\n    \"inline-flex items-center justify-center gap-1 font-mono font-medium select-none transition-all duration-150\",\r\n    {\r\n        variants: {\r\n            variant: {\r\n                default: \"bg-muted border border-border text-muted-foreground shadow-sm\",\r\n                outline: \"bg-transparent border-2 border-border text-foreground hover:border-primary/50\",\r\n                solid: \"bg-foreground text-background border border-foreground shadow-md\",\r\n                ghost: \"bg-transparent text-muted-foreground\",\r\n                glass: \"bg-background/20 backdrop-blur-sm border border-border/50 text-foreground shadow-lg\",\r\n                mac: \"bg-gradient-to-b from-muted to-muted/80 border border-border text-foreground shadow-md hover:shadow-lg\",\r\n                modern: \"bg-gradient-to-br from-primary/10 to-secondary/10 border border-primary/20 text-primary shadow backdrop-blur-sm\",\r\n            },\r\n            size: {\r\n                xs: \"h-4 px-1 text-[9px] rounded-[2px] gap-0.5\",\r\n                sm: \"h-5 px-1.5 text-[10px] rounded-sm gap-0.5\",\r\n                md: \"h-6 px-2 text-xs rounded-md gap-1\",\r\n                lg: \"h-8 px-3 text-sm rounded-lg gap-1.5\",\r\n                xl: \"h-10 px-4 text-base rounded-xl gap-2\",\r\n            },\r\n            state: {\r\n                idle: \"\",\r\n                active: \"ring-2 ring-primary ring-offset-2 ring-offset-background scale-105\",\r\n                disabled: \"opacity-50 cursor-not-allowed\",\r\n            },\r\n        },\r\n        defaultVariants: {\r\n            variant: \"default\",\r\n            size: \"md\",\r\n            state: \"idle\",\r\n        },\r\n    }\r\n)\r\n\r\n// Platform detection helper\r\nconst getPlatform = (): \"mac\" | \"windows\" | \"linux\" => {\r\n    if (typeof window === \"undefined\") return \"windows\"\r\n    const userAgent = window.navigator.userAgent.toLowerCase()\r\n    if (userAgent.includes(\"mac\")) return \"mac\"\r\n    if (userAgent.includes(\"linux\")) return \"linux\"\r\n    return \"windows\"\r\n}\r\n\r\n// Special key symbols mapping\r\nconst keySymbols: Record<string, { mac: string; windows: string; icon?: string }> = {\r\n    mod: { mac: \"⌘\", windows: \"Ctrl\" },\r\n    cmd: { mac: \"⌘\", windows: \"Ctrl\" },\r\n    command: { mac: \"⌘\", windows: \"Ctrl\" },\r\n    ctrl: { mac: \"⌃\", windows: \"Ctrl\" },\r\n    control: { mac: \"⌃\", windows: \"Ctrl\" },\r\n    shift: { mac: \"⇧\", windows: \"Shift\" },\r\n    alt: { mac: \"⌥\", windows: \"Alt\" },\r\n    option: { mac: \"⌥\", windows: \"Alt\" },\r\n    enter: { mac: \"↵\", windows: \"Enter\" },\r\n    return: { mac: \"↵\", windows: \"Enter\" },\r\n    delete: { mac: \"⌫\", windows: \"Del\" },\r\n    backspace: { mac: \"⌫\", windows: \"Backspace\" },\r\n    escape: { mac: \"⎋\", windows: \"Esc\" },\r\n    esc: { mac: \"⎋\", windows: \"Esc\" },\r\n    tab: { mac: \"⇥\", windows: \"Tab\" },\r\n    capslock: { mac: \"⇪\", windows: \"Caps\" },\r\n    up: { mac: \"↑\", windows: \"↑\" },\r\n    down: { mac: \"↓\", windows: \"↓\" },\r\n    left: { mac: \"←\", windows: \"←\" },\r\n    right: { mac: \"→\", windows: \"→\" },\r\n    space: { mac: \"␣\", windows: \"Space\" },\r\n}\r\n\r\nexport interface KbdProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof kbdVariants> {\r\n    /**\r\n     * Array of keys to display (e.g., ['Cmd', 'Shift', 'P'])\r\n     */\r\n    keys?: string[]\r\n    /**\r\n     * Separator between keys (default: '+')\r\n     */\r\n    separator?: string\r\n    /**\r\n     * Platform to use for key display ('mac' | 'windows' | 'linux' | 'auto')\r\n     * 'auto' will detect the user's platform\r\n     */\r\n    platform?: \"mac\" | \"windows\" | \"linux\" | \"auto\"\r\n    /**\r\n     * Icon to display alongside the key\r\n     */\r\n    icon?: React.ReactNode\r\n    /**\r\n     * Position of the icon\r\n     */\r\n    iconPosition?: \"start\" | \"end\"\r\n    /**\r\n     * Whether the key is currently active/pressed\r\n     */\r\n    active?: boolean\r\n    /**\r\n     * Whether the key is disabled\r\n     */\r\n    disabled?: boolean\r\n    /**\r\n     * Enable click to copy functionality\r\n     */\r\n    copyable?: boolean\r\n    /**\r\n     * Callback when the kbd text is copied to clipboard\r\n     */\r\n    onCopyText?: (text: string) => void\r\n}\r\n\r\nconst Kbd = React.forwardRef<HTMLElement, KbdProps>(\r\n    (\r\n        {\r\n            className,\r\n            variant,\r\n            size,\r\n            state,\r\n            keys,\r\n            separator = \"+\",\r\n            platform = \"auto\",\r\n            icon,\r\n            iconPosition = \"start\",\r\n            active,\r\n            disabled,\r\n            copyable,\r\n            onCopyText,\r\n            children,\r\n            onClick,\r\n            ...props\r\n        },\r\n        ref\r\n    ) => {\r\n        const detectedPlatform = platform === \"auto\" ? getPlatform() : platform\r\n        const [copied, setCopied] = React.useState(false)\r\n        const [isPressed, setIsPressed] = React.useState(false)\r\n\r\n        // Determine the actual state\r\n        const actualState = disabled ? \"disabled\" : active || isPressed ? \"active\" : state || \"idle\"\r\n\r\n        // Format keys with platform-specific symbols\r\n        const formatKey = (key: string): string => {\r\n            const lowerKey = key.toLowerCase()\r\n            const symbol = keySymbols[lowerKey]\r\n            if (symbol) {\r\n                return detectedPlatform === \"mac\" ? symbol.mac : symbol.windows\r\n            }\r\n            return key\r\n        }\r\n\r\n        // Build the display content\r\n        const displayContent = React.useMemo(() => {\r\n            if (keys && keys.length > 0) {\r\n                return keys.map((key, index) => (\r\n                    <React.Fragment key={index}>\r\n                        {index > 0 && (\r\n                            <span className=\"opacity-50 text-[0.7em] px-0.5\">{separator}</span>\r\n                        )}\r\n                        <span>{formatKey(key)}</span>\r\n                    </React.Fragment>\r\n                ))\r\n            }\r\n            return children\r\n        }, [keys, children, separator, detectedPlatform])\r\n\r\n        // Get text for copying\r\n        const getCopyText = (): string => {\r\n            if (keys && keys.length > 0) {\r\n                return keys.map(formatKey).join(separator)\r\n            }\r\n            if (typeof children === \"string\") {\r\n                return children\r\n            }\r\n            return \"\"\r\n        }\r\n\r\n        // Handle copy functionality\r\n        const handleClick = async (e: React.MouseEvent<HTMLElement>) => {\r\n            if (copyable && !disabled) {\r\n                const text = getCopyText()\r\n                try {\r\n                    await navigator.clipboard.writeText(text)\r\n                    setCopied(true)\r\n                    setIsPressed(true)\r\n                    onCopyText?.(text)\r\n                    setTimeout(() => {\r\n                        setCopied(false)\r\n                        setIsPressed(false)\r\n                    }, 1000)\r\n                } catch (err) {\r\n                    console.error(\"Failed to copy:\", err)\r\n                }\r\n            }\r\n            onClick?.(e)\r\n        }\r\n\r\n        return (\r\n            <kbd\r\n                ref={ref}\r\n                className={cn(\r\n                    kbdVariants({ variant, size, state: actualState }),\r\n                    copyable && !disabled && \"cursor-pointer hover:scale-105 active:scale-95\",\r\n                    copied && \"ring-2 ring-green-500 ring-offset-2\",\r\n                    className\r\n                )}\r\n                onClick={handleClick}\r\n                aria-label={props[\"aria-label\"] || getCopyText()}\r\n                {...props}\r\n            >\r\n                {icon && iconPosition === \"start\" && (\r\n                    <span className=\"inline-flex items-center\">{icon}</span>\r\n                )}\r\n                {displayContent}\r\n                {icon && iconPosition === \"end\" && (\r\n                    <span className=\"inline-flex items-center\">{icon}</span>\r\n                )}\r\n            </kbd>\r\n        )\r\n    }\r\n)\r\nKbd.displayName = \"Kbd\"\r\n\r\n// KbdGroup component for displaying multiple related shortcuts\r\nexport interface KbdGroupProps extends React.HTMLAttributes<HTMLDivElement> {\r\n    /**\r\n     * Separator between Kbd elements\r\n     */\r\n    separator?: React.ReactNode\r\n}\r\n\r\nconst KbdGroup = React.forwardRef<HTMLDivElement, KbdGroupProps>(\r\n    ({ className, separator, children, ...props }, ref) => {\r\n        const childArray = React.Children.toArray(children)\r\n\r\n        return (\r\n            <div\r\n                ref={ref}\r\n                className={cn(\"inline-flex items-center gap-1\", className)}\r\n                {...props}\r\n            >\r\n                {childArray.map((child, index) => (\r\n                    <React.Fragment key={index}>\r\n                        {index > 0 && (\r\n                            <span className=\"text-muted-foreground text-xs opacity-70\">\r\n                                {separator || \"+\"}\r\n                            </span>\r\n                        )}\r\n                        {child}\r\n                    </React.Fragment>\r\n                ))}\r\n            </div>\r\n        )\r\n    }\r\n)\r\nKbdGroup.displayName = \"KbdGroup\"\r\n\r\nexport { Kbd, KbdGroup, kbdVariants }\r\n\r\n"}]}