{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "voice-button",
	"title": "Voice Button",
	"type": "registry:ui",
	"description": "A voice conversation toggle button with live mic waveform and start/stop state.",
	"dependencies": [
		"@lucide/svelte"
	],
	"devDependencies": [
		"@lucide/svelte@^1.7.0"
	],
	"registryDependencies": [
		"button",
		"https://sv11.ui.twango.dev/r/live-waveform.json"
	],
	"files": [
		{
			"content": "<script lang=\"ts\" module>\n\texport type VoiceButtonState = \"idle\" | \"recording\" | \"processing\" | \"success\" | \"error\";\n</script>\n\n<script lang=\"ts\">\n\timport type { ComponentProps, Snippet } from \"svelte\";\n\timport Check from \"@lucide/svelte/icons/check\";\n\timport X from \"@lucide/svelte/icons/x\";\n\timport { Button } from \"$UI$/button/index.js\";\n\timport { LiveWaveform } from \"$UI$/live-waveform/index.js\";\n\timport { cn } from \"$UTILS$.js\";\n\n\texport type VoiceButtonProps = Omit<ComponentProps<typeof Button>, \"onclick\"> & {\n\t\t/**\n\t\t * Current state of the voice button. Drives the internal waveform,\n\t\t * success/error feedback, and disabled behavior.\n\t\t * @default \"idle\"\n\t\t */\n\t\tstate?: VoiceButtonState;\n\t\t/** Called when the button is clicked, after any native `onclick` handler. */\n\t\tonPress?: () => void;\n\t\t/** Native click handler invoked before `onPress`. */\n\t\tonclick?: (e: MouseEvent) => void;\n\t\t/** Leading content rendered on non-icon sizes. Accepts a string or a snippet. */\n\t\tlabel?: string | Snippet;\n\t\t/**\n\t\t * Trailing content rendered on the right (e.g. a keyboard shortcut).\n\t\t * Hidden while the waveform is active. Accepts a string or a snippet.\n\t\t */\n\t\ttrailing?: string | Snippet;\n\t\t/** Icon snippet rendered when `size=\"icon\"` and no waveform is active. */\n\t\ticon?: Snippet;\n\t\t/** Extra classes forwarded to the waveform container. */\n\t\twaveformClassName?: string;\n\t\t/**\n\t\t * Milliseconds to display the success/error state before returning\n\t\t * to idle visuals.\n\t\t * @default 1500\n\t\t */\n\t\tfeedbackDuration?: number;\n\t};\n\n\tlet {\n\t\tstate: voiceState = \"idle\",\n\t\tonPress,\n\t\tlabel,\n\t\ttrailing,\n\t\ticon,\n\t\tvariant = \"outline\",\n\t\tsize = \"default\",\n\t\twaveformClassName,\n\t\tfeedbackDuration = 1500,\n\t\tdisabled = false,\n\t\tclass: className,\n\t\tonclick: externalOnClick,\n\t\t...restProps\n\t}: VoiceButtonProps = $props();\n\n\tlet showFeedback = $state(false);\n\n\t$effect(() => {\n\t\tif (voiceState === \"success\" || voiceState === \"error\") {\n\t\t\tshowFeedback = true;\n\t\t\tconst t = setTimeout(() => {\n\t\t\t\tshowFeedback = false;\n\t\t\t}, feedbackDuration);\n\t\t\treturn () => clearTimeout(t);\n\t\t}\n\t\tshowFeedback = false;\n\t});\n\n\tconst isRecording = $derived(voiceState === \"recording\");\n\tconst isProcessing = $derived(voiceState === \"processing\");\n\tconst isSuccess = $derived(voiceState === \"success\");\n\tconst isError = $derived(voiceState === \"error\");\n\tconst isDisabled = $derived(disabled || isProcessing);\n\tconst shouldShowWaveform = $derived(isRecording || isProcessing || showFeedback);\n\tconst shouldShowTrailing = $derived(!shouldShowWaveform && trailing != null);\n\tconst shouldShowIcon = $derived(\n\t\t!shouldShowWaveform && !shouldShowTrailing && icon != null && size === \"icon\"\n\t);\n</script>\n\n<Button\n\ttype=\"button\"\n\t{variant}\n\t{size}\n\tdisabled={isDisabled}\n\taria-label=\"Voice Button\"\n\tclass={cn(\"gap-2 transition-all duration-200\", size === \"icon\" && \"relative\", className)}\n\tonclick={(e) => {\n\t\texternalOnClick?.(e);\n\t\tonPress?.();\n\t}}\n\t{...restProps}\n>\n\t{#if size !== \"icon\" && label != null}\n\t\t<span class=\"inline-flex shrink-0 items-center justify-start\">\n\t\t\t{#if typeof label === \"string\"}\n\t\t\t\t{label}\n\t\t\t{:else}\n\t\t\t\t{@render label()}\n\t\t\t{/if}\n\t\t</span>\n\t{/if}\n\n\t<div\n\t\tclass={cn(\n\t\t\t\"relative box-content flex shrink-0 items-center justify-center overflow-hidden transition-all duration-300\",\n\t\t\tsize === \"icon\" ? \"absolute inset-0 rounded-sm border-0\" : \"h-5 w-24 rounded-sm border\",\n\t\t\tisRecording\n\t\t\t\t? \"bg-primary/10 dark:bg-primary/5\"\n\t\t\t\t: size === \"icon\"\n\t\t\t\t\t? \"bg-muted/50 border-0\"\n\t\t\t\t\t: \"border-border bg-muted/50\",\n\t\t\twaveformClassName\n\t\t)}\n\t>\n\t\t{#if shouldShowWaveform}\n\t\t\t<LiveWaveform\n\t\t\t\tactive={isRecording}\n\t\t\t\tprocessing={isProcessing || isSuccess}\n\t\t\t\tbarWidth={2}\n\t\t\t\tbarGap={1}\n\t\t\t\tbarRadius={4}\n\t\t\t\tfadeEdges={false}\n\t\t\t\tsensitivity={1.8}\n\t\t\t\tsmoothingTimeConstant={0.85}\n\t\t\t\theight={20}\n\t\t\t\tmode=\"static\"\n\t\t\t\tclass=\"animate-in fade-in absolute inset-0 h-full w-full duration-300\"\n\t\t\t/>\n\t\t{/if}\n\n\t\t{#if shouldShowTrailing}\n\t\t\t<div\n\t\t\t\tclass=\"animate-in fade-in absolute inset-0 flex items-center justify-center duration-300\"\n\t\t\t>\n\t\t\t\t{#if typeof trailing === \"string\"}\n\t\t\t\t\t<span class=\"text-muted-foreground px-1.5 font-mono text-[10px] font-medium select-none\">\n\t\t\t\t\t\t{trailing}\n\t\t\t\t\t</span>\n\t\t\t\t{:else if trailing}\n\t\t\t\t\t{@render trailing()}\n\t\t\t\t{/if}\n\t\t\t</div>\n\t\t{/if}\n\n\t\t{#if shouldShowIcon && icon}\n\t\t\t<div\n\t\t\t\tclass=\"animate-in fade-in absolute inset-0 flex items-center justify-center duration-300\"\n\t\t\t>\n\t\t\t\t{@render icon()}\n\t\t\t</div>\n\t\t{/if}\n\n\t\t{#if isSuccess && showFeedback}\n\t\t\t<div\n\t\t\t\tclass=\"animate-in fade-in bg-background/80 absolute inset-0 flex items-center justify-center duration-300\"\n\t\t\t>\n\t\t\t\t<Check class=\"text-primary size-3.5\" />\n\t\t\t</div>\n\t\t{/if}\n\n\t\t{#if isError && showFeedback}\n\t\t\t<div\n\t\t\t\tclass=\"animate-in fade-in bg-background/80 absolute inset-0 flex items-center justify-center duration-300\"\n\t\t\t>\n\t\t\t\t<X class=\"text-destructive size-3.5\" />\n\t\t\t</div>\n\t\t{/if}\n\t</div>\n</Button>\n",
			"type": "registry:ui",
			"target": "voice-button/voice-button.svelte"
		},
		{
			"content": "import Root from \"./voice-button.svelte\";\n\nexport {\n\tRoot,\n\t//\n\tRoot as VoiceButton,\n};\nexport type { VoiceButtonProps, VoiceButtonState } from \"./voice-button.svelte\";\n",
			"type": "registry:ui",
			"target": "voice-button/index.ts"
		}
	]
}