@arbidocs/react 0.3.173 → 0.3.174

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.
@@ -442,6 +442,86 @@ var TooltipContent = React2__namespace.forwardRef(
442
442
  }
443
443
  );
444
444
  TooltipContent.displayName = "TooltipContent";
445
+ function IconToggle({
446
+ icon: Icon2,
447
+ isActive,
448
+ onToggle,
449
+ colors,
450
+ fillWhenActive = false,
451
+ disabled = false,
452
+ disabledTooltip,
453
+ showLabel = false,
454
+ label,
455
+ tooltipActive,
456
+ tooltipInactive,
457
+ tooltipMixed,
458
+ isMixed = false,
459
+ showTooltip = true,
460
+ "data-testid": dataTestId
461
+ }) {
462
+ const handleClick = (e) => {
463
+ e.stopPropagation();
464
+ onToggle(!isActive);
465
+ };
466
+ const { bg: bgColorClass } = colors;
467
+ const tooltipText = disabled ? disabledTooltip : isMixed ? tooltipMixed : isActive ? tooltipActive : tooltipInactive;
468
+ const { text: textColorClass } = colors;
469
+ const button = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
470
+ "button",
471
+ {
472
+ type: "button",
473
+ onClick: handleClick,
474
+ disabled,
475
+ "data-testid": dataTestId,
476
+ "aria-pressed": isMixed ? "mixed" : isActive,
477
+ className: `
478
+ group/toggle relative inline-flex items-center h-4 w-8 rounded-full
479
+ transition-colors cursor-pointer
480
+ disabled:opacity-50 disabled:cursor-not-allowed
481
+ ${isActive ? bgColorClass : isMixed ? "bg-gray-400" : "bg-muted"}
482
+ `,
483
+ children: [
484
+ isActive && !disabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 w-4 h-4 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "w-2.5 h-2.5 text-white/70 hidden group-hover/toggle:block" }) }),
485
+ !isActive && !disabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 w-4 h-4 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-2.5 h-2.5 text-muted-foreground hidden group-hover/toggle:block" }) }),
486
+ /* @__PURE__ */ jsxRuntime.jsx(
487
+ "div",
488
+ {
489
+ className: `
490
+ absolute h-4 w-4 rounded-full bg-white shadow-sm
491
+ flex items-center justify-center
492
+ transition-transform duration-200
493
+ ${isActive ? "translate-x-4" : "translate-x-0"}
494
+ `,
495
+ children: /* @__PURE__ */ jsxRuntime.jsx(
496
+ Icon2,
497
+ {
498
+ className: `w-2.5 h-2.5 ${isActive ? textColorClass : "text-muted-foreground"} ${isActive && fillWhenActive ? "fill-current" : ""}`
499
+ }
500
+ )
501
+ }
502
+ )
503
+ ]
504
+ }
505
+ ) });
506
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-2", children: [
507
+ showLabel && label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm text-primary", children: label }),
508
+ tooltipText && showTooltip ? /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, onFocusCapture: (e) => e.stopPropagation(), children: button }),
510
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: tooltipText }) })
511
+ ] }) : button
512
+ ] });
513
+ }
514
+ var DictateContext = React2.createContext(null);
515
+ function DictateProvider({
516
+ transcribe,
517
+ onError,
518
+ children
519
+ }) {
520
+ return /* @__PURE__ */ jsxRuntime.jsx(DictateContext.Provider, { value: { transcribe, onError }, children });
521
+ }
522
+ function useDictateContext() {
523
+ return React2.useContext(DictateContext);
524
+ }
445
525
  var Avatar = React2__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
446
526
  AvatarPrimitive__namespace.Root,
447
527
  {
@@ -617,21 +697,379 @@ var CardFooter = React2__namespace.forwardRef(
617
697
  )
618
698
  );
619
699
  CardFooter.displayName = "CardFooter";
700
+ var LEVEL_UPDATE_INTERVAL_MS = 50;
701
+ function classifyMicError(err) {
702
+ const name = err instanceof DOMException ? err.name : void 0;
703
+ if (name === "NotAllowedError" || name === "SecurityError") {
704
+ return "Microphone access is blocked. Allow microphone permission in your browser settings to use voice input.";
705
+ }
706
+ if (name === "NotFoundError" || name === "DevicesNotFoundError") {
707
+ return "No microphone found on this device.";
708
+ }
709
+ if (!navigator.mediaDevices?.getUserMedia) {
710
+ return "Voice input is not supported in this browser.";
711
+ }
712
+ return "Could not start recording. Please try again.";
713
+ }
714
+ function useAudioRecorder() {
715
+ const [state, setState] = React2.useState("idle");
716
+ const [level, setLevel] = React2.useState(0);
717
+ const [elapsedMs, setElapsedMs] = React2.useState(0);
718
+ const [blob, setBlob] = React2.useState(null);
719
+ const [mimeType, setMimeType] = React2.useState(null);
720
+ const [error, setError] = React2.useState(null);
721
+ const mediaRecorderRef = React2.useRef(null);
722
+ const chunksRef = React2.useRef([]);
723
+ const streamRef = React2.useRef(null);
724
+ const stateRef = React2.useRef("idle");
725
+ stateRef.current = state;
726
+ const segmentStartRef = React2.useRef(0);
727
+ const accumulatedMsRef = React2.useRef(0);
728
+ const audioContextRef = React2.useRef(null);
729
+ const analyserRef = React2.useRef(null);
730
+ const levelRafRef = React2.useRef(null);
731
+ const stopMeter = React2.useCallback(() => {
732
+ if (levelRafRef.current !== null) cancelAnimationFrame(levelRafRef.current);
733
+ levelRafRef.current = null;
734
+ analyserRef.current = null;
735
+ void audioContextRef.current?.close().catch(() => {
736
+ });
737
+ audioContextRef.current = null;
738
+ }, []);
739
+ const startMeter = React2.useCallback((stream) => {
740
+ const audioContext = new AudioContext();
741
+ const source = audioContext.createMediaStreamSource(stream);
742
+ const analyser = audioContext.createAnalyser();
743
+ analyser.fftSize = 256;
744
+ source.connect(analyser);
745
+ audioContextRef.current = audioContext;
746
+ analyserRef.current = analyser;
747
+ const data = new Uint8Array(analyser.frequencyBinCount);
748
+ let lastUpdate = 0;
749
+ const tick = (t) => {
750
+ const active = analyserRef.current;
751
+ if (!active) return;
752
+ if (t - lastUpdate >= LEVEL_UPDATE_INTERVAL_MS) {
753
+ lastUpdate = t;
754
+ active.getByteTimeDomainData(data);
755
+ let sumSquares = 0;
756
+ for (let i = 0; i < data.length; i++) {
757
+ const centered = (data[i] - 128) / 128;
758
+ sumSquares += centered * centered;
759
+ }
760
+ const rms = Math.sqrt(sumSquares / data.length);
761
+ setLevel(Math.sqrt(Math.min(1, rms * 8)));
762
+ setElapsedMs(accumulatedMsRef.current + (t - segmentStartRef.current));
763
+ }
764
+ levelRafRef.current = requestAnimationFrame(tick);
765
+ };
766
+ levelRafRef.current = requestAnimationFrame(tick);
767
+ }, []);
768
+ const stopTracks = React2.useCallback(() => {
769
+ streamRef.current?.getTracks().forEach((t) => t.stop());
770
+ streamRef.current = null;
771
+ }, []);
772
+ React2.useEffect(
773
+ () => () => {
774
+ stopTracks();
775
+ stopMeter();
776
+ },
777
+ [stopTracks, stopMeter]
778
+ );
779
+ const start = React2.useCallback(async () => {
780
+ if (stateRef.current === "recording" || stateRef.current === "paused") return;
781
+ setError(null);
782
+ try {
783
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
784
+ streamRef.current = stream;
785
+ const mt = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
786
+ const recorder = new MediaRecorder(stream, { mimeType: mt });
787
+ chunksRef.current = [];
788
+ recorder.ondataavailable = (e) => {
789
+ if (e.data.size > 0) chunksRef.current.push(e.data);
790
+ };
791
+ mediaRecorderRef.current = recorder;
792
+ setMimeType(mt);
793
+ setBlob(null);
794
+ accumulatedMsRef.current = 0;
795
+ segmentStartRef.current = performance.now();
796
+ recorder.start();
797
+ setState("recording");
798
+ startMeter(stream);
799
+ } catch (err) {
800
+ stopTracks();
801
+ setError(classifyMicError(err));
802
+ }
803
+ }, [startMeter, stopTracks]);
804
+ const pause = React2.useCallback(() => {
805
+ const recorder = mediaRecorderRef.current;
806
+ if (!recorder || recorder.state !== "recording") return;
807
+ recorder.pause();
808
+ accumulatedMsRef.current += performance.now() - segmentStartRef.current;
809
+ stopMeter();
810
+ setLevel(0);
811
+ setState("paused");
812
+ }, [stopMeter]);
813
+ const resume = React2.useCallback(() => {
814
+ const recorder = mediaRecorderRef.current;
815
+ if (!recorder || recorder.state !== "paused") return;
816
+ recorder.resume();
817
+ segmentStartRef.current = performance.now();
818
+ setState("recording");
819
+ if (streamRef.current) startMeter(streamRef.current);
820
+ }, [startMeter]);
821
+ const stop = React2.useCallback(() => {
822
+ const recorder = mediaRecorderRef.current;
823
+ if (!recorder || recorder.state === "inactive") return;
824
+ const finalElapsed = recorder.state === "paused" ? accumulatedMsRef.current : accumulatedMsRef.current + (performance.now() - segmentStartRef.current);
825
+ stopMeter();
826
+ recorder.onstop = () => {
827
+ stopTracks();
828
+ const finalBlob = chunksRef.current.length ? new Blob(chunksRef.current, { type: recorder.mimeType }) : null;
829
+ setBlob(finalBlob);
830
+ setElapsedMs(finalElapsed);
831
+ setLevel(0);
832
+ setState("stopped");
833
+ };
834
+ recorder.stop();
835
+ }, [stopMeter, stopTracks]);
836
+ const reset = React2.useCallback(() => {
837
+ setState("idle");
838
+ setBlob(null);
839
+ setElapsedMs(0);
840
+ setLevel(0);
841
+ setMimeType(null);
842
+ setError(null);
843
+ }, []);
844
+ return { state, level, elapsedMs, blob, mimeType, error, start, pause, resume, stop, reset };
845
+ }
846
+
847
+ // src/documents/format-elapsed.ts
848
+ function formatElapsed(ms) {
849
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
850
+ const minutes = Math.floor(totalSeconds / 60);
851
+ const seconds = totalSeconds % 60;
852
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
853
+ }
854
+ var BAR_WEIGHTS = [
855
+ 0.25,
856
+ 0.32,
857
+ 0.48,
858
+ 0.62,
859
+ 0.82,
860
+ 0.56,
861
+ 0.92,
862
+ 0.72,
863
+ 1,
864
+ 0.86,
865
+ 0.62,
866
+ 0.96,
867
+ 0.76,
868
+ 0.5,
869
+ 0.84,
870
+ 0.66,
871
+ 0.42,
872
+ 0.56,
873
+ 0.36,
874
+ 0.28,
875
+ 0.22
876
+ ];
877
+ var MIN_BAR_PX = 3;
878
+ var MAX_BAR_PX = 40;
879
+ function MicWaveformIndicator({
880
+ level,
881
+ elapsedMs,
882
+ paused = false
883
+ }) {
884
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", "data-testid": "mic-waveform-indicator", children: [
885
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
886
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2", children: [
887
+ !paused && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-75" }),
888
+ /* @__PURE__ */ jsxRuntime.jsx(
889
+ "span",
890
+ {
891
+ className: `relative inline-flex h-2 w-2 rounded-full ${paused ? "bg-muted-foreground" : "bg-red-500"}`
892
+ }
893
+ )
894
+ ] }),
895
+ /* @__PURE__ */ jsxRuntime.jsx(
896
+ "span",
897
+ {
898
+ className: `text-xs font-semibold uppercase tracking-wide ${paused ? "text-muted-foreground" : "text-red-500"}`,
899
+ children: paused ? "Paused" : "Rec"
900
+ }
901
+ ),
902
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs tabular-nums text-muted-foreground", children: formatElapsed(elapsedMs) })
903
+ ] }),
904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 items-end gap-[3px]", "data-testid": "mic-waveform-indicator-bars", children: BAR_WEIGHTS.map((weight, i) => /* @__PURE__ */ jsxRuntime.jsx(
905
+ "span",
906
+ {
907
+ className: "w-1 rounded-full bg-secondary transition-[height] duration-75 ease-out",
908
+ style: {
909
+ height: Math.min(MAX_BAR_PX, Math.max(MIN_BAR_PX, level * weight * MAX_BAR_PX))
910
+ }
911
+ },
912
+ i
913
+ )) })
914
+ ] });
915
+ }
916
+ function MicRecordingOverlay({
917
+ level,
918
+ elapsedMs,
919
+ paused,
920
+ caption = "Keep button pressed while speaking"
921
+ }) {
922
+ if (typeof document === "undefined") return null;
923
+ return reactDom.createPortal(
924
+ /* @__PURE__ */ jsxRuntime.jsx(
925
+ "div",
926
+ {
927
+ "aria-hidden": true,
928
+ className: "pointer-events-none fixed inset-0 z-[2147483647] flex items-center justify-center",
929
+ "data-testid": "mic-recording-overlay",
930
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-72 flex-col items-center gap-3 rounded-2xl bg-background/95 px-6 py-5 shadow-2xl ring-1 ring-border backdrop-blur-sm", children: [
931
+ /* @__PURE__ */ jsxRuntime.jsx(MicWaveformIndicator, { level, elapsedMs, paused }),
932
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center text-sm font-medium text-muted-foreground", children: caption })
933
+ ] })
934
+ }
935
+ ),
936
+ document.body
937
+ );
938
+ }
939
+ var HOLD_THRESHOLD_MS = 350;
940
+ function InlineMicButton({
941
+ focused,
942
+ onTranscript,
943
+ onError,
944
+ transcribe,
945
+ className
946
+ }) {
947
+ const recorder = useAudioRecorder();
948
+ const [isTranscribing, setIsTranscribing] = React2.useState(false);
949
+ const [latched, setLatched] = React2.useState(false);
950
+ const pressedAtRef = React2.useRef(0);
951
+ React2.useEffect(() => {
952
+ if (recorder.error) onError?.(recorder.error);
953
+ }, [recorder.error]);
954
+ React2.useEffect(() => {
955
+ if (recorder.state !== "stopped" || !recorder.blob) return;
956
+ const blob = recorder.blob;
957
+ setIsTranscribing(true);
958
+ transcribe(blob).then((text) => {
959
+ if (text.trim()) onTranscript(text.trim());
960
+ }).catch((err) => {
961
+ onError?.(err instanceof Error ? err.message : "Could not transcribe audio.");
962
+ }).finally(() => {
963
+ setIsTranscribing(false);
964
+ setLatched(false);
965
+ recorder.reset();
966
+ });
967
+ }, [recorder.state, recorder.blob]);
968
+ const isRecording = recorder.state === "recording";
969
+ const isBusy = isTranscribing;
970
+ if (!focused && !isRecording && !isBusy) return null;
971
+ const handlePointerDown = (e) => {
972
+ e.preventDefault();
973
+ if (isRecording) return;
974
+ pressedAtRef.current = Date.now();
975
+ void recorder.start();
976
+ };
977
+ const handlePointerUp = () => {
978
+ if (!isRecording) return;
979
+ if (latched) {
980
+ recorder.stop();
981
+ setLatched(false);
982
+ return;
983
+ }
984
+ const held = Date.now() - pressedAtRef.current;
985
+ if (held >= HOLD_THRESHOLD_MS) {
986
+ recorder.stop();
987
+ } else {
988
+ setLatched(true);
989
+ }
990
+ };
991
+ const handlePointerLeave = () => {
992
+ if (isRecording && !latched) recorder.stop();
993
+ };
994
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
995
+ /* @__PURE__ */ jsxRuntime.jsx(
996
+ "button",
997
+ {
998
+ type: "button",
999
+ onPointerDown: handlePointerDown,
1000
+ onPointerUp: handlePointerUp,
1001
+ onPointerLeave: handlePointerLeave,
1002
+ onPointerCancel: handlePointerLeave,
1003
+ disabled: isBusy,
1004
+ title: isRecording ? "Release to stop, or tap again if hands-free" : "Hold, or tap to dictate",
1005
+ "data-testid": "inline-mic-button",
1006
+ className: `inline-flex items-center justify-center rounded-full transition-colors disabled:pointer-events-none ${isRecording ? "h-7 w-7 bg-red-500 text-white shadow-sm hover:bg-red-600" : isBusy ? "h-6 w-6 text-muted-foreground" : "h-6 w-6 text-muted-foreground/50 hover:bg-muted hover:text-muted-foreground"} ${className ?? ""}`,
1007
+ children: isBusy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : isRecording ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { className: "h-3 w-3 fill-current" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-3.5 w-3.5" })
1008
+ }
1009
+ ),
1010
+ isRecording && /* @__PURE__ */ jsxRuntime.jsx(
1011
+ MicRecordingOverlay,
1012
+ {
1013
+ level: recorder.level,
1014
+ elapsedMs: recorder.elapsedMs,
1015
+ caption: latched ? "Tap the mic again to stop" : "Release to stop, or keep holding"
1016
+ }
1017
+ )
1018
+ ] });
1019
+ }
620
1020
  var Input = React2__namespace.forwardRef(
621
- ({ className, type, ...props }, ref) => {
622
- return /* @__PURE__ */ jsxRuntime.jsx(
1021
+ ({ className, type, dictate, onFocus, onBlur, ...props }, forwardedRef) => {
1022
+ const dictateCtx = useDictateContext();
1023
+ const [focused, setFocused] = React2__namespace.useState(false);
1024
+ const innerRef = React2__namespace.useRef(null);
1025
+ React2__namespace.useImperativeHandle(forwardedRef, () => innerRef.current);
1026
+ const showDictate = dictate && !!dictateCtx && !props.disabled && !props.readOnly;
1027
+ const input = /* @__PURE__ */ jsxRuntime.jsx(
623
1028
  "input",
624
1029
  {
625
1030
  type,
626
1031
  "data-slot": "input",
627
1032
  className: cn(
628
1033
  "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground/60 focus-visible:outline-none focus-visible:border-ring/50 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1034
+ showDictate && "pr-9",
629
1035
  className
630
1036
  ),
631
- ref,
1037
+ ref: innerRef,
1038
+ onFocus: (e) => {
1039
+ setFocused(true);
1040
+ onFocus?.(e);
1041
+ },
1042
+ onBlur: (e) => {
1043
+ setFocused(false);
1044
+ onBlur?.(e);
1045
+ },
632
1046
  ...props
633
1047
  }
634
1048
  );
1049
+ if (!showDictate) return input;
1050
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
1051
+ input,
1052
+ /* @__PURE__ */ jsxRuntime.jsx(
1053
+ InlineMicButton,
1054
+ {
1055
+ focused,
1056
+ transcribe: dictateCtx.transcribe,
1057
+ onError: dictateCtx.onError,
1058
+ className: "absolute right-1.5 top-1/2 -translate-y-1/2",
1059
+ onTranscript: (text) => {
1060
+ const el = innerRef.current;
1061
+ if (!el) return;
1062
+ const next = el.value ? `${el.value} ${text}` : text;
1063
+ const setter = Object.getOwnPropertyDescriptor(
1064
+ window.HTMLInputElement.prototype,
1065
+ "value"
1066
+ )?.set;
1067
+ setter?.call(el, next);
1068
+ el.dispatchEvent(new Event("input", { bubbles: true }));
1069
+ }
1070
+ }
1071
+ )
1072
+ ] });
635
1073
  }
636
1074
  );
637
1075
  Input.displayName = "Input";
@@ -660,18 +1098,56 @@ var Separator = React2__namespace.forwardRef(({ className, orientation = "horizo
660
1098
  ));
661
1099
  Separator.displayName = SeparatorPrimitive__namespace.Root.displayName;
662
1100
  var Textarea = React2__namespace.forwardRef(
663
- ({ className, ...props }, ref) => {
664
- return /* @__PURE__ */ jsxRuntime.jsx(
1101
+ ({ className, dictate, onFocus, onBlur, ...props }, forwardedRef) => {
1102
+ const dictateCtx = useDictateContext();
1103
+ const [focused, setFocused] = React2__namespace.useState(false);
1104
+ const innerRef = React2__namespace.useRef(null);
1105
+ React2__namespace.useImperativeHandle(forwardedRef, () => innerRef.current);
1106
+ const showDictate = dictate && !!dictateCtx && !props.disabled && !props.readOnly;
1107
+ const textarea = /* @__PURE__ */ jsxRuntime.jsx(
665
1108
  "textarea",
666
1109
  {
667
1110
  className: cn(
668
1111
  "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground/60 focus-visible:outline-none focus-visible:border-ring/50 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1112
+ showDictate && "pr-9",
669
1113
  className
670
1114
  ),
671
- ref,
1115
+ ref: innerRef,
1116
+ onFocus: (e) => {
1117
+ setFocused(true);
1118
+ onFocus?.(e);
1119
+ },
1120
+ onBlur: (e) => {
1121
+ setFocused(false);
1122
+ onBlur?.(e);
1123
+ },
672
1124
  ...props
673
1125
  }
674
1126
  );
1127
+ if (!showDictate) return textarea;
1128
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full", children: [
1129
+ textarea,
1130
+ /* @__PURE__ */ jsxRuntime.jsx(
1131
+ InlineMicButton,
1132
+ {
1133
+ focused,
1134
+ transcribe: dictateCtx.transcribe,
1135
+ onError: dictateCtx.onError,
1136
+ className: "absolute right-1.5 top-1.5",
1137
+ onTranscript: (text) => {
1138
+ const el = innerRef.current;
1139
+ if (!el) return;
1140
+ const next = el.value ? `${el.value} ${text}` : text;
1141
+ const setter = Object.getOwnPropertyDescriptor(
1142
+ window.HTMLTextAreaElement.prototype,
1143
+ "value"
1144
+ )?.set;
1145
+ setter?.call(el, next);
1146
+ el.dispatchEvent(new Event("input", { bubbles: true }));
1147
+ }
1148
+ }
1149
+ )
1150
+ ] });
675
1151
  }
676
1152
  );
677
1153
  Textarea.displayName = "Textarea";
@@ -1411,6 +1887,7 @@ exports.DialogOverlay = DialogOverlay;
1411
1887
  exports.DialogPortal = DialogPortal;
1412
1888
  exports.DialogTitle = DialogTitle;
1413
1889
  exports.DialogTrigger = DialogTrigger;
1890
+ exports.DictateProvider = DictateProvider;
1414
1891
  exports.DropdownMenu = DropdownMenu;
1415
1892
  exports.DropdownMenuCheckboxItem = DropdownMenuCheckboxItem;
1416
1893
  exports.DropdownMenuContent = DropdownMenuContent;
@@ -1429,10 +1906,14 @@ exports.DropdownMenuTrigger = DropdownMenuTrigger;
1429
1906
  exports.HoverCard = HoverCard;
1430
1907
  exports.HoverCardContent = HoverCardContent;
1431
1908
  exports.HoverCardTrigger = HoverCardTrigger;
1909
+ exports.IconToggle = IconToggle;
1910
+ exports.InlineMicButton = InlineMicButton;
1432
1911
  exports.Input = Input;
1433
1912
  exports.Label = Label;
1434
1913
  exports.MarkdownRenderer = MarkdownRenderer;
1435
1914
  exports.MarkdownStreamingContext = MarkdownStreamingContext;
1915
+ exports.MicRecordingOverlay = MicRecordingOverlay;
1916
+ exports.MicWaveformIndicator = MicWaveformIndicator;
1436
1917
  exports.Popover = Popover;
1437
1918
  exports.PopoverAnchor = PopoverAnchor;
1438
1919
  exports.PopoverContent = PopoverContent;
@@ -1479,8 +1960,10 @@ exports.badgeVariants = badgeVariants;
1479
1960
  exports.buttonVariants = buttonVariants;
1480
1961
  exports.cardVariants = cardVariants;
1481
1962
  exports.cn = cn;
1963
+ exports.formatElapsed = formatElapsed;
1482
1964
  exports.markdownBaseComponents = markdownBaseComponents;
1483
1965
  exports.splitMarkdownBlocks = splitMarkdownBlocks;
1484
1966
  exports.toggleVariants = toggleVariants;
1485
- //# sourceMappingURL=chunk-5DV7FW6T.cjs.map
1486
- //# sourceMappingURL=chunk-5DV7FW6T.cjs.map
1967
+ exports.useAudioRecorder = useAudioRecorder;
1968
+ //# sourceMappingURL=chunk-ZYHVJM4V.cjs.map
1969
+ //# sourceMappingURL=chunk-ZYHVJM4V.cjs.map