KBD
optics/kbdFreeComponent
Keyboard key component for displaying keyboard shortcuts.
See it running
Open the demo on optics
optics.agusmayol.com.ar/components/kbdInstall it
npx shadcn@latest add https://optics.agusmayol.com.ar/r/kbd.jsonSource
1"use client";23import { cn } from "@/registry/optics/lib/utils";4import { cva } from "class-variance-authority";5import { useHotkeys } from "react-hotkeys-hook";6import {7 useCallback,8 useRef,9 useState,10 useMemo,11 isValidElement,12 useEffect,13} from "react";1415/**16 * Extrae texto de children de React17 */18function extractTextFromChildren(children) {19 if (!children) return "";20 if (typeof children === "string") return children;21 if (typeof children === "number") return String(children);22 if (Array.isArray(children)) {23 return children24 .map((child) => extractTextFromChildren(child))25 .filter(Boolean)26 .join("");27 }28 if (isValidElement(children)) {29 return extractTextFromChildren(children.props.children);30 }31 if (typeof children === "object" && children?.props?.children) {32 return extractTextFromChildren(children.props.children);33 }34 return "";35}3637/**38 * Normaliza un string de hotkey al formato que react-hotkeys-hook espera39 */40function normalizeHotkeyString(str) {41 if (!str) return null;42 let normalized = str.trim().toLowerCase();43 normalized = normalized44 .replace(/⌘|cmd|command|meta/gi, "mod")45 .replace(/ctrl|control/gi, "ctrl")46 .replace(/alt|option/gi, "alt")47 .replace(/shift/gi, "shift")48 .replace(/[\s,\-]+/g, "+")49 .replace(/\++/g, "+")50 .replace(/^\+|\+$/g, "");51 return normalized || null;52}5354/**55 * Lista de modificadores conocidos56 */57const MODIFIER_KEYS = new Set([58 "mod",59 "ctrl",60 "alt",61 "shift",62 "meta",63 "cmd",64 "command",65]);6667/**68 * Verifica si una tecla es un modificador69 */70function isModifierKey(key) {71 return MODIFIER_KEYS.has(key.toLowerCase());72}7374const kbdVariants = cva(75 "select-none outline-hidden transition-all duration-150",76 {77 variants: {78 variant: {79 default:80 "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)]",81 legacy:82 "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",83 },84 },85 defaultVariants: {86 variant: "default",87 },88 },89);9091// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from optics
All 79 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | optics | Components | Free | 5 deps | |
| Alertalert | optics | Components | Free | 4 deps | |
| Alert Dialogalert-dialog | optics | Components | Free | 4 deps | |
| Aspect Ratioaspect-ratio | optics | Components | Free | 2 deps | |
| Auto Heightauto-height | optics | Components | Free | 3 deps | |
| Avataravatar | optics | Components | Free | 3 deps | |
| Badgebadge | optics | Components | Free | 4 deps | |
| Breadcrumbbreadcrumb | optics | Components | Free | 4 deps |
