{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "voice-button", "registryDependencies": [ "button", "https://ui.elevenlabs.io/r/live-waveform.json" ], "files": [ { "path": "components/ui/voice-button.tsx", "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { LiveWaveform } from \"@/components/ui/live-waveform\"\n\nexport type VoiceButtonState =\n | \"idle\"\n | \"recording\"\n | \"processing\"\n | \"success\"\n | \"error\"\n\nexport interface VoiceButtonProps\n extends Omit, \"onError\"> {\n /**\n * Current state of the voice button\n * @default \"idle\"\n */\n state?: VoiceButtonState\n\n /**\n * Callback when button is clicked\n */\n onPress?: () => void\n\n /**\n * Content to display on the left side (label)\n * Can be a string or ReactNode for custom components\n */\n label?: React.ReactNode\n\n /**\n * Content to display on the right side (e.g., keyboard shortcut)\n * Can be a string or ReactNode for custom components\n * @example \"⌥Space\" or ⌘K\n */\n trailing?: React.ReactNode\n\n /**\n * Icon to display in the center when idle (for icon size buttons)\n */\n icon?: React.ReactNode\n\n /**\n * Custom variant for the button\n * @default \"outline\"\n */\n variant?:\n | \"default\"\n | \"destructive\"\n | \"outline\"\n | \"secondary\"\n | \"ghost\"\n | \"link\"\n\n /**\n * Size of the button\n * @default \"default\"\n */\n size?: \"default\" | \"sm\" | \"lg\" | \"icon\"\n\n /**\n * Custom className for the button\n */\n className?: string\n\n /**\n * Custom className for the waveform container\n */\n waveformClassName?: string\n\n /**\n * Duration in ms to show success/error states\n * @default 1500\n */\n feedbackDuration?: number\n\n /**\n * Disable the button\n */\n disabled?: boolean\n}\n\nexport const VoiceButton = React.forwardRef<\n HTMLButtonElement,\n VoiceButtonProps\n>(\n (\n {\n state = \"idle\",\n onPress,\n label,\n trailing,\n icon,\n variant = \"outline\",\n size = \"default\",\n className,\n waveformClassName,\n feedbackDuration = 1500,\n disabled,\n onClick,\n ...props\n },\n ref\n ) => {\n const [showFeedback, setShowFeedback] = React.useState(false)\n\n React.useEffect(() => {\n if (state === \"success\" || state === \"error\") {\n setShowFeedback(true)\n const timeout = setTimeout(\n () => setShowFeedback(false),\n feedbackDuration\n )\n return () => clearTimeout(timeout)\n } else {\n // Reset feedback when state changes away from success/error\n setShowFeedback(false)\n }\n }, [state, feedbackDuration])\n\n const handleClick = (e: React.MouseEvent) => {\n onClick?.(e)\n onPress?.()\n }\n\n const isRecording = state === \"recording\"\n const isProcessing = state === \"processing\"\n const isSuccess = state === \"success\"\n const isError = state === \"error\"\n\n const buttonVariant = variant\n const isDisabled = disabled || isProcessing\n\n const displayLabel = label\n\n const shouldShowWaveform = isRecording || isProcessing || showFeedback\n const shouldShowTrailing = !shouldShowWaveform && trailing\n\n return (\n \n {size !== \"icon\" && displayLabel && (\n \n {displayLabel}\n \n )}\n\n \n {shouldShowWaveform && (\n \n )}\n\n {shouldShowTrailing && (\n
\n {typeof trailing === \"string\" ? (\n \n {trailing}\n \n ) : (\n trailing\n )}\n
\n )}\n\n {!shouldShowWaveform &&\n !shouldShowTrailing &&\n icon &&\n size === \"icon\" && (\n
\n {icon}\n
\n )}\n\n {isSuccess && showFeedback && (\n
\n \n \n \n
\n )}\n\n {/* Error Icon */}\n {isError && showFeedback && (\n
\n \n \n \n
\n )}\n \n \n )\n }\n)\n\nVoiceButton.displayName = \"VoiceButton\"\n", "type": "registry:ui" } ], "type": "registry:ui" }