Mac Keyboard
componentry/mac-keyboardFreeComponent
Component for mac-keyboard
Live preview
live · rendered by the registry
Rendered by componentry on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://componentry.dev/r/mac-keyboard.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import {5 ArrowLeft,6 ArrowRight,7 ArrowDown,8 ArrowUp,9 ChevronUp,10 Command,11 Globe,12 LayoutGrid,13 Lock,14 Mic,15 Moon,16 Option,17 Play,18 Search,19 SkipBack,20 SkipForward,21 Sun,22 Volume1,23 Volume2,24 VolumeX,25} from "lucide-react";26import * as React from "react";2728// Context to share active keys state29const KeyboardContext = React.createContext<{30 activeKeys: Set<string>;31}>({32 activeKeys: new Set(),33});3435export interface MacKeyProps extends React.HTMLAttributes<HTMLDivElement> {36 label?: React.ReactNode;37 subLabel?: React.ReactNode;38 icon?: React.ReactNode;39 iconLabel?: string;40 width?: number;41 keyCode?: string | string[]; // Can be a single code or array of codes42 noAspectRatio?: boolean; // Skip aspect-ratio on wrapper (for arrow half-height keys)43}4445interface MacKeyboardProps extends React.HTMLAttributes<HTMLDivElement> {46 // Optional prop to provide a custom sound URL47 soundSrc?: string;48}4950export function MacKeyboard({ className, soundSrc = "/audio/key-press.wav", ...props }: MacKeyboardProps) {51 const [activeKeys, setActiveKeys] = React.useState<Set<string>>(new Set());52 const audioCtxRef = React.useRef<AudioContext | null>(null);53 const audioBufferRef = React.useRef<AudioBuffer | null>(null);5455 React.useEffect(() => {56 if (!soundSrc) return;57 // Use Web Audio API for low-latency, short key click sounds58 const ctx = new (window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)();59 audioCtxRef.current = ctx;6061 fetch(soundSrc)62 .then((res) => res.arrayBuffer())63 .then((buf) => ctx.decodeAudioData(buf))64 .then((decoded) => { audioBufferRef.current = decoded; })65 .catch(() => {}); // Silently fail if audio can't load6667 return () => { ctx.close().catch(() => {}); };68 }, [soundSrc]);6970 const playClick = React.useCallback(() => {71 const ctx = audioCtxRef.current;72 const buffer = audioBufferRef.current;73 if (!ctx || !buffer) return;7475 const play = () => {76 const source = ctx.createBufferSource();77 source.buffer = buffer;7879 const gain = ctx.createGain();80 gain.gain.value = 0.15; // Set volume very low (15%) so it's a subtle click8182 source.connect(gain);83 gain.connect(ctx.destination);84 source.start(0);85 };8687 if (ctx.state === "suspended") {88 ctx.resume().then(play).catch(() => {});89 } else {90 play();91 }92 }, []);9394// … truncated
More from componentry
All 59 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Gradientanimated-gradient | componentry | Components | Free | no deps | |
| ASCII Effectascii-effect | componentry | Components | Free | no deps | |
| Aurora Flowaurora-flow | componentry | Components | Free | no deps | |
| auth-modalauth-modal | componentry | Components | Free | no deps | |
| border-beamborder-beam | componentry | Components | Free | no deps | |
| circuit-boardcircuit-board | componentry | Components | Free | no deps | |
| closing-plasmaclosing-plasma | componentry | Components | Free | no deps | |
| collection-surfercollection-surfer | componentry | Components | Free | no deps |
