{
  "$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<React.ButtonHTMLAttributes<HTMLButtonElement>, \"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 <kbd>⌘K</kbd>\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<HTMLButtonElement>) => {\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      <Button\n        ref={ref}\n        type=\"button\"\n        variant={buttonVariant}\n        size={size}\n        onClick={handleClick}\n        disabled={isDisabled}\n        className={cn(\n          \"gap-2 transition-all duration-200\",\n          size === \"icon\" && \"relative\",\n          className\n        )}\n        aria-label={\"Voice Button\"}\n        {...props}\n      >\n        {size !== \"icon\" && displayLabel && (\n          <span className=\"inline-flex shrink-0 items-center justify-start\">\n            {displayLabel}\n          </span>\n        )}\n\n        <div\n          className={cn(\n            \"relative box-content flex shrink-0 items-center justify-center overflow-hidden transition-all duration-300\",\n            size === \"icon\"\n              ? \"absolute inset-0 rounded-sm border-0\"\n              : \"h-5 w-24 rounded-sm border\",\n            isRecording\n              ? \"bg-primary/10 dark:bg-primary/5\"\n              : size === \"icon\"\n                ? \"bg-muted/50 border-0\"\n                : \"border-border bg-muted/50\",\n            waveformClassName\n          )}\n        >\n          {shouldShowWaveform && (\n            <LiveWaveform\n              active={isRecording}\n              processing={isProcessing || isSuccess}\n              barWidth={2}\n              barGap={1}\n              barRadius={4}\n              fadeEdges={false}\n              sensitivity={1.8}\n              smoothingTimeConstant={0.85}\n              height={20}\n              mode=\"static\"\n              className=\"animate-in fade-in absolute inset-0 h-full w-full duration-300\"\n            />\n          )}\n\n          {shouldShowTrailing && (\n            <div className=\"animate-in fade-in absolute inset-0 flex items-center justify-center duration-300\">\n              {typeof trailing === \"string\" ? (\n                <span className=\"text-muted-foreground px-1.5 font-mono text-[10px] font-medium select-none\">\n                  {trailing}\n                </span>\n              ) : (\n                trailing\n              )}\n            </div>\n          )}\n\n          {!shouldShowWaveform &&\n            !shouldShowTrailing &&\n            icon &&\n            size === \"icon\" && (\n              <div className=\"animate-in fade-in absolute inset-0 flex items-center justify-center duration-300\">\n                {icon}\n              </div>\n            )}\n\n          {isSuccess && showFeedback && (\n            <div className=\"animate-in fade-in bg-background/80 absolute inset-0 flex items-center justify-center duration-300\">\n              <span className=\"text-primary text-[10px] font-medium\">\n                <CheckIcon className=\"size-3.5\" />\n              </span>\n            </div>\n          )}\n\n          {/* Error Icon */}\n          {isError && showFeedback && (\n            <div className=\"animate-in fade-in bg-background/80 absolute inset-0 flex items-center justify-center duration-300\">\n              <span className=\"text-destructive text-[10px] font-medium\">\n                <XIcon className=\"size-3.5\" />\n              </span>\n            </div>\n          )}\n        </div>\n      </Button>\n    )\n  }\n)\n\nVoiceButton.displayName = \"VoiceButton\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}