@epicbagel/saas-kit 1.16.0 → 1.17.0

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 CHANGED
@@ -42,16 +42,20 @@ does not import a voice provider. Stop and release audio tracks before setting
42
42
  Wrap the application in `DictationProvider` to use an authenticated transcription
43
43
  API for every `VoiceDictationButton`, including buttons nested in chat, forms or
44
44
  another component library. The recorder opens beside its microphone on desktop
45
- and fills the screen below 768px, with elapsed time, LiveKit audio bars and
46
- Done/Cancel controls. The bars use the recording stream without opening a second
47
- microphone. Resizing keeps the current recording alive.
45
+ and fills the screen below 768px. Its roomy panel puts the title and close control
46
+ above a microphone, live audio bars and elapsed time, followed by the transcript
47
+ and Done/Cancel controls. The microphone and bars share the palette's first chart
48
+ colour; surfaces and buttons keep their normal theme. The bars use the recording
49
+ stream without opening a second microphone; native recognition has no meter and
50
+ shows no waveform. No device selector is shown because the capture API does not
51
+ offer device selection. Resizing keeps the current recording alive.
48
52
 
49
53
  For hidden/programmatic triggers, it uses the focused visible button as the
50
54
  desktop anchor. Hosts can pass an explicit `anchorRef` for a custom trigger.
51
55
  Without a visible anchor it opens full-screen. Nested and sibling dialogs keep
52
56
  their focus and cancellation behavior.
53
57
  The recorder runs until **Done**, finishes the last words, then commits the returned text once. Failed requests keep the
54
- take available for retry. Cancel stops microphone tracks, aborts the request and
58
+ take available for retry. Cancel and the header's close control stop microphone tracks, abort the request and
55
59
  ignores a late answer. The kit never persists audio or knows provider credentials.
56
60
 
57
61
  ```tsx
@@ -118,40 +118,72 @@ function VoiceDictationButton({
118
118
  }
119
119
  };
120
120
  const content = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
121
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex min-w-0 items-start justify-between gap-3", !anchored && "w-full max-w-xl self-center"), children: [
122
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "min-w-0 space-y-1", children: [
123
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "font-medium", anchored ? "text-sm" : "text-lg"), children: title || "Voice dictation" }),
124
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
125
- busy ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.LoaderCircle, { "aria-hidden": "true", className: "size-3.5 animate-spin motion-reduce:animate-none" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { "aria-hidden": "true", className: "size-3.5" }),
121
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex min-w-0 shrink-0 items-start justify-between gap-4", !anchored && "w-full max-w-xl self-center"), children: [
122
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "min-w-0 space-y-2 pt-1", children: [
123
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "break-words text-xl font-semibold leading-tight", children: title || "Voice dictation" }),
124
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { role: "status", className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
125
+ busy && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.LoaderCircle, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin motion-reduce:animate-none" }),
126
126
  capture.state === "transcribing" ? "Finishing transcript\u2026" : capture.state === "requesting" ? "Starting dictation\u2026" : error ? "Recording not saved" : capture.state === "ready" ? "Ready" : caption
127
127
  ] })
128
128
  ] }),
129
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "time", { "aria-label": "Recording duration", dateTime: `PT${elapsed}S`, className: "pt-0.5 font-mono text-xs tabular-nums text-muted-foreground", children: [
130
- Math.floor(elapsed / 60).toString().padStart(2, "0"),
131
- ":",
132
- (elapsed % 60).toString().padStart(2, "0")
133
- ] })
129
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
130
+ _chunkJDEXZGIYcjs.Button,
131
+ {
132
+ type: "button",
133
+ variant: "ghost",
134
+ size: "icon",
135
+ shape: "circle",
136
+ "aria-label": "Cancel dictation",
137
+ className: "size-[44px] shrink-0 text-muted-foreground [&_svg]:size-5",
138
+ onClick: handleCancel,
139
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.X, { "aria-hidden": "true", className: "size-5" })
140
+ }
141
+ )
134
142
  ] }),
135
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex min-h-0 flex-col gap-3", !anchored && "w-full max-w-xl flex-1 justify-center self-center py-8 text-center"), children: [
136
- capture.audioLevels && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DictationWaveform, { source: capture.audioLevels, state: capture.state, className: !anchored ? "h-20" : void 0 }),
137
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: descriptionId, className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "Finishing your last words\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Try recording again." : "Speak, then choose Done to add the text." }),
138
- displayTranscript && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "max-h-40 overflow-y-auto text-sm leading-relaxed", children: [
143
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex flex-col gap-5", !anchored && "w-full max-w-xl flex-1 justify-center self-center py-6"), children: [
144
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { "data-slot": "dictation-meter", className: "flex min-w-0 items-center gap-3 py-5 text-[hsl(var(--chart-1))] sm:gap-5 sm:py-7", children: [
145
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
146
+ "div",
147
+ {
148
+ "aria-hidden": "true",
149
+ "data-slot": "dictation-microphone",
150
+ className: "flex size-[72px] shrink-0 items-center justify-center rounded-full bg-current/10 sm:size-[96px]",
151
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex size-[52px] items-center justify-center rounded-full bg-[hsl(var(--chart-1))] text-popover sm:size-[72px]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { className: "size-7 sm:size-9" }) })
152
+ }
153
+ ),
154
+ capture.audioLevels && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DictationWaveform, { source: capture.audioLevels, state: capture.state }),
155
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
156
+ "time",
157
+ {
158
+ "aria-label": "Recording duration",
159
+ dateTime: `PT${elapsed}S`,
160
+ className: "ml-auto shrink-0 font-mono text-xl tabular-nums text-muted-foreground sm:text-2xl",
161
+ children: [
162
+ Math.floor(elapsed / 60).toString().padStart(2, "0"),
163
+ ":",
164
+ (elapsed % 60).toString().padStart(2, "0")
165
+ ]
166
+ }
167
+ )
168
+ ] }),
169
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: descriptionId, className: "text-base leading-relaxed text-muted-foreground", children: capture.state === "transcribing" ? "Finishing your last words\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Try recording again." : "Speak, then choose Done to add the text." }),
170
+ displayTranscript && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "max-h-48 overflow-y-auto break-words text-base leading-relaxed", children: [
139
171
  transcript,
140
172
  interim && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "text-muted-foreground", children: [
141
173
  transcript ? " " : "",
142
174
  interim
143
175
  ] })
144
176
  ] }),
145
- error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-xs text-destructive", children: error }),
146
- !error && capture.previewError && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "status", className: "text-xs text-muted-foreground", children: capture.previewError })
177
+ error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-sm leading-relaxed text-destructive", children: error }),
178
+ !error && capture.previewError && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "status", className: "text-sm leading-relaxed text-muted-foreground", children: capture.previewError })
147
179
  ] }),
148
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "gap-2", anchored ? "flex flex-wrap items-center justify-end" : "mt-auto grid w-full max-w-xl grid-cols-2 gap-3 self-center"), children: [
149
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { ref: cancelControl, type: "button", variant: "ghost", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: handleCancel, children: "Cancel" }),
150
- capture.canRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", variant: "outline", size: "sm", className: anchored ? "min-h-[44px]" : "order-first col-span-2 min-h-[48px]", onClick: () => {
180
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "shrink-0 gap-3 pt-2", anchored ? "flex flex-wrap items-center justify-end" : "mt-auto grid w-full max-w-xl grid-cols-[auto_minmax(0,1fr)] self-center"), children: [
181
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { ref: cancelControl, type: "button", variant: "ghost", className: "min-h-[48px] px-5", onClick: handleCancel, children: "Cancel" }),
182
+ capture.canRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", variant: "outline", className: _chunkKWLSBTB2cjs.cn.call(void 0, "min-h-[48px] px-5", !anchored && "order-first col-span-2"), onClick: () => {
151
183
  _optionalChain([cancelControl, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.focus, 'call', _15 => _15({ preventScroll: true })]);
152
184
  void capture.start();
153
185
  }, children: "Record again" }),
154
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: () => void handleDone(), disabled: busy, children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done" })
186
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", className: "min-h-[48px] min-w-24 px-6", onClick: () => void handleDone(), disabled: busy, children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done" })
155
187
  ] })
156
188
  ] });
157
189
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
@@ -195,13 +227,13 @@ function VoiceDictationButton({
195
227
  "data-placement": "anchored",
196
228
  side: "top",
197
229
  align: "center",
198
- sideOffset: 8,
230
+ sideOffset: 12,
199
231
  collisionPadding: 12,
200
232
  avoidCollisions: true,
201
233
  onCloseAutoFocus: closeAutoFocus,
202
234
  onKeyDown,
203
235
  onEscapeKeyDown: (event) => event.stopPropagation(),
204
- className: "z-[200] flex max-h-[var(--radix-popover-content-available-height)] w-96 max-w-[calc(100vw-1.5rem)] flex-col gap-3 overflow-y-auto rounded-2xl border border-border bg-background p-4 text-foreground shadow-lg outline-hidden",
236
+ className: "z-[200] flex max-h-[var(--radix-popover-content-available-height)] w-[560px] max-w-[calc(100vw-24px)] flex-col gap-6 overflow-y-auto rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-xl outline-hidden",
205
237
  children: content
206
238
  }
207
239
  ) })
@@ -221,7 +253,7 @@ function VoiceDictationButton({
221
253
  onCloseAutoFocus: closeAutoFocus,
222
254
  onKeyDown,
223
255
  onEscapeKeyDown: (event) => event.stopPropagation(),
224
- className: "fixed inset-0 z-[200] flex h-dvh w-screen flex-col gap-6 overflow-y-auto overscroll-contain bg-background px-6 pt-[max(1.5rem,env(safe-area-inset-top))] pb-[max(1.5rem,env(safe-area-inset-bottom))] text-foreground outline-hidden",
256
+ className: "fixed inset-0 z-[200] flex h-dvh w-screen flex-col gap-6 overflow-y-auto overscroll-contain bg-popover px-5 pt-[max(1.5rem,env(safe-area-inset-top))] pb-[max(1.5rem,env(safe-area-inset-bottom))] text-popover-foreground outline-hidden",
225
257
  children: [
226
258
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Title, { className: "sr-only", children: "Voice dictation" }),
227
259
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Description, { className: "sr-only", children: "Record your message, then choose Done to add the text. Cancel discards the recording." }),
@@ -234,7 +266,7 @@ function VoiceDictationButton({
234
266
  }
235
267
  var SILENCE = [];
236
268
  var silence = () => SILENCE;
237
- function DictationWaveform({ source, state, className }) {
269
+ function DictationWaveform({ source, state }) {
238
270
  const bands = _react.useSyncExternalStore.call(void 0, source.subscribe, source.getSnapshot, silence);
239
271
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
240
272
  _chunkNKWHDI4Ccjs.AgentAudioVisualizerBar,
@@ -245,7 +277,8 @@ function DictationWaveform({ source, state, className }) {
245
277
  barCount: 24,
246
278
  state: state === "recording" ? "listening" : state === "transcribing" ? "thinking" : state === "requesting" ? "connecting" : "disconnected",
247
279
  volumeBands: state === "recording" ? bands : void 0,
248
- className: _chunkKWLSBTB2cjs.cn.call(void 0, "h-9 w-full text-foreground", className)
280
+ className: "h-[64px] min-w-0 flex-1 gap-[3px] sm:h-[88px] sm:gap-[4px]",
281
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-h-[4px] max-w-[6px] flex-1 rounded-full bg-current/15 transition-colors duration-200 motion-reduce:transition-none data-[lk-highlighted=true]:bg-current" })
249
282
  }
250
283
  );
251
284
  }
@@ -23,7 +23,7 @@ import {
23
23
  } from "react";
24
24
  import * as DialogPrimitive from "@radix-ui/react-dialog";
25
25
  import * as PopoverPrimitive from "@radix-ui/react-popover";
26
- import { Mic, LoaderCircle } from "lucide-react";
26
+ import { Mic, LoaderCircle, X } from "lucide-react";
27
27
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
28
28
  function VoiceDictationButton({
29
29
  onCommit,
@@ -118,40 +118,72 @@ function VoiceDictationButton({
118
118
  }
119
119
  };
120
120
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
121
- /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-start justify-between gap-3", !anchored && "w-full max-w-xl self-center"), children: [
122
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
123
- /* @__PURE__ */ jsx("h2", { className: cn("font-medium", anchored ? "text-sm" : "text-lg"), children: title || "Voice dictation" }),
124
- /* @__PURE__ */ jsxs("span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
125
- busy ? /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true", className: "size-3.5 animate-spin motion-reduce:animate-none" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true", className: "size-3.5" }),
121
+ /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 shrink-0 items-start justify-between gap-4", !anchored && "w-full max-w-xl self-center"), children: [
122
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-2 pt-1", children: [
123
+ /* @__PURE__ */ jsx("h2", { className: "break-words text-xl font-semibold leading-tight", children: title || "Voice dictation" }),
124
+ /* @__PURE__ */ jsxs("span", { role: "status", className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
125
+ busy && /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true", className: "size-4 shrink-0 animate-spin motion-reduce:animate-none" }),
126
126
  capture.state === "transcribing" ? "Finishing transcript\u2026" : capture.state === "requesting" ? "Starting dictation\u2026" : error ? "Recording not saved" : capture.state === "ready" ? "Ready" : caption
127
127
  ] })
128
128
  ] }),
129
- /* @__PURE__ */ jsxs("time", { "aria-label": "Recording duration", dateTime: `PT${elapsed}S`, className: "pt-0.5 font-mono text-xs tabular-nums text-muted-foreground", children: [
130
- Math.floor(elapsed / 60).toString().padStart(2, "0"),
131
- ":",
132
- (elapsed % 60).toString().padStart(2, "0")
133
- ] })
129
+ /* @__PURE__ */ jsx(
130
+ Button,
131
+ {
132
+ type: "button",
133
+ variant: "ghost",
134
+ size: "icon",
135
+ shape: "circle",
136
+ "aria-label": "Cancel dictation",
137
+ className: "size-[44px] shrink-0 text-muted-foreground [&_svg]:size-5",
138
+ onClick: handleCancel,
139
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-5" })
140
+ }
141
+ )
134
142
  ] }),
135
- /* @__PURE__ */ jsxs("div", { className: cn("flex min-h-0 flex-col gap-3", !anchored && "w-full max-w-xl flex-1 justify-center self-center py-8 text-center"), children: [
136
- capture.audioLevels && /* @__PURE__ */ jsx(DictationWaveform, { source: capture.audioLevels, state: capture.state, className: !anchored ? "h-20" : void 0 }),
137
- /* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "Finishing your last words\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Try recording again." : "Speak, then choose Done to add the text." }),
138
- displayTranscript && /* @__PURE__ */ jsxs("p", { className: "max-h-40 overflow-y-auto text-sm leading-relaxed", children: [
143
+ /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5", !anchored && "w-full max-w-xl flex-1 justify-center self-center py-6"), children: [
144
+ /* @__PURE__ */ jsxs("div", { "data-slot": "dictation-meter", className: "flex min-w-0 items-center gap-3 py-5 text-[hsl(var(--chart-1))] sm:gap-5 sm:py-7", children: [
145
+ /* @__PURE__ */ jsx(
146
+ "div",
147
+ {
148
+ "aria-hidden": "true",
149
+ "data-slot": "dictation-microphone",
150
+ className: "flex size-[72px] shrink-0 items-center justify-center rounded-full bg-current/10 sm:size-[96px]",
151
+ children: /* @__PURE__ */ jsx("div", { className: "flex size-[52px] items-center justify-center rounded-full bg-[hsl(var(--chart-1))] text-popover sm:size-[72px]", children: /* @__PURE__ */ jsx(Mic, { className: "size-7 sm:size-9" }) })
152
+ }
153
+ ),
154
+ capture.audioLevels && /* @__PURE__ */ jsx(DictationWaveform, { source: capture.audioLevels, state: capture.state }),
155
+ /* @__PURE__ */ jsxs(
156
+ "time",
157
+ {
158
+ "aria-label": "Recording duration",
159
+ dateTime: `PT${elapsed}S`,
160
+ className: "ml-auto shrink-0 font-mono text-xl tabular-nums text-muted-foreground sm:text-2xl",
161
+ children: [
162
+ Math.floor(elapsed / 60).toString().padStart(2, "0"),
163
+ ":",
164
+ (elapsed % 60).toString().padStart(2, "0")
165
+ ]
166
+ }
167
+ )
168
+ ] }),
169
+ /* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-base leading-relaxed text-muted-foreground", children: capture.state === "transcribing" ? "Finishing your last words\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Try recording again." : "Speak, then choose Done to add the text." }),
170
+ displayTranscript && /* @__PURE__ */ jsxs("p", { className: "max-h-48 overflow-y-auto break-words text-base leading-relaxed", children: [
139
171
  transcript,
140
172
  interim && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
141
173
  transcript ? " " : "",
142
174
  interim
143
175
  ] })
144
176
  ] }),
145
- error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", children: error }),
146
- !error && capture.previewError && /* @__PURE__ */ jsx("p", { role: "status", className: "text-xs text-muted-foreground", children: capture.previewError })
177
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm leading-relaxed text-destructive", children: error }),
178
+ !error && capture.previewError && /* @__PURE__ */ jsx("p", { role: "status", className: "text-sm leading-relaxed text-muted-foreground", children: capture.previewError })
147
179
  ] }),
148
- /* @__PURE__ */ jsxs("div", { className: cn("gap-2", anchored ? "flex flex-wrap items-center justify-end" : "mt-auto grid w-full max-w-xl grid-cols-2 gap-3 self-center"), children: [
149
- /* @__PURE__ */ jsx(Button, { ref: cancelControl, type: "button", variant: "ghost", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: handleCancel, children: "Cancel" }),
150
- capture.canRetry && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", className: anchored ? "min-h-[44px]" : "order-first col-span-2 min-h-[48px]", onClick: () => {
180
+ /* @__PURE__ */ jsxs("div", { className: cn("shrink-0 gap-3 pt-2", anchored ? "flex flex-wrap items-center justify-end" : "mt-auto grid w-full max-w-xl grid-cols-[auto_minmax(0,1fr)] self-center"), children: [
181
+ /* @__PURE__ */ jsx(Button, { ref: cancelControl, type: "button", variant: "ghost", className: "min-h-[48px] px-5", onClick: handleCancel, children: "Cancel" }),
182
+ capture.canRetry && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", className: cn("min-h-[48px] px-5", !anchored && "order-first col-span-2"), onClick: () => {
151
183
  cancelControl.current?.focus({ preventScroll: true });
152
184
  void capture.start();
153
185
  }, children: "Record again" }),
154
- /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: () => void handleDone(), disabled: busy, children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done" })
186
+ /* @__PURE__ */ jsx(Button, { type: "button", className: "min-h-[48px] min-w-24 px-6", onClick: () => void handleDone(), disabled: busy, children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done" })
155
187
  ] })
156
188
  ] });
157
189
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -195,13 +227,13 @@ function VoiceDictationButton({
195
227
  "data-placement": "anchored",
196
228
  side: "top",
197
229
  align: "center",
198
- sideOffset: 8,
230
+ sideOffset: 12,
199
231
  collisionPadding: 12,
200
232
  avoidCollisions: true,
201
233
  onCloseAutoFocus: closeAutoFocus,
202
234
  onKeyDown,
203
235
  onEscapeKeyDown: (event) => event.stopPropagation(),
204
- className: "z-[200] flex max-h-[var(--radix-popover-content-available-height)] w-96 max-w-[calc(100vw-1.5rem)] flex-col gap-3 overflow-y-auto rounded-2xl border border-border bg-background p-4 text-foreground shadow-lg outline-hidden",
236
+ className: "z-[200] flex max-h-[var(--radix-popover-content-available-height)] w-[560px] max-w-[calc(100vw-24px)] flex-col gap-6 overflow-y-auto rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-xl outline-hidden",
205
237
  children: content
206
238
  }
207
239
  ) })
@@ -221,7 +253,7 @@ function VoiceDictationButton({
221
253
  onCloseAutoFocus: closeAutoFocus,
222
254
  onKeyDown,
223
255
  onEscapeKeyDown: (event) => event.stopPropagation(),
224
- className: "fixed inset-0 z-[200] flex h-dvh w-screen flex-col gap-6 overflow-y-auto overscroll-contain bg-background px-6 pt-[max(1.5rem,env(safe-area-inset-top))] pb-[max(1.5rem,env(safe-area-inset-bottom))] text-foreground outline-hidden",
256
+ className: "fixed inset-0 z-[200] flex h-dvh w-screen flex-col gap-6 overflow-y-auto overscroll-contain bg-popover px-5 pt-[max(1.5rem,env(safe-area-inset-top))] pb-[max(1.5rem,env(safe-area-inset-bottom))] text-popover-foreground outline-hidden",
225
257
  children: [
226
258
  /* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "sr-only", children: "Voice dictation" }),
227
259
  /* @__PURE__ */ jsx(DialogPrimitive.Description, { className: "sr-only", children: "Record your message, then choose Done to add the text. Cancel discards the recording." }),
@@ -234,7 +266,7 @@ function VoiceDictationButton({
234
266
  }
235
267
  var SILENCE = [];
236
268
  var silence = () => SILENCE;
237
- function DictationWaveform({ source, state, className }) {
269
+ function DictationWaveform({ source, state }) {
238
270
  const bands = useSyncExternalStore(source.subscribe, source.getSnapshot, silence);
239
271
  return /* @__PURE__ */ jsx(
240
272
  AgentAudioVisualizerBar,
@@ -245,7 +277,8 @@ function DictationWaveform({ source, state, className }) {
245
277
  barCount: 24,
246
278
  state: state === "recording" ? "listening" : state === "transcribing" ? "thinking" : state === "requesting" ? "connecting" : "disconnected",
247
279
  volumeBands: state === "recording" ? bands : void 0,
248
- className: cn("h-9 w-full text-foreground", className)
280
+ className: "h-[64px] min-w-0 flex-1 gap-[3px] sm:h-[88px] sm:gap-[4px]",
281
+ children: /* @__PURE__ */ jsx("div", { className: "min-h-[4px] max-w-[6px] flex-1 rounded-full bg-current/15 transition-colors duration-200 motion-reduce:transition-none data-[lk-highlighted=true]:bg-current" })
249
282
  }
250
283
  );
251
284
  }
@@ -7,7 +7,7 @@
7
7
  var _chunk2BTDBZYNcjs = require('../chunk-2BTDBZYN.cjs');
8
8
 
9
9
 
10
- var _chunkQWUMIAYDcjs = require('../chunk-QWUMIAYD.cjs');
10
+ var _chunkPH2TV7XIcjs = require('../chunk-PH2TV7XI.cjs');
11
11
 
12
12
 
13
13
  var _chunkYMI3X2YLcjs = require('../chunk-YMI3X2YL.cjs');
@@ -545,7 +545,7 @@ function ChatInput({
545
545
  ) : null,
546
546
  (!helperText || compact) && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex-1" }),
547
547
  voice && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
548
- _chunkQWUMIAYDcjs.VoiceDictationButton,
548
+ _chunkPH2TV7XIcjs.VoiceDictationButton,
549
549
  {
550
550
  size: "icon",
551
551
  title: voiceTitle,
@@ -623,7 +623,7 @@ function VoiceHero({
623
623
  voiceSession ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
624
624
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkYMI3X2YLcjs.VoiceSessionControls, { ...voiceSession, size: "hero" }),
625
625
  voice && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
626
- _chunkQWUMIAYDcjs.VoiceDictationButton,
626
+ _chunkPH2TV7XIcjs.VoiceDictationButton,
627
627
  {
628
628
  size: "icon",
629
629
  title: voiceTitle,
@@ -633,7 +633,7 @@ function VoiceHero({
633
633
  }
634
634
  )
635
635
  ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
636
- _chunkQWUMIAYDcjs.VoiceDictationButton,
636
+ _chunkPH2TV7XIcjs.VoiceDictationButton,
637
637
  {
638
638
  size: "lg",
639
639
  title: voiceTitle,
@@ -7,7 +7,7 @@ import {
7
7
  } from "../chunk-Z45YKVAM.js";
8
8
  import {
9
9
  VoiceDictationButton
10
- } from "../chunk-JKVVTFAP.js";
10
+ } from "../chunk-ZGTPNSR7.js";
11
11
  import {
12
12
  VoiceSessionControls
13
13
  } from "../chunk-KF4X37DP.js";
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
 
4
4
 
5
- var _chunkQWUMIAYDcjs = require('../chunk-QWUMIAYD.cjs');
5
+ var _chunkPH2TV7XIcjs = require('../chunk-PH2TV7XI.cjs');
6
6
  require('../chunk-NKWHDI4C.cjs');
7
7
  require('../chunk-JDEXZGIY.cjs');
8
8
  require('../chunk-KWLSBTB2.cjs');
@@ -10,4 +10,4 @@ require('../chunk-2BSFYU22.cjs');
10
10
  require('../chunk-7Q4EFCUI.cjs');
11
11
 
12
12
 
13
- exports.VoiceDictationButton = _chunkQWUMIAYDcjs.VoiceDictationButton;
13
+ exports.VoiceDictationButton = _chunkPH2TV7XIcjs.VoiceDictationButton;
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
  import {
4
4
  VoiceDictationButton
5
- } from "../chunk-JKVVTFAP.js";
5
+ } from "../chunk-ZGTPNSR7.js";
6
6
  import "../chunk-5OF6D3GH.js";
7
7
  import "../chunk-W326GKML.js";
8
8
  import "../chunk-G6SRSWXB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicbagel/saas-kit",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Accessible React primitives, layout components and design tokens for SaaS apps, built on Radix UI and Tailwind CSS v4.",
5
5
  "license": "MIT",
6
6
  "keywords": [