@burtson-labs/ui 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,240 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { Button } from "./button.js";
3
+ import { formatDuration } from "./audio-player.js";
4
+ import * as React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import Pause from "@burtson-labs/icons/react/pause";
7
+ import Play from "@burtson-labs/icons/react/play";
8
+ import Trash from "@burtson-labs/icons/react/trash";
9
+ import Square from "@burtson-labs/icons/react/square";
10
+ import Mic from "@burtson-labs/icons/react/mic";
11
+ //#region src/components/voice-recorder.tsx
12
+ var LEVEL_BARS = 12;
13
+ function recordingSupported() {
14
+ return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof globalThis.MediaRecorder === "function";
15
+ }
16
+ /**
17
+ * A microphone control for a composer: tap to record, then pause, stop to
18
+ * keep the clip or discard it. While recording it shows a live level meter
19
+ * and the elapsed time, in the recording colour (a state, not an error).
20
+ * When the browser can't record or the microphone is blocked it says so and
21
+ * how to fix it.
22
+ */
23
+ function VoiceRecorder({ onRecorded, onCancel, onStateChange, maxDurationMs = 3e5, mimeType, label = "Record a voice message", className, ...props }) {
24
+ const [state, setStateRaw] = React.useState("idle");
25
+ const [elapsed, setElapsed] = React.useState(0);
26
+ const [levels, setLevels] = React.useState(() => Array.from({ length: LEVEL_BARS }, () => 0));
27
+ const recorder = React.useRef(null);
28
+ const stream = React.useRef(null);
29
+ const chunks = React.useRef([]);
30
+ const started = React.useRef(0);
31
+ const pausedTotal = React.useRef(0);
32
+ const pausedAt = React.useRef(0);
33
+ const discard = React.useRef(false);
34
+ const frame = React.useRef(0);
35
+ const audioCtx = React.useRef(null);
36
+ const tick = React.useRef(void 0);
37
+ const setState = React.useCallback((s) => {
38
+ setStateRaw(s);
39
+ onStateChange?.(s);
40
+ }, [onStateChange]);
41
+ const cleanup = React.useCallback(() => {
42
+ cancelAnimationFrame(frame.current);
43
+ clearInterval(tick.current);
44
+ stream.current?.getTracks().forEach((t) => t.stop());
45
+ stream.current = null;
46
+ audioCtx.current?.close().catch(() => void 0);
47
+ audioCtx.current = null;
48
+ setLevels(Array.from({ length: LEVEL_BARS }, () => 0));
49
+ }, []);
50
+ React.useEffect(() => cleanup, [cleanup]);
51
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
52
+ const recordedMs = () => now() - started.current - pausedTotal.current - (pausedAt.current ? now() - pausedAt.current : 0);
53
+ const meter = (s) => {
54
+ const Ctx = globalThis.AudioContext ?? globalThis.webkitAudioContext;
55
+ if (!Ctx) return;
56
+ try {
57
+ const ctx = new Ctx();
58
+ audioCtx.current = ctx;
59
+ const analyser = ctx.createAnalyser();
60
+ analyser.fftSize = 256;
61
+ ctx.createMediaStreamSource(s).connect(analyser);
62
+ const data = new Uint8Array(analyser.frequencyBinCount);
63
+ const draw = () => {
64
+ analyser.getByteFrequencyData(data);
65
+ const per = Math.floor(data.length / LEVEL_BARS) || 1;
66
+ setLevels(Array.from({ length: LEVEL_BARS }, (_, i) => {
67
+ let sum = 0;
68
+ for (let j = i * per; j < (i + 1) * per; j++) sum += data[j] ?? 0;
69
+ return Math.min(1, sum / per / 180);
70
+ }));
71
+ frame.current = requestAnimationFrame(draw);
72
+ };
73
+ draw();
74
+ } catch {
75
+ audioCtx.current?.close().catch(() => void 0);
76
+ audioCtx.current = null;
77
+ }
78
+ };
79
+ const start = async () => {
80
+ if (!recordingSupported()) {
81
+ setState("unsupported");
82
+ return;
83
+ }
84
+ setState("requesting");
85
+ let s;
86
+ try {
87
+ s = await navigator.mediaDevices.getUserMedia({ audio: true });
88
+ } catch (err) {
89
+ const name = err?.name;
90
+ setState(name === "NotAllowedError" || name === "SecurityError" ? "denied" : "error");
91
+ return;
92
+ }
93
+ stream.current = s;
94
+ const type = mimeType && MediaRecorder.isTypeSupported?.(mimeType) ? mimeType : void 0;
95
+ let rec;
96
+ try {
97
+ rec = new MediaRecorder(s, type ? { mimeType: type } : void 0);
98
+ } catch {
99
+ cleanup();
100
+ setState("error");
101
+ return;
102
+ }
103
+ recorder.current = rec;
104
+ chunks.current = [];
105
+ discard.current = false;
106
+ rec.ondataavailable = (e) => {
107
+ if (e.data && e.data.size > 0) chunks.current.push(e.data);
108
+ };
109
+ rec.onstop = () => {
110
+ const durationMs = Math.round(recordedMs());
111
+ const mime = rec.mimeType || type || "audio/webm";
112
+ cleanup();
113
+ setElapsed(0);
114
+ setState("idle");
115
+ if (!discard.current) onRecorded({
116
+ blob: new Blob(chunks.current, { type: mime }),
117
+ mimeType: mime,
118
+ durationMs
119
+ });
120
+ chunks.current = [];
121
+ };
122
+ started.current = now();
123
+ pausedTotal.current = 0;
124
+ pausedAt.current = 0;
125
+ rec.start(250);
126
+ setState("recording");
127
+ meter(s);
128
+ tick.current = setInterval(() => {
129
+ const ms = recordedMs();
130
+ setElapsed(ms);
131
+ if (ms >= maxDurationMs && rec.state !== "inactive") rec.stop();
132
+ }, 200);
133
+ };
134
+ const pause = () => {
135
+ const rec = recorder.current;
136
+ if (!rec) return;
137
+ if (rec.state === "recording") {
138
+ rec.pause();
139
+ pausedAt.current = now();
140
+ setState("paused");
141
+ } else if (rec.state === "paused") {
142
+ rec.resume();
143
+ pausedTotal.current += now() - pausedAt.current;
144
+ pausedAt.current = 0;
145
+ setState("recording");
146
+ }
147
+ };
148
+ const stop = (keep) => {
149
+ const rec = recorder.current;
150
+ discard.current = !keep;
151
+ if (rec && rec.state !== "inactive") rec.stop();
152
+ else {
153
+ cleanup();
154
+ setState("idle");
155
+ }
156
+ if (!keep) onCancel?.();
157
+ };
158
+ if (!(state === "recording" || state === "paused")) {
159
+ const note = state === "denied" ? "Microphone access is blocked. Allow it in your browser’s site settings, then try again." : state === "unsupported" ? "This browser can’t record audio. Try a current Chrome, Edge, Firefox or Safari." : state === "error" ? "The microphone could not start. Check that one is connected and not in use." : null;
160
+ return /* @__PURE__ */ jsxs("div", {
161
+ "data-slot": "voice-recorder",
162
+ "data-state": state,
163
+ className: cn("inline-flex items-center gap-2", className),
164
+ ...props,
165
+ children: [/* @__PURE__ */ jsx(Button, {
166
+ type: "button",
167
+ size: "icon-sm",
168
+ variant: "ghost",
169
+ "aria-label": label,
170
+ title: label,
171
+ disabled: state === "requesting" || state === "unsupported",
172
+ onClick: () => void start(),
173
+ className: "pointer-coarse:size-11",
174
+ children: /* @__PURE__ */ jsx(Mic, {})
175
+ }), note && /* @__PURE__ */ jsx("p", {
176
+ role: "alert",
177
+ className: "max-w-xs text-xs text-muted-foreground",
178
+ children: note
179
+ })]
180
+ });
181
+ }
182
+ return /* @__PURE__ */ jsxs("div", {
183
+ "data-slot": "voice-recorder",
184
+ "data-state": state,
185
+ role: "group",
186
+ "aria-label": "Recording",
187
+ className: cn("inline-flex items-center gap-2 rounded-full border border-recording/30 bg-recording/8 py-1 pr-1 pl-3", className),
188
+ ...props,
189
+ children: [
190
+ /* @__PURE__ */ jsx("span", {
191
+ "aria-hidden": true,
192
+ className: cn("size-2 shrink-0 rounded-full bg-recording", state === "recording" && "motion-safe:animate-pulse")
193
+ }),
194
+ /* @__PURE__ */ jsx("span", {
195
+ "aria-hidden": true,
196
+ className: "flex h-5 items-center gap-[2px]",
197
+ children: levels.map((l, i) => /* @__PURE__ */ jsx("span", {
198
+ className: "w-[3px] rounded-full bg-recording/80 transition-[height] duration-[var(--duration-fast)] ease-[var(--ease-standard)]",
199
+ style: { height: `${Math.max(15, Math.round(l * 100))}%` }
200
+ }, i))
201
+ }),
202
+ /* @__PURE__ */ jsx("span", {
203
+ role: "timer",
204
+ "aria-label": `${state === "paused" ? "Paused at" : "Recording"} ${formatDuration(elapsed / 1e3)}`,
205
+ className: "font-mono text-xs text-foreground tabular-nums",
206
+ children: formatDuration(elapsed / 1e3)
207
+ }),
208
+ /* @__PURE__ */ jsx(Button, {
209
+ type: "button",
210
+ size: "icon-sm",
211
+ variant: "ghost",
212
+ "aria-label": state === "paused" ? "Resume recording" : "Pause recording",
213
+ onClick: pause,
214
+ className: "rounded-full pointer-coarse:size-11",
215
+ children: state === "paused" ? /* @__PURE__ */ jsx(Play, { className: "fill-current" }) : /* @__PURE__ */ jsx(Pause, { className: "fill-current" })
216
+ }),
217
+ /* @__PURE__ */ jsx(Button, {
218
+ type: "button",
219
+ size: "icon-sm",
220
+ variant: "ghost",
221
+ "aria-label": "Discard recording",
222
+ onClick: () => stop(false),
223
+ className: "rounded-full pointer-coarse:size-11",
224
+ children: /* @__PURE__ */ jsx(Trash, {})
225
+ }),
226
+ /* @__PURE__ */ jsx(Button, {
227
+ type: "button",
228
+ size: "icon-sm",
229
+ "aria-label": "Stop and attach recording",
230
+ onClick: () => stop(true),
231
+ className: "rounded-full bg-recording text-recording-foreground hover:bg-recording/90 pointer-coarse:size-11",
232
+ children: /* @__PURE__ */ jsx(Square, { className: "fill-current" })
233
+ })
234
+ ]
235
+ });
236
+ }
237
+ //#endregion
238
+ export { VoiceRecorder };
239
+
240
+ //# sourceMappingURL=voice-recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-recorder.js","names":[],"sources":["../../src/components/voice-recorder.tsx"],"sourcesContent":["import Mic from '@burtson-labs/icons/react/mic';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport Square from '@burtson-labs/icons/react/square';\nimport Trash from '@burtson-labs/icons/react/trash';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { formatDuration } from './audio-player';\nimport { Button } from './button';\n\nexport type VoiceRecorderState =\n 'idle' | 'requesting' | 'recording' | 'paused' | 'denied' | 'unsupported' | 'error';\n\nexport interface VoiceRecording {\n blob: Blob;\n mimeType: string;\n durationMs: number;\n}\n\nexport interface VoiceRecorderProps extends React.ComponentProps<'div'> {\n /** The finished recording, after Stop. */\n onRecorded: (recording: VoiceRecording) => void;\n onCancel?: () => void;\n onStateChange?: (state: VoiceRecorderState) => void;\n /** Stops by itself at this length. Default 5 minutes. */\n maxDurationMs?: number;\n /** Preferred container, used when the browser supports it (e.g. \"audio/webm\"). */\n mimeType?: string;\n /** Accessible name of the record button. */\n label?: string;\n}\n\nconst LEVEL_BARS = 12;\n\nfunction recordingSupported() {\n return (\n typeof navigator !== 'undefined' &&\n typeof navigator.mediaDevices?.getUserMedia === 'function' &&\n typeof (globalThis as { MediaRecorder?: unknown }).MediaRecorder === 'function'\n );\n}\n\n/**\n * A microphone control for a composer: tap to record, then pause, stop to\n * keep the clip or discard it. While recording it shows a live level meter\n * and the elapsed time, in the recording colour (a state, not an error).\n * When the browser can't record or the microphone is blocked it says so and\n * how to fix it.\n */\nfunction VoiceRecorder({\n onRecorded,\n onCancel,\n onStateChange,\n maxDurationMs = 5 * 60 * 1000,\n mimeType,\n label = 'Record a voice message',\n className,\n ...props\n}: VoiceRecorderProps) {\n const [state, setStateRaw] = React.useState<VoiceRecorderState>('idle');\n const [elapsed, setElapsed] = React.useState(0);\n const [levels, setLevels] = React.useState<number[]>(() =>\n Array.from({ length: LEVEL_BARS }, () => 0),\n );\n const recorder = React.useRef<MediaRecorder | null>(null);\n const stream = React.useRef<MediaStream | null>(null);\n const chunks = React.useRef<Blob[]>([]);\n const started = React.useRef(0);\n const pausedTotal = React.useRef(0);\n const pausedAt = React.useRef(0);\n const discard = React.useRef(false);\n const frame = React.useRef(0);\n const audioCtx = React.useRef<AudioContext | null>(null);\n const tick = React.useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n const setState = React.useCallback(\n (s: VoiceRecorderState) => {\n setStateRaw(s);\n onStateChange?.(s);\n },\n [onStateChange],\n );\n\n const cleanup = React.useCallback(() => {\n cancelAnimationFrame(frame.current);\n clearInterval(tick.current);\n stream.current?.getTracks().forEach((t) => t.stop());\n stream.current = null;\n void audioCtx.current?.close().catch(() => undefined);\n audioCtx.current = null;\n setLevels(Array.from({ length: LEVEL_BARS }, () => 0));\n }, []);\n\n React.useEffect(() => cleanup, [cleanup]);\n\n const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());\n const recordedMs = () =>\n now() -\n started.current -\n pausedTotal.current -\n (pausedAt.current ? now() - pausedAt.current : 0);\n\n const meter = (s: MediaStream) => {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) return;\n try {\n const ctx = new Ctx();\n audioCtx.current = ctx;\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n ctx.createMediaStreamSource(s).connect(analyser);\n const data = new Uint8Array(analyser.frequencyBinCount);\n const draw = () => {\n analyser.getByteFrequencyData(data);\n const per = Math.floor(data.length / LEVEL_BARS) || 1;\n setLevels(\n Array.from({ length: LEVEL_BARS }, (_, i) => {\n let sum = 0;\n for (let j = i * per; j < (i + 1) * per; j++) sum += data[j] ?? 0;\n return Math.min(1, sum / per / 180);\n }),\n );\n frame.current = requestAnimationFrame(draw);\n };\n draw();\n } catch {\n // No meter is fine; recording still works. Drop the half-made context.\n void audioCtx.current?.close().catch(() => undefined);\n audioCtx.current = null;\n }\n };\n\n const start = async () => {\n if (!recordingSupported()) {\n setState('unsupported');\n return;\n }\n setState('requesting');\n let s: MediaStream;\n try {\n s = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (err) {\n const name = (err as { name?: string })?.name;\n setState(name === 'NotAllowedError' || name === 'SecurityError' ? 'denied' : 'error');\n return;\n }\n stream.current = s;\n const type = mimeType && MediaRecorder.isTypeSupported?.(mimeType) ? mimeType : undefined;\n let rec: MediaRecorder;\n try {\n rec = new MediaRecorder(s, type ? { mimeType: type } : undefined);\n } catch {\n cleanup();\n setState('error');\n return;\n }\n recorder.current = rec;\n chunks.current = [];\n discard.current = false;\n rec.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) chunks.current.push(e.data);\n };\n rec.onstop = () => {\n const durationMs = Math.round(recordedMs());\n const mime = rec.mimeType || type || 'audio/webm';\n cleanup();\n setElapsed(0);\n setState('idle');\n if (!discard.current)\n onRecorded({ blob: new Blob(chunks.current, { type: mime }), mimeType: mime, durationMs });\n chunks.current = [];\n };\n started.current = now();\n pausedTotal.current = 0;\n pausedAt.current = 0;\n rec.start(250);\n setState('recording');\n meter(s);\n tick.current = setInterval(() => {\n const ms = recordedMs();\n setElapsed(ms);\n if (ms >= maxDurationMs && rec.state !== 'inactive') rec.stop();\n }, 200);\n };\n\n const pause = () => {\n const rec = recorder.current;\n if (!rec) return;\n if (rec.state === 'recording') {\n rec.pause();\n pausedAt.current = now();\n setState('paused');\n } else if (rec.state === 'paused') {\n rec.resume();\n pausedTotal.current += now() - pausedAt.current;\n pausedAt.current = 0;\n setState('recording');\n }\n };\n\n const stop = (keep: boolean) => {\n const rec = recorder.current;\n discard.current = !keep;\n if (rec && rec.state !== 'inactive') rec.stop();\n else {\n cleanup();\n setState('idle');\n }\n if (!keep) onCancel?.();\n };\n\n const live = state === 'recording' || state === 'paused';\n\n if (!live) {\n const note =\n state === 'denied'\n ? 'Microphone access is blocked. Allow it in your browser’s site settings, then try again.'\n : state === 'unsupported'\n ? 'This browser can’t record audio. Try a current Chrome, Edge, Firefox or Safari.'\n : state === 'error'\n ? 'The microphone could not start. Check that one is connected and not in use.'\n : null;\n return (\n <div\n data-slot=\"voice-recorder\"\n data-state={state}\n className={cn('inline-flex items-center gap-2', className)}\n {...props}\n >\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={state === 'requesting' || state === 'unsupported'}\n onClick={() => void start()}\n className=\"pointer-coarse:size-11\"\n >\n <Mic />\n </Button>\n {note && (\n <p role=\"alert\" className=\"max-w-xs text-xs text-muted-foreground\">\n {note}\n </p>\n )}\n </div>\n );\n }\n\n return (\n <div\n data-slot=\"voice-recorder\"\n data-state={state}\n role=\"group\"\n aria-label=\"Recording\"\n className={cn(\n 'inline-flex items-center gap-2 rounded-full border border-recording/30 bg-recording/8 py-1 pr-1 pl-3',\n className,\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n 'size-2 shrink-0 rounded-full bg-recording',\n state === 'recording' && 'motion-safe:animate-pulse',\n )}\n />\n <span aria-hidden className=\"flex h-5 items-center gap-[2px]\">\n {levels.map((l, i) => (\n <span\n key={i}\n className=\"w-[3px] rounded-full bg-recording/80 transition-[height] duration-[var(--duration-fast)] ease-[var(--ease-standard)]\"\n style={{ height: `${Math.max(15, Math.round(l * 100))}%` }}\n />\n ))}\n </span>\n <span\n role=\"timer\"\n aria-label={`${state === 'paused' ? 'Paused at' : 'Recording'} ${formatDuration(elapsed / 1000)}`}\n className=\"font-mono text-xs text-foreground tabular-nums\"\n >\n {formatDuration(elapsed / 1000)}\n </span>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={state === 'paused' ? 'Resume recording' : 'Pause recording'}\n onClick={pause}\n className=\"rounded-full pointer-coarse:size-11\"\n >\n {state === 'paused' ? (\n <Play className=\"fill-current\" />\n ) : (\n <Pause className=\"fill-current\" />\n )}\n </Button>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label=\"Discard recording\"\n onClick={() => stop(false)}\n className=\"rounded-full pointer-coarse:size-11\"\n >\n <Trash />\n </Button>\n <Button\n type=\"button\"\n size=\"icon-sm\"\n aria-label=\"Stop and attach recording\"\n onClick={() => stop(true)}\n className=\"rounded-full bg-recording text-recording-foreground hover:bg-recording/90 pointer-coarse:size-11\"\n >\n <Square className=\"fill-current\" />\n </Button>\n </div>\n );\n}\n\nexport { VoiceRecorder };\n"],"mappings":";;;;;;;;;;;AAkCA,IAAM,aAAa;AAEnB,SAAS,qBAAqB;CAC5B,OACE,OAAO,cAAc,eACrB,OAAO,UAAU,cAAc,iBAAiB,cAChD,OAAQ,WAA2C,kBAAkB;AAEzE;;;;;;;;AASA,SAAS,cAAc,EACrB,YACA,UACA,eACA,gBAAgB,KAChB,UACA,QAAQ,0BACR,WACA,GAAG,SACkB;CACrB,MAAM,CAAC,OAAO,eAAe,MAAM,SAA6B,MAAM;CACtE,MAAM,CAAC,SAAS,cAAc,MAAM,SAAS,CAAC;CAC9C,MAAM,CAAC,QAAQ,aAAa,MAAM,eAChC,MAAM,KAAK,EAAE,QAAQ,WAAW,SAAS,CAAC,CAC5C;CACA,MAAM,WAAW,MAAM,OAA6B,IAAI;CACxD,MAAM,SAAS,MAAM,OAA2B,IAAI;CACpD,MAAM,SAAS,MAAM,OAAe,CAAC,CAAC;CACtC,MAAM,UAAU,MAAM,OAAO,CAAC;CAC9B,MAAM,cAAc,MAAM,OAAO,CAAC;CAClC,MAAM,WAAW,MAAM,OAAO,CAAC;CAC/B,MAAM,UAAU,MAAM,OAAO,KAAK;CAClC,MAAM,QAAQ,MAAM,OAAO,CAAC;CAC5B,MAAM,WAAW,MAAM,OAA4B,IAAI;CACvD,MAAM,OAAO,MAAM,OAAmD,KAAA,CAAS;CAE/E,MAAM,WAAW,MAAM,aACpB,MAA0B;EACzB,YAAY,CAAC;EACb,gBAAgB,CAAC;CACnB,GACA,CAAC,aAAa,CAChB;CAEA,MAAM,UAAU,MAAM,kBAAkB;EACtC,qBAAqB,MAAM,OAAO;EAClC,cAAc,KAAK,OAAO;EAC1B,OAAO,SAAS,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC;EACnD,OAAO,UAAU;EACjB,SAAc,SAAS,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;EACpD,SAAS,UAAU;EACnB,UAAU,MAAM,KAAK,EAAE,QAAQ,WAAW,SAAS,CAAC,CAAC;CACvD,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,SAAS,CAAC,OAAO,CAAC;CAExC,MAAM,YAAa,OAAO,gBAAgB,cAAc,YAAY,IAAI,IAAI,KAAK,IAAI;CACrF,MAAM,mBACJ,IAAI,IACJ,QAAQ,UACR,YAAY,WACX,SAAS,UAAU,IAAI,IAAI,SAAS,UAAU;CAEjD,MAAM,SAAS,MAAmB;EAChC,MAAM,MACJ,WAAW,gBACV,WAAuE;EAC1E,IAAI,CAAC,KAAK;EACV,IAAI;GACF,MAAM,MAAM,IAAI,IAAI;GACpB,SAAS,UAAU;GACnB,MAAM,WAAW,IAAI,eAAe;GACpC,SAAS,UAAU;GACnB,IAAI,wBAAwB,CAAC,CAAC,CAAC,QAAQ,QAAQ;GAC/C,MAAM,OAAO,IAAI,WAAW,SAAS,iBAAiB;GACtD,MAAM,aAAa;IACjB,SAAS,qBAAqB,IAAI;IAClC,MAAM,MAAM,KAAK,MAAM,KAAK,SAAS,UAAU,KAAK;IACpD,UACE,MAAM,KAAK,EAAE,QAAQ,WAAW,IAAI,GAAG,MAAM;KAC3C,IAAI,MAAM;KACV,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,OAAO,KAAK,MAAM;KAChE,OAAO,KAAK,IAAI,GAAG,MAAM,MAAM,GAAG;IACpC,CAAC,CACH;IACA,MAAM,UAAU,sBAAsB,IAAI;GAC5C;GACA,KAAK;EACP,QAAQ;GAEN,SAAc,SAAS,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;GACpD,SAAS,UAAU;EACrB;CACF;CAEA,MAAM,QAAQ,YAAY;EACxB,IAAI,CAAC,mBAAmB,GAAG;GACzB,SAAS,aAAa;GACtB;EACF;EACA,SAAS,YAAY;EACrB,IAAI;EACJ,IAAI;GACF,IAAI,MAAM,UAAU,aAAa,aAAa,EAAE,OAAO,KAAK,CAAC;EAC/D,SAAS,KAAK;GACZ,MAAM,OAAQ,KAA2B;GACzC,SAAS,SAAS,qBAAqB,SAAS,kBAAkB,WAAW,OAAO;GACpF;EACF;EACA,OAAO,UAAU;EACjB,MAAM,OAAO,YAAY,cAAc,kBAAkB,QAAQ,IAAI,WAAW,KAAA;EAChF,IAAI;EACJ,IAAI;GACF,MAAM,IAAI,cAAc,GAAG,OAAO,EAAE,UAAU,KAAK,IAAI,KAAA,CAAS;EAClE,QAAQ;GACN,QAAQ;GACR,SAAS,OAAO;GAChB;EACF;EACA,SAAS,UAAU;EACnB,OAAO,UAAU,CAAC;EAClB,QAAQ,UAAU;EAClB,IAAI,mBAAmB,MAAM;GAC3B,IAAI,EAAE,QAAQ,EAAE,KAAK,OAAO,GAAG,OAAO,QAAQ,KAAK,EAAE,IAAI;EAC3D;EACA,IAAI,eAAe;GACjB,MAAM,aAAa,KAAK,MAAM,WAAW,CAAC;GAC1C,MAAM,OAAO,IAAI,YAAY,QAAQ;GACrC,QAAQ;GACR,WAAW,CAAC;GACZ,SAAS,MAAM;GACf,IAAI,CAAC,QAAQ,SACX,WAAW;IAAE,MAAM,IAAI,KAAK,OAAO,SAAS,EAAE,MAAM,KAAK,CAAC;IAAG,UAAU;IAAM;GAAW,CAAC;GAC3F,OAAO,UAAU,CAAC;EACpB;EACA,QAAQ,UAAU,IAAI;EACtB,YAAY,UAAU;EACtB,SAAS,UAAU;EACnB,IAAI,MAAM,GAAG;EACb,SAAS,WAAW;EACpB,MAAM,CAAC;EACP,KAAK,UAAU,kBAAkB;GAC/B,MAAM,KAAK,WAAW;GACtB,WAAW,EAAE;GACb,IAAI,MAAM,iBAAiB,IAAI,UAAU,YAAY,IAAI,KAAK;EAChE,GAAG,GAAG;CACR;CAEA,MAAM,cAAc;EAClB,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,KAAK;EACV,IAAI,IAAI,UAAU,aAAa;GAC7B,IAAI,MAAM;GACV,SAAS,UAAU,IAAI;GACvB,SAAS,QAAQ;EACnB,OAAO,IAAI,IAAI,UAAU,UAAU;GACjC,IAAI,OAAO;GACX,YAAY,WAAW,IAAI,IAAI,SAAS;GACxC,SAAS,UAAU;GACnB,SAAS,WAAW;EACtB;CACF;CAEA,MAAM,QAAQ,SAAkB;EAC9B,MAAM,MAAM,SAAS;EACrB,QAAQ,UAAU,CAAC;EACnB,IAAI,OAAO,IAAI,UAAU,YAAY,IAAI,KAAK;OACzC;GACH,QAAQ;GACR,SAAS,MAAM;EACjB;EACA,IAAI,CAAC,MAAM,WAAW;CACxB;CAIA,IAAI,EAFS,UAAU,eAAe,UAAU,WAErC;EACT,MAAM,OACJ,UAAU,WACN,4FACA,UAAU,gBACR,oFACA,UAAU,UACR,gFACA;EACV,OACE,qBAAC,OAAD;GACE,aAAU;GACV,cAAY;GACZ,WAAW,GAAG,kCAAkC,SAAS;GACzD,GAAI;GAJN,UAAA,CAME,oBAAC,QAAD;IACE,MAAK;IACL,MAAK;IACL,SAAQ;IACR,cAAY;IACZ,OAAO;IACP,UAAU,UAAU,gBAAgB,UAAU;IAC9C,eAAe,KAAK,MAAM;IAC1B,WAAU;IAEV,UAAA,oBAAC,KAAD,CAAM,CAAA;GACA,CAAA,GACP,QACC,oBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;IACvB,UAAA;GACA,CAAA,CAEF;;CAET;CAEA,OACE,qBAAC,OAAD;EACE,aAAU;EACV,cAAY;EACZ,MAAK;EACL,cAAW;EACX,WAAW,GACT,wGACA,SACF;EACA,GAAI;EATN,UAAA;GAWE,oBAAC,QAAD;IACE,eAAA;IACA,WAAW,GACT,6CACA,UAAU,eAAe,2BAC3B;GACD,CAAA;GACD,oBAAC,QAAD;IAAM,eAAA;IAAY,WAAU;IACzB,UAAA,OAAO,KAAK,GAAG,MACd,oBAAC,QAAD;KAEE,WAAU;KACV,OAAO,EAAE,QAAQ,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,EAAE,GAAG;IAC1D,GAHM,CAGN,CACF;GACG,CAAA;GACN,oBAAC,QAAD;IACE,MAAK;IACL,cAAY,GAAG,UAAU,WAAW,cAAc,YAAY,GAAG,eAAe,UAAU,GAAI;IAC9F,WAAU;IAET,UAAA,eAAe,UAAU,GAAI;GAC1B,CAAA;GACN,oBAAC,QAAD;IACE,MAAK;IACL,MAAK;IACL,SAAQ;IACR,cAAY,UAAU,WAAW,qBAAqB;IACtD,SAAS;IACT,WAAU;IAET,UAAA,UAAU,WACT,oBAAC,MAAD,EAAM,WAAU,eAAgB,CAAA,IAEhC,oBAAC,OAAD,EAAO,WAAU,eAAgB,CAAA;GAE7B,CAAA;GACR,oBAAC,QAAD;IACE,MAAK;IACL,MAAK;IACL,SAAQ;IACR,cAAW;IACX,eAAe,KAAK,KAAK;IACzB,WAAU;IAEV,UAAA,oBAAC,OAAD,CAAQ,CAAA;GACF,CAAA;GACR,oBAAC,QAAD;IACE,MAAK;IACL,MAAK;IACL,cAAW;IACX,eAAe,KAAK,IAAI;IACxB,WAAU;IAEV,UAAA,oBAAC,QAAD,EAAQ,WAAU,eAAgB,CAAA;GAC5B,CAAA;EACL;;AAET"}
package/dist/index.d.ts CHANGED
@@ -6,11 +6,14 @@ export * from './components/alert';
6
6
  export * from './components/alert-dialog';
7
7
  export * from './components/app-shell';
8
8
  export * from './components/attachment';
9
+ export * from './components/audio-player';
9
10
  export * from './components/avatar';
10
11
  export * from './components/badge';
11
12
  export * from './components/breadcrumb';
12
13
  export * from './components/button';
13
14
  export * from './components/card';
15
+ export * from './components/chat-history';
16
+ export * from './components/chat-layout';
14
17
  export * from './components/checkbox';
15
18
  export * from './components/collapsible';
16
19
  export * from './components/combobox';
@@ -33,6 +36,7 @@ export * from './components/label';
33
36
  export * from './components/markdown';
34
37
  export * from './components/menubar';
35
38
  export * from './components/message';
39
+ export * from './components/message-actions';
36
40
  export * from './components/mobile-nav';
37
41
  export * from './components/navigation-menu';
38
42
  export * from './components/onboarding-checklist';
@@ -66,3 +70,4 @@ export * from './components/toolbar';
66
70
  export * from './components/tooltip';
67
71
  export * from './components/tour';
68
72
  export * from './components/tree-view';
73
+ export * from './components/voice-recorder';
package/dist/index.js CHANGED
@@ -9,24 +9,30 @@ import { AppShell, AppShellBody, AppShellContent, AppShellHeader, AppShellMain,
9
9
  import { Progress } from "./components/progress.js";
10
10
  import { Spinner } from "./components/spinner.js";
11
11
  import { AttachmentItem, AttachmentTray, UploadQueue, formatBytes } from "./components/attachment.js";
12
+ import { AudioPlayer, VoiceMessage, computePeaks, formatDuration, peaksFromChannelData } from "./components/audio-player.js";
12
13
  import { Avatar, AvatarFallback, AvatarImage } from "./components/avatar.js";
13
14
  import { Badge, badgeVariants } from "./components/badge.js";
14
15
  import { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "./components/breadcrumb.js";
15
16
  import { Card, CardAction, CardContent, CardDescription, CardEyebrow, CardFooter, CardHeader, CardTitle, cardVariants } from "./components/card.js";
17
+ import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, menuItemClasses, surfaceClasses } from "./components/popover.js";
18
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./components/dropdown-menu.js";
19
+ import { ChatHistory, groupConversations, matchesConversation } from "./components/chat-history.js";
20
+ import { IconButton } from "./components/icon-button.js";
21
+ import { ResizablePanel, ResizablePanelGroup, ResizeHandle, useGroupRef, usePanelRef } from "./components/resizable.js";
22
+ import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./components/sheet.js";
23
+ import { ChatLayout, useFullscreen } from "./components/chat-layout.js";
16
24
  import { Checkbox } from "./components/checkbox.js";
17
25
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./components/collapsible.js";
18
26
  import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "./components/dialog.js";
19
27
  import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from "./components/command.js";
20
28
  import { Input, fieldClasses } from "./components/input.js";
21
- import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, menuItemClasses, surfaceClasses } from "./components/popover.js";
22
29
  import { Combobox } from "./components/combobox.js";
23
- import { Composer, Suggestions } from "./components/composer.js";
30
+ import { Composer, Suggestions, acceptsFile } from "./components/composer.js";
24
31
  import { Status, StatusDot, statusDotVariants } from "./components/status.js";
25
32
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/tooltip.js";
26
33
  import { ConnectionBanner, ConnectionStatus, SyncStatus } from "./components/connection-status.js";
27
34
  import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from "./components/context-menu.js";
28
35
  import { Conversation } from "./components/conversation.js";
29
- import { IconButton } from "./components/icon-button.js";
30
36
  import { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, toastVariants } from "./components/toast.js";
31
37
  import { Toaster, toast } from "./components/toaster.js";
32
38
  import { CopyButton } from "./components/copy-button.js";
@@ -34,7 +40,6 @@ import { Pagination, paginationRange } from "./components/pagination.js";
34
40
  import { Skeleton } from "./components/skeleton.js";
35
41
  import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "./components/table.js";
36
42
  import { DataTable } from "./components/data-table.js";
37
- import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./components/dropdown-menu.js";
38
43
  import { EditorTabs } from "./components/editor-tabs.js";
39
44
  import { EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle } from "./components/empty-state.js";
40
45
  import { Field, FieldDescription, FieldError, FieldHeader, FieldHint, FieldLabel } from "./components/field.js";
@@ -43,18 +48,17 @@ import { Label } from "./components/label.js";
43
48
  import { CodeBlock, Markdown, parseMarkdown } from "./components/markdown.js";
44
49
  import { Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from "./components/menubar.js";
45
50
  import { Message } from "./components/message.js";
51
+ import { MessageActions, MessageAttachments, MessageEditor } from "./components/message-actions.js";
46
52
  import { MobileNav } from "./components/mobile-nav.js";
47
53
  import { NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, navigationMenuTriggerStyle } from "./components/navigation-menu.js";
48
54
  import { OnboardingChecklist } from "./components/onboarding-checklist.js";
49
55
  import { PageHeader, PageHeaderActions, PageHeaderDescription, PageHeaderEyebrow, PageHeaderMain, PageHeaderTitle } from "./components/page-header.js";
50
56
  import { RadioGroup, RadioGroupItem } from "./components/radio-group.js";
51
57
  import { Reasoning, StreamingIndicator } from "./components/reasoning.js";
52
- import { ResizablePanel, ResizablePanelGroup, ResizeHandle, useGroupRef, usePanelRef } from "./components/resizable.js";
53
58
  import { ScrollArea, ScrollBar } from "./components/scroll-area.js";
54
59
  import { SecretInput } from "./components/secret-input.js";
55
60
  import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from "./components/select.js";
56
61
  import { Separator } from "./components/separator.js";
57
- import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./components/sheet.js";
58
62
  import { Slider } from "./components/slider.js";
59
63
  import { SourceCitation, SourceList, safeSourceHref } from "./components/source.js";
60
64
  import { StatCard } from "./components/stat-card.js";
@@ -66,4 +70,5 @@ import { ToolApproval, ToolCall } from "./components/tool-call.js";
66
70
  import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarSpacer } from "./components/toolbar.js";
67
71
  import { Spotlight, Tour, TourAnchor, findTourTarget, tourTarget, waitForTourTarget } from "./components/tour.js";
68
72
  import { TreeView, flattenTree } from "./components/tree-view.js";
69
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellHeader, AppShellMain, AppShellSidebar, AttachmentItem, AttachmentTray, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Card, CardAction, CardContent, CardDescription, CardEyebrow, CardFooter, CardHeader, CardTitle, Checkbox, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Composer, ConnectionBanner, ConnectionStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, Conversation, CopyButton, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorTabs, EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle, Field, FieldDescription, FieldError, FieldHeader, FieldHint, FieldLabel, IconButton, Input, Kbd, KbdGroup, Label, Markdown, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Message, MobileNav, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, OnboardingChecklist, PageHeader, PageHeaderActions, PageHeaderDescription, PageHeaderEyebrow, PageHeaderMain, PageHeaderTitle, Pagination, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, Reasoning, ResizablePanel, ResizablePanelGroup, ResizeHandle, ScrollArea, ScrollBar, SecretInput, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Skeleton, Slider, SourceCitation, SourceList, Spinner, Spotlight, StatCard, Status, StatusDot, Steps, StreamingIndicator, Suggestions, Switch, SyncStatus, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, ToolApproval, ToolCall, Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarSpacer, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Tour, TourAnchor, TreeView, UploadQueue, accentTokens, alertVariants, badgeVariants, brand, buttonVariants, cardVariants, cn, contrast, dark, duration, easing, elevation, fieldClasses, findTourTarget, flattenTree, fontMono, fontSans, formatBytes, isApplePlatform, light, menuItemClasses, mix, modKeyLabel, motion, navigationMenuTriggerStyle, paginationRange, parseMarkdown, radius, safeSourceHref, shadow, shortcutLabel, statusDotVariants, surfaceClasses, toast, toastVariants, tokens, tourTarget, useGroupRef, usePanelRef, waitForTourTarget };
73
+ import { VoiceRecorder } from "./components/voice-recorder.js";
74
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellHeader, AppShellMain, AppShellSidebar, AttachmentItem, AttachmentTray, AudioPlayer, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Card, CardAction, CardContent, CardDescription, CardEyebrow, CardFooter, CardHeader, CardTitle, ChatHistory, ChatLayout, Checkbox, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Composer, ConnectionBanner, ConnectionStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, Conversation, CopyButton, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorTabs, EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle, Field, FieldDescription, FieldError, FieldHeader, FieldHint, FieldLabel, IconButton, Input, Kbd, KbdGroup, Label, Markdown, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Message, MessageActions, MessageAttachments, MessageEditor, MobileNav, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, OnboardingChecklist, PageHeader, PageHeaderActions, PageHeaderDescription, PageHeaderEyebrow, PageHeaderMain, PageHeaderTitle, Pagination, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, Reasoning, ResizablePanel, ResizablePanelGroup, ResizeHandle, ScrollArea, ScrollBar, SecretInput, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Skeleton, Slider, SourceCitation, SourceList, Spinner, Spotlight, StatCard, Status, StatusDot, Steps, StreamingIndicator, Suggestions, Switch, SyncStatus, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, ToolApproval, ToolCall, Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarSpacer, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Tour, TourAnchor, TreeView, UploadQueue, VoiceMessage, VoiceRecorder, accentTokens, acceptsFile, alertVariants, badgeVariants, brand, buttonVariants, cardVariants, cn, computePeaks, contrast, dark, duration, easing, elevation, fieldClasses, findTourTarget, flattenTree, fontMono, fontSans, formatBytes, formatDuration, groupConversations, isApplePlatform, light, matchesConversation, menuItemClasses, mix, modKeyLabel, motion, navigationMenuTriggerStyle, paginationRange, parseMarkdown, peaksFromChannelData, radius, safeSourceHref, shadow, shortcutLabel, statusDotVariants, surfaceClasses, toast, toastVariants, tokens, tourTarget, useFullscreen, useGroupRef, usePanelRef, waitForTourTarget };