{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"voice-input","type":"registry:ui","title":"Voice Input","description":"A microphone button that expands into a recording panel — live waveform, a running transcript, and cancel or send.","author":"Scrim UI (https://scrimui.dev)","categories":["voice"],"docs":"https://scrimui.dev/components/voice-input","dependencies":[],"registryDependencies":[],"files":[{"path":"src/showcase/voice-input/voice-input.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\n\n/* ------------------------------------------------------------------ */\n/* Types                                                               */\n/* ------------------------------------------------------------------ */\n\nexport type VoiceInputState = \"idle\" | \"recording\";\n\nexport type VoiceInputProps = {\n  state?: VoiceInputState;\n  onStart?: () => void;\n  onStop?: () => void;\n  onCancel?: () => void;\n  recordingTime?: string;\n  transcript?: string;\n  className?: string;\n};\n\n/* ------------------------------------------------------------------ */\n/* Icons                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction MicIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      width=\"16\"\n      height=\"16\"\n      {...props}\n    >\n      <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z\" />\n      <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n      <path d=\"M12 19v3\" />\n    </svg>\n  );\n}\n\nfunction StopIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"11\" height=\"11\" {...props}>\n      <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      width=\"12\"\n      height=\"12\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18M6 6l12 12\" />\n    </svg>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/* Bars                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction Bars({ active }: { active: boolean }) {\n  return (\n    <>\n      <style>{`@keyframes aiui-bar{0%,100%{transform:scaleY(.35)}50%{transform:scaleY(1)}}`}</style>\n      <div className=\"flex h-5 items-center gap-[3px]\" aria-hidden>\n        {Array.from({ length: 9 }).map((_, i) => (\n          <span\n            key={i}\n            className=\"w-[3px] rounded-full bg-current\"\n            style={{\n              height: \"100%\",\n              transform: \"scaleY(.35)\",\n              transformOrigin: \"center\",\n              animation: active ? `aiui-bar 0.8s ease-in-out ${i * 0.06}s infinite` : \"none\",\n              opacity: active ? 1 : 0.3,\n            }}\n          />\n        ))}\n      </div>\n    </>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/* VoiceInput                                                          */\n/* ------------------------------------------------------------------ */\n\nexport function VoiceInput({\n  state = \"idle\",\n  onStart,\n  onStop,\n  onCancel,\n  recordingTime = \"0:07\",\n  transcript = \"\",\n  className = \"\",\n}: VoiceInputProps) {\n  if (state === \"recording\") {\n    return (\n      <div\n        className={`rounded-xl border border-red-200 bg-white p-3 dark:border-red-900/50 dark:bg-zinc-900 ${className}`}\n      >\n        <div className=\"flex items-center gap-3\">\n          <button\n            type=\"button\"\n            onClick={onStop}\n            aria-label=\"Stop recording\"\n            className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-red-500 text-white transition-transform hover:scale-105\"\n          >\n            <StopIcon />\n          </button>\n          <div className=\"min-w-0 flex-1 text-red-500 dark:text-red-400\">\n            <Bars active />\n          </div>\n          <span className=\"shrink-0 text-xs tabular-nums text-zinc-500 dark:text-zinc-400\">{recordingTime}</span>\n          {onCancel && (\n            <button\n              type=\"button\"\n              onClick={onCancel}\n              aria-label=\"Cancel recording\"\n              className=\"shrink-0 rounded-md p-1 text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800\"\n            >\n              <XIcon />\n            </button>\n          )}\n        </div>\n        <p className=\"mt-2 text-sm text-zinc-600 dark:text-zinc-300\">\n          {transcript || \"Listening…\"}\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      className={`flex items-center gap-3 rounded-xl border border-zinc-200 bg-white p-3 dark:border-zinc-800 dark:bg-zinc-900 ${className}`}\n    >\n      <button\n        type=\"button\"\n        onClick={onStart}\n        aria-label=\"Start voice input\"\n        className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-zinc-900 text-white transition-transform hover:scale-105 dark:bg-zinc-100 dark:text-zinc-900\"\n      >\n        <MicIcon />\n      </button>\n      <span className=\"text-sm text-zinc-500 dark:text-zinc-400\">Click to talk</span>\n      <div className=\"ml-auto text-zinc-300 dark:text-zinc-600\">\n        <Bars active={false} />\n      </div>\n    </div>\n  );\n}\n","type":"registry:ui","target":"components/ui/voice-input.tsx"}]}