@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.
- package/README.md +2 -2
- package/dist/components/audio-player.d.ts +39 -0
- package/dist/components/audio-player.js +250 -0
- package/dist/components/audio-player.js.map +1 -0
- package/dist/components/chat-history.d.ts +64 -0
- package/dist/components/chat-history.js +282 -0
- package/dist/components/chat-history.js.map +1 -0
- package/dist/components/chat-layout.d.ts +42 -0
- package/dist/components/chat-layout.js +217 -0
- package/dist/components/chat-layout.js.map +1 -0
- package/dist/components/combobox.js +1 -1
- package/dist/components/composer.d.ts +16 -1
- package/dist/components/composer.js +103 -20
- package/dist/components/composer.js.map +1 -1
- package/dist/components/data-table.js +1 -1
- package/dist/components/message-actions.d.ts +56 -0
- package/dist/components/message-actions.js +181 -0
- package/dist/components/message-actions.js.map +1 -0
- package/dist/components/secret-input.js +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/voice-recorder.d.ts +28 -0
- package/dist/components/voice-recorder.js +240 -0
- package/dist/components/voice-recorder.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -7
- package/dist/styles.css +1 -1
- package/dist/tokens.js +1 -1
- package/dist/tokens.js.map +1 -1
- package/package.json +1 -1
|
@@ -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":"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"}
|
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
|
-
|
|
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 };
|