@zentauri-ui/zentauri-components 2.3.0 → 2.3.2

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.
Files changed (243) hide show
  1. package/README.md +9 -5
  2. package/cli/props.json +2508 -1458
  3. package/cli/registry.json +20 -0
  4. package/dist/chunk-2RBBXVFA.js +19 -0
  5. package/dist/{chunk-ENKXB2BA.js.map → chunk-2RBBXVFA.js.map} +1 -1
  6. package/dist/{chunk-BFHJF4MV.mjs → chunk-425DAZTS.mjs} +4 -4
  7. package/dist/{chunk-BFHJF4MV.mjs.map → chunk-425DAZTS.mjs.map} +1 -1
  8. package/dist/chunk-4U6PVVST.mjs +15 -0
  9. package/dist/chunk-4U6PVVST.mjs.map +1 -0
  10. package/dist/{chunk-AARJLZXP.js → chunk-6GP74P4F.js} +6 -6
  11. package/dist/{chunk-AARJLZXP.js.map → chunk-6GP74P4F.js.map} +1 -1
  12. package/dist/{chunk-DIAA5VH4.mjs → chunk-CIZQQ32L.mjs} +3 -3
  13. package/dist/chunk-CIZQQ32L.mjs.map +1 -0
  14. package/dist/chunk-DUKHIN2W.js +44 -0
  15. package/dist/chunk-DUKHIN2W.js.map +1 -0
  16. package/dist/{chunk-PQ2XTY3M.js → chunk-G36ZV446.js} +13 -13
  17. package/dist/{chunk-PQ2XTY3M.js.map → chunk-G36ZV446.js.map} +1 -1
  18. package/dist/chunk-HJ7EFBED.js +86 -0
  19. package/dist/chunk-HJ7EFBED.js.map +1 -0
  20. package/dist/{chunk-H3BJOK22.js → chunk-HNAUUCR5.js} +3 -3
  21. package/dist/chunk-HNAUUCR5.js.map +1 -0
  22. package/dist/chunk-K2VCZK4I.mjs +75 -0
  23. package/dist/chunk-K2VCZK4I.mjs.map +1 -0
  24. package/dist/{chunk-DSX6RUYI.js → chunk-KVBFWRPF.js} +12 -12
  25. package/dist/{chunk-DSX6RUYI.js.map → chunk-KVBFWRPF.js.map} +1 -1
  26. package/dist/chunk-N4MLFU2Q.mjs +69 -0
  27. package/dist/chunk-N4MLFU2Q.mjs.map +1 -0
  28. package/dist/{chunk-ATE5SCTR.mjs → chunk-N6Q4ZLQR.mjs} +3 -3
  29. package/dist/{chunk-ATE5SCTR.mjs.map → chunk-N6Q4ZLQR.mjs.map} +1 -1
  30. package/dist/{chunk-YY7G4NV3.js → chunk-NZQA4M35.js} +49 -17
  31. package/dist/chunk-NZQA4M35.js.map +1 -0
  32. package/dist/chunk-OME7XOPN.js +78 -0
  33. package/dist/chunk-OME7XOPN.js.map +1 -0
  34. package/dist/chunk-PVDWJUMF.mjs +34 -0
  35. package/dist/chunk-PVDWJUMF.mjs.map +1 -0
  36. package/dist/chunk-PVVYOIU2.js +38 -0
  37. package/dist/chunk-PVVYOIU2.js.map +1 -0
  38. package/dist/chunk-PWEZB53R.js +90 -0
  39. package/dist/chunk-PWEZB53R.js.map +1 -0
  40. package/dist/chunk-PWYEC3KY.mjs +30 -0
  41. package/dist/chunk-PWYEC3KY.mjs.map +1 -0
  42. package/dist/{chunk-JKKF5DCF.mjs → chunk-R2JXARKB.mjs} +3 -3
  43. package/dist/{chunk-JKKF5DCF.mjs.map → chunk-R2JXARKB.mjs.map} +1 -1
  44. package/dist/chunk-SD7YXMNV.js +40 -0
  45. package/dist/chunk-SD7YXMNV.js.map +1 -0
  46. package/dist/{chunk-ZB6C6CJQ.mjs → chunk-TZTUL6C4.mjs} +40 -8
  47. package/dist/chunk-TZTUL6C4.mjs.map +1 -0
  48. package/dist/chunk-UBQY572I.mjs +81 -0
  49. package/dist/chunk-UBQY572I.mjs.map +1 -0
  50. package/dist/chunk-V5EE5ATH.mjs +36 -0
  51. package/dist/chunk-V5EE5ATH.mjs.map +1 -0
  52. package/dist/chunk-XUK5P37Y.js +19 -0
  53. package/dist/chunk-XUK5P37Y.js.map +1 -0
  54. package/dist/{chunk-WZY32L6K.mjs → chunk-YDD5HQGX.mjs} +3 -3
  55. package/dist/{chunk-WZY32L6K.mjs.map → chunk-YDD5HQGX.mjs.map} +1 -1
  56. package/dist/design-system/facade.js +12 -8
  57. package/dist/design-system/facade.js.map +1 -1
  58. package/dist/design-system/facade.mjs +11 -7
  59. package/dist/design-system/facade.mjs.map +1 -1
  60. package/dist/design-system/index.d.ts +4 -0
  61. package/dist/design-system/index.d.ts.map +1 -1
  62. package/dist/design-system/qr-code.d.ts +4 -0
  63. package/dist/design-system/qr-code.d.ts.map +1 -0
  64. package/dist/design-system/qr-scanner.d.ts +11 -0
  65. package/dist/design-system/qr-scanner.d.ts.map +1 -0
  66. package/dist/design-system/secret-reveal.d.ts +1 -1
  67. package/dist/design-system/secret-reveal.d.ts.map +1 -1
  68. package/dist/design-system/speech-recognition.d.ts +39 -0
  69. package/dist/design-system/speech-recognition.d.ts.map +1 -0
  70. package/dist/design-system/speech-synthesizer.d.ts +41 -0
  71. package/dist/design-system/speech-synthesizer.d.ts.map +1 -0
  72. package/dist/ui/buttons/animated.js +14 -10
  73. package/dist/ui/buttons/animated.js.map +1 -1
  74. package/dist/ui/buttons/animated.mjs +12 -8
  75. package/dist/ui/buttons/animated.mjs.map +1 -1
  76. package/dist/ui/buttons.js +15 -11
  77. package/dist/ui/buttons.mjs +13 -9
  78. package/dist/ui/data-table.js +25 -21
  79. package/dist/ui/data-table.js.map +1 -1
  80. package/dist/ui/data-table.mjs +15 -11
  81. package/dist/ui/data-table.mjs.map +1 -1
  82. package/dist/ui/dynamic-stepper.js +24 -20
  83. package/dist/ui/dynamic-stepper.js.map +1 -1
  84. package/dist/ui/dynamic-stepper.mjs +13 -9
  85. package/dist/ui/dynamic-stepper.mjs.map +1 -1
  86. package/dist/ui/pagination.js +16 -12
  87. package/dist/ui/pagination.mjs +13 -9
  88. package/dist/ui/qr-code/animated/animations.d.ts +8 -0
  89. package/dist/ui/qr-code/animated/animations.d.ts.map +1 -0
  90. package/dist/ui/qr-code/animated/index.d.ts +4 -0
  91. package/dist/ui/qr-code/animated/index.d.ts.map +1 -0
  92. package/dist/ui/qr-code/animated/qr-code-animated.d.ts +6 -0
  93. package/dist/ui/qr-code/animated/qr-code-animated.d.ts.map +1 -0
  94. package/dist/ui/qr-code/animated/types.d.ts +9 -0
  95. package/dist/ui/qr-code/animated/types.d.ts.map +1 -0
  96. package/dist/ui/qr-code/animated.js +156 -0
  97. package/dist/ui/qr-code/animated.js.map +1 -0
  98. package/dist/ui/qr-code/animated.mjs +149 -0
  99. package/dist/ui/qr-code/animated.mjs.map +1 -0
  100. package/dist/ui/qr-code/index.d.ts +5 -0
  101. package/dist/ui/qr-code/index.d.ts.map +1 -0
  102. package/dist/ui/qr-code/qr-code-base.d.ts +47 -0
  103. package/dist/ui/qr-code/qr-code-base.d.ts.map +1 -0
  104. package/dist/ui/qr-code/qr-code.d.ts +2 -0
  105. package/dist/ui/qr-code/qr-code.d.ts.map +1 -0
  106. package/dist/ui/qr-code/types.d.ts +14 -0
  107. package/dist/ui/qr-code/types.d.ts.map +1 -0
  108. package/dist/ui/qr-code/variants.d.ts +4 -0
  109. package/dist/ui/qr-code/variants.d.ts.map +1 -0
  110. package/dist/ui/qr-code.js +35 -0
  111. package/dist/ui/qr-code.js.map +1 -0
  112. package/dist/ui/qr-code.mjs +17 -0
  113. package/dist/ui/qr-code.mjs.map +1 -0
  114. package/dist/ui/qr-scanner/index.d.ts +4 -0
  115. package/dist/ui/qr-scanner/index.d.ts.map +1 -0
  116. package/dist/ui/qr-scanner/qr-scanner-base.d.ts +62 -0
  117. package/dist/ui/qr-scanner/qr-scanner-base.d.ts.map +1 -0
  118. package/dist/ui/qr-scanner/qr-scanner.d.ts +2 -0
  119. package/dist/ui/qr-scanner/qr-scanner.d.ts.map +1 -0
  120. package/dist/ui/qr-scanner/types.d.ts +28 -0
  121. package/dist/ui/qr-scanner/types.d.ts.map +1 -0
  122. package/dist/ui/qr-scanner/variants.d.ts +9 -0
  123. package/dist/ui/qr-scanner/variants.d.ts.map +1 -0
  124. package/dist/ui/qr-scanner.js +316 -0
  125. package/dist/ui/qr-scanner.js.map +1 -0
  126. package/dist/ui/qr-scanner.mjs +308 -0
  127. package/dist/ui/qr-scanner.mjs.map +1 -0
  128. package/dist/ui/secret-reveal/animated/secret-reveal-animated.d.ts.map +1 -1
  129. package/dist/ui/secret-reveal/animated.js +10 -7
  130. package/dist/ui/secret-reveal/animated.js.map +1 -1
  131. package/dist/ui/secret-reveal/animated.mjs +6 -3
  132. package/dist/ui/secret-reveal/animated.mjs.map +1 -1
  133. package/dist/ui/secret-reveal/secret-reveal-base.d.ts.map +1 -1
  134. package/dist/ui/secret-reveal.js +14 -11
  135. package/dist/ui/secret-reveal.js.map +1 -1
  136. package/dist/ui/secret-reveal.mjs +7 -4
  137. package/dist/ui/secret-reveal.mjs.map +1 -1
  138. package/dist/ui/speech-recognition/animated/animations.d.ts +8 -0
  139. package/dist/ui/speech-recognition/animated/animations.d.ts.map +1 -0
  140. package/dist/ui/speech-recognition/animated/index.d.ts +4 -0
  141. package/dist/ui/speech-recognition/animated/index.d.ts.map +1 -0
  142. package/dist/ui/speech-recognition/animated/speech-recognition-animated.d.ts +6 -0
  143. package/dist/ui/speech-recognition/animated/speech-recognition-animated.d.ts.map +1 -0
  144. package/dist/ui/speech-recognition/animated/types.d.ts +9 -0
  145. package/dist/ui/speech-recognition/animated/types.d.ts.map +1 -0
  146. package/dist/ui/speech-recognition/animated.js +288 -0
  147. package/dist/ui/speech-recognition/animated.js.map +1 -0
  148. package/dist/ui/speech-recognition/animated.mjs +285 -0
  149. package/dist/ui/speech-recognition/animated.mjs.map +1 -0
  150. package/dist/ui/speech-recognition/index.d.ts +4 -0
  151. package/dist/ui/speech-recognition/index.d.ts.map +1 -0
  152. package/dist/ui/speech-recognition/speech-recognition-base.d.ts +6 -0
  153. package/dist/ui/speech-recognition/speech-recognition-base.d.ts.map +1 -0
  154. package/dist/ui/speech-recognition/speech-recognition.d.ts +2 -0
  155. package/dist/ui/speech-recognition/speech-recognition.d.ts.map +1 -0
  156. package/dist/ui/speech-recognition/types.d.ts +31 -0
  157. package/dist/ui/speech-recognition/types.d.ts.map +1 -0
  158. package/dist/ui/speech-recognition/variants.d.ts +11 -0
  159. package/dist/ui/speech-recognition/variants.d.ts.map +1 -0
  160. package/dist/ui/speech-recognition.js +242 -0
  161. package/dist/ui/speech-recognition.js.map +1 -0
  162. package/dist/ui/speech-recognition.mjs +233 -0
  163. package/dist/ui/speech-recognition.mjs.map +1 -0
  164. package/dist/ui/speech-synthesizer/animated/animations.d.ts +8 -0
  165. package/dist/ui/speech-synthesizer/animated/animations.d.ts.map +1 -0
  166. package/dist/ui/speech-synthesizer/animated/index.d.ts +4 -0
  167. package/dist/ui/speech-synthesizer/animated/index.d.ts.map +1 -0
  168. package/dist/ui/speech-synthesizer/animated/speech-synthesizer-animated.d.ts +6 -0
  169. package/dist/ui/speech-synthesizer/animated/speech-synthesizer-animated.d.ts.map +1 -0
  170. package/dist/ui/speech-synthesizer/animated/types.d.ts +9 -0
  171. package/dist/ui/speech-synthesizer/animated/types.d.ts.map +1 -0
  172. package/dist/ui/speech-synthesizer/animated.js +269 -0
  173. package/dist/ui/speech-synthesizer/animated.js.map +1 -0
  174. package/dist/ui/speech-synthesizer/animated.mjs +266 -0
  175. package/dist/ui/speech-synthesizer/animated.mjs.map +1 -0
  176. package/dist/ui/speech-synthesizer/index.d.ts +4 -0
  177. package/dist/ui/speech-synthesizer/index.d.ts.map +1 -0
  178. package/dist/ui/speech-synthesizer/speech-synthesizer-base.d.ts +6 -0
  179. package/dist/ui/speech-synthesizer/speech-synthesizer-base.d.ts.map +1 -0
  180. package/dist/ui/speech-synthesizer/speech-synthesizer.d.ts +2 -0
  181. package/dist/ui/speech-synthesizer/speech-synthesizer.d.ts.map +1 -0
  182. package/dist/ui/speech-synthesizer/types.d.ts +43 -0
  183. package/dist/ui/speech-synthesizer/types.d.ts.map +1 -0
  184. package/dist/ui/speech-synthesizer/variants.d.ts +13 -0
  185. package/dist/ui/speech-synthesizer/variants.d.ts.map +1 -0
  186. package/dist/ui/speech-synthesizer.js +220 -0
  187. package/dist/ui/speech-synthesizer.js.map +1 -0
  188. package/dist/ui/speech-synthesizer.mjs +211 -0
  189. package/dist/ui/speech-synthesizer.mjs.map +1 -0
  190. package/dist/ui/split-button.js +26 -22
  191. package/dist/ui/split-button.js.map +1 -1
  192. package/dist/ui/split-button.mjs +13 -9
  193. package/dist/ui/split-button.mjs.map +1 -1
  194. package/package.json +5 -2
  195. package/src/design-system/index.ts +4 -0
  196. package/src/design-system/qr-code.ts +13 -0
  197. package/src/design-system/qr-scanner.ts +32 -0
  198. package/src/design-system/secret-reveal.ts +1 -1
  199. package/src/design-system/speech-recognition.ts +82 -0
  200. package/src/design-system/speech-synthesizer.ts +90 -0
  201. package/src/ui/qr-code/animated/animations.ts +51 -0
  202. package/src/ui/qr-code/animated/index.ts +5 -0
  203. package/src/ui/qr-code/animated/qr-code-animated.tsx +111 -0
  204. package/src/ui/qr-code/animated/types.ts +10 -0
  205. package/src/ui/qr-code/index.ts +10 -0
  206. package/src/ui/qr-code/qr-code-base.tsx +149 -0
  207. package/src/ui/qr-code/qr-code.test.tsx +58 -0
  208. package/src/ui/qr-code/qr-code.tsx +2 -0
  209. package/src/ui/qr-code/types.ts +22 -0
  210. package/src/ui/qr-code/variants.ts +11 -0
  211. package/src/ui/qr-scanner/index.ts +17 -0
  212. package/src/ui/qr-scanner/qr-scanner-base.tsx +568 -0
  213. package/src/ui/qr-scanner/qr-scanner.test.tsx +61 -0
  214. package/src/ui/qr-scanner/qr-scanner.tsx +2 -0
  215. package/src/ui/qr-scanner/types.ts +32 -0
  216. package/src/ui/qr-scanner/variants.ts +26 -0
  217. package/src/ui/secret-reveal/animated/secret-reveal-animated.tsx +4 -1
  218. package/src/ui/secret-reveal/secret-reveal-base.tsx +4 -1
  219. package/src/ui/speech-recognition/animated/animations.ts +62 -0
  220. package/src/ui/speech-recognition/animated/index.ts +8 -0
  221. package/src/ui/speech-recognition/animated/speech-recognition-animated.tsx +276 -0
  222. package/src/ui/speech-recognition/animated/types.ts +11 -0
  223. package/src/ui/speech-recognition/index.ts +15 -0
  224. package/src/ui/speech-recognition/speech-recognition-base.tsx +276 -0
  225. package/src/ui/speech-recognition/speech-recognition.test.tsx +74 -0
  226. package/src/ui/speech-recognition/speech-recognition.tsx +1 -0
  227. package/src/ui/speech-recognition/types.ts +50 -0
  228. package/src/ui/speech-recognition/variants.ts +47 -0
  229. package/src/ui/speech-synthesizer/animated/animations.ts +62 -0
  230. package/src/ui/speech-synthesizer/animated/index.ts +8 -0
  231. package/src/ui/speech-synthesizer/animated/speech-synthesizer-animated.tsx +260 -0
  232. package/src/ui/speech-synthesizer/animated/types.ts +11 -0
  233. package/src/ui/speech-synthesizer/index.ts +14 -0
  234. package/src/ui/speech-synthesizer/speech-synthesizer-base.tsx +255 -0
  235. package/src/ui/speech-synthesizer/speech-synthesizer.test.tsx +87 -0
  236. package/src/ui/speech-synthesizer/speech-synthesizer.tsx +1 -0
  237. package/src/ui/speech-synthesizer/types.ts +57 -0
  238. package/src/ui/speech-synthesizer/variants.ts +55 -0
  239. package/dist/chunk-DIAA5VH4.mjs.map +0 -1
  240. package/dist/chunk-ENKXB2BA.js +0 -19
  241. package/dist/chunk-H3BJOK22.js.map +0 -1
  242. package/dist/chunk-YY7G4NV3.js.map +0 -1
  243. package/dist/chunk-ZB6C6CJQ.mjs.map +0 -1
@@ -62,7 +62,10 @@ export function SecretRevealBase({
62
62
  )}
63
63
  <span
64
64
  data-slot="secret-reveal-value"
65
- className={cn(secretRevealValueVariants({ size }), "flex-1 truncate")}
65
+ className={cn(
66
+ secretRevealValueVariants({ size }),
67
+ "flex-1 min-w-0 truncate",
68
+ )}
66
69
  >
67
70
  {revealed
68
71
  ? (value ?? children)
@@ -0,0 +1,62 @@
1
+ import type { Transition, Variants } from "framer-motion";
2
+
3
+ export type SpeechRecognitionAnimation = "none" | "pulse" | "wave" | "glow";
4
+
5
+ export type SpeechRecognitionAnimationPresets = Record<
6
+ SpeechRecognitionAnimation,
7
+ {
8
+ transition: Transition;
9
+ variants: Variants;
10
+ }
11
+ >;
12
+
13
+ export const speechRecognitionAnimationPresets: SpeechRecognitionAnimationPresets =
14
+ {
15
+ none: {
16
+ transition: { duration: 0 },
17
+ variants: {
18
+ initial: { scale: 1 },
19
+ animate: { scale: 1 },
20
+ },
21
+ },
22
+ pulse: {
23
+ transition: {
24
+ duration: 1,
25
+ repeat: Infinity,
26
+ repeatType: "reverse",
27
+ ease: "easeInOut",
28
+ },
29
+ variants: {
30
+ initial: { scale: 1, opacity: 0.7 },
31
+ animate: { scale: 1.15, opacity: 1 },
32
+ },
33
+ },
34
+ wave: {
35
+ transition: {
36
+ duration: 0.8,
37
+ repeat: Infinity,
38
+ repeatType: "reverse",
39
+ ease: "easeInOut",
40
+ },
41
+ variants: {
42
+ initial: { scale: 1, rotate: 0 },
43
+ animate: { scale: 1.1, rotate: -5 },
44
+ },
45
+ },
46
+ glow: {
47
+ transition: {
48
+ duration: 1.5,
49
+ repeat: Infinity,
50
+ repeatType: "reverse",
51
+ ease: "easeInOut",
52
+ },
53
+ variants: {
54
+ initial: {
55
+ boxShadow: "0 0 0px rgba(59,130,246,0)",
56
+ },
57
+ animate: {
58
+ boxShadow: "0 0 20px rgba(59,130,246,0.5)",
59
+ },
60
+ },
61
+ },
62
+ };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+
3
+ export { SpeechRecognitionAnimated } from "./speech-recognition-animated";
4
+ export type {
5
+ SpeechRecognitionAnimation,
6
+ SpeechRecognitionAnimatedProps,
7
+ } from "./types";
8
+ export { speechRecognitionAnimationPresets } from "./animations";
@@ -0,0 +1,276 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import {
5
+ useCallback,
6
+ useEffect,
7
+ useImperativeHandle,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+
12
+ import { cn } from "../../../lib/utils";
13
+
14
+ import type { SpeechRecognitionRef, SpeechRecognitionState } from "../types";
15
+ import {
16
+ speechRecognitionBtnActiveClasses,
17
+ speechRecognitionBtnVariants,
18
+ speechRecognitionStatusClasses,
19
+ speechRecognitionTranscriptClasses,
20
+ speechRecognitionVariants,
21
+ } from "../variants";
22
+
23
+ import { speechRecognitionAnimationPresets } from "./animations";
24
+ import type { SpeechRecognitionAnimatedProps } from "./types";
25
+
26
+ function getSpeechRecognition(): any {
27
+ if (typeof window === "undefined") return undefined;
28
+ return (
29
+ (window as any).SpeechRecognition ?? (window as any).webkitSpeechRecognition
30
+ );
31
+ }
32
+
33
+ function DefaultMic({
34
+ isListening,
35
+ state,
36
+ }: {
37
+ isListening: boolean;
38
+ state: SpeechRecognitionState;
39
+ }) {
40
+ return (
41
+ <svg
42
+ viewBox="0 0 24 24"
43
+ fill="none"
44
+ stroke="currentColor"
45
+ strokeWidth="2"
46
+ strokeLinecap="round"
47
+ strokeLinejoin="round"
48
+ className={`size-5 ${state === "error" ? "text-red-400" : ""}`}
49
+ >
50
+ <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
51
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
52
+ <line x1="12" y1="19" x2="12" y2="22" />
53
+ {isListening && (
54
+ <>
55
+ <line x1="9" y1="20" x2="7" y2="22" />
56
+ <line x1="15" y1="20" x2="17" y2="22" />
57
+ </>
58
+ )}
59
+ </svg>
60
+ );
61
+ }
62
+
63
+ export function SpeechRecognitionAnimated({
64
+ appearance,
65
+ size,
66
+ lang,
67
+ continuous = false,
68
+ interimResults = true,
69
+ autoStart = false,
70
+ onResult,
71
+ onError,
72
+ onStateChange,
73
+ renderMic,
74
+ animation = "pulse",
75
+ className,
76
+ children,
77
+ ...rest
78
+ }: SpeechRecognitionAnimatedProps) {
79
+ const [state, setState] = useState<SpeechRecognitionState>("idle");
80
+ const [transcript, setTranscript] = useState("");
81
+ const [interimText, setInterimText] = useState("");
82
+ const recognitionRef = useRef<any>(null);
83
+ const stateRef = useRef<SpeechRecognitionState>("idle");
84
+ const preset = speechRecognitionAnimationPresets[animation];
85
+
86
+ const setStateSafe = useCallback(
87
+ (newState: SpeechRecognitionState) => {
88
+ stateRef.current = newState;
89
+ setState(newState);
90
+ onStateChange?.(newState);
91
+ },
92
+ [onStateChange],
93
+ );
94
+
95
+ const start = useCallback(() => {
96
+ const SR = getSpeechRecognition();
97
+ if (!SR) {
98
+ setStateSafe("error");
99
+ onError?.("SpeechRecognition is not supported in this browser");
100
+ return;
101
+ }
102
+
103
+ if (recognitionRef.current) {
104
+ try {
105
+ recognitionRef.current.abort();
106
+ } catch {}
107
+ }
108
+
109
+ const recognition = new SR();
110
+ recognition.continuous = continuous;
111
+ recognition.interimResults = interimResults;
112
+ if (lang) recognition.lang = lang;
113
+
114
+ recognition.onstart = () => setStateSafe("listening");
115
+ recognition.onresult = (event: any) => {
116
+ let finalText = "";
117
+ let interimTextBuilder = "";
118
+
119
+ for (let i = event.resultIndex; i < event.results.length; i++) {
120
+ const result = event.results[i];
121
+ if (result.isFinal) {
122
+ finalText += result[0].transcript;
123
+ } else {
124
+ interimTextBuilder += result[0].transcript;
125
+ }
126
+ }
127
+
128
+ if (finalText) {
129
+ setTranscript((prev) => prev + finalText);
130
+ onResult?.({ transcript: finalText, isFinal: true });
131
+ setStateSafe("processing");
132
+ }
133
+ setInterimText(interimTextBuilder);
134
+ if (interimTextBuilder) {
135
+ onResult?.({ transcript: interimTextBuilder, isFinal: false });
136
+ }
137
+ };
138
+ recognition.onerror = (event: { error: string }) => {
139
+ setStateSafe("error");
140
+ onError?.(event.error);
141
+ };
142
+ recognition.onend = () => {
143
+ if (stateRef.current === "listening") {
144
+ setStateSafe("idle");
145
+ }
146
+ };
147
+
148
+ try {
149
+ recognition.start();
150
+ } catch (err) {
151
+ setStateSafe("error");
152
+ onError?.(String(err));
153
+ }
154
+ recognitionRef.current = recognition;
155
+ }, [continuous, interimResults, lang, onError, onResult, setStateSafe]);
156
+
157
+ const stop = useCallback(() => {
158
+ if (recognitionRef.current) {
159
+ try {
160
+ recognitionRef.current.stop();
161
+ } catch {}
162
+ }
163
+ setStateSafe("idle");
164
+ }, [setStateSafe]);
165
+
166
+ const abort = useCallback(() => {
167
+ if (recognitionRef.current) {
168
+ try {
169
+ recognitionRef.current.abort();
170
+ } catch {}
171
+ }
172
+ setTranscript("");
173
+ setInterimText("");
174
+ setStateSafe("idle");
175
+ }, [setStateSafe]);
176
+
177
+ const imperativeHandle: SpeechRecognitionRef = {
178
+ start,
179
+ stop,
180
+ abort,
181
+ get state() {
182
+ return stateRef.current;
183
+ },
184
+ };
185
+
186
+ useImperativeHandle((rest as any).ref, () => imperativeHandle, [
187
+ start,
188
+ stop,
189
+ abort,
190
+ ]);
191
+
192
+ useEffect(() => {
193
+ if (autoStart) start();
194
+ return () => {
195
+ if (recognitionRef.current) {
196
+ try {
197
+ recognitionRef.current.abort();
198
+ } catch {}
199
+ }
200
+ };
201
+ }, []);
202
+
203
+ const isListening = state === "listening";
204
+ const toggleListening = useCallback(() => {
205
+ isListening ? stop() : start();
206
+ }, [isListening, start, stop]);
207
+
208
+ const displayText = transcript + (interimText ? ` ${interimText}` : "");
209
+ const statusText =
210
+ state === "idle"
211
+ ? "Click to start listening"
212
+ : state === "listening"
213
+ ? "Listening..."
214
+ : state === "processing"
215
+ ? "Processing..."
216
+ : "Error occurred";
217
+
218
+ return (
219
+ <motion.div
220
+ data-slot="speech-recognition"
221
+ className={cn(speechRecognitionVariants({ size }), className)}
222
+ {...(rest as any)}
223
+ >
224
+ <div className="flex items-center gap-3">
225
+ <motion.button
226
+ type="button"
227
+ data-slot="speech-recognition-btn"
228
+ className={cn(
229
+ speechRecognitionBtnVariants({ appearance, size }),
230
+ isListening && speechRecognitionBtnActiveClasses,
231
+ )}
232
+ onClick={toggleListening}
233
+ aria-label={isListening ? "Stop listening" : "Start listening"}
234
+ aria-pressed={isListening}
235
+ animate={
236
+ isListening
237
+ ? {
238
+ scale: [1, 1.1, 1],
239
+ transition: { duration: 1, repeat: Infinity },
240
+ }
241
+ : { scale: 1 }
242
+ }
243
+ >
244
+ {renderMic ? (
245
+ renderMic({ isListening, state })
246
+ ) : (
247
+ <DefaultMic isListening={isListening} state={state} />
248
+ )}
249
+ </motion.button>
250
+ <motion.span
251
+ data-slot="speech-recognition-status"
252
+ className={speechRecognitionStatusClasses}
253
+ initial="initial"
254
+ animate={isListening ? "animate" : "initial"}
255
+ variants={preset.variants}
256
+ transition={preset.transition}
257
+ >
258
+ {statusText}
259
+ </motion.span>
260
+ </div>
261
+ {displayText && (
262
+ <motion.p
263
+ data-slot="speech-recognition-transcript"
264
+ className={speechRecognitionTranscriptClasses}
265
+ initial={{ opacity: 0, y: 5 }}
266
+ animate={{ opacity: 1, y: 0 }}
267
+ >
268
+ {displayText}
269
+ </motion.p>
270
+ )}
271
+ {children}
272
+ </motion.div>
273
+ );
274
+ }
275
+
276
+ SpeechRecognitionAnimated.displayName = "SpeechRecognitionAnimated";
@@ -0,0 +1,11 @@
1
+ import type { Ref } from "react";
2
+
3
+ import type { SpeechRecognitionBaseProps } from "../types";
4
+ import type { SpeechRecognitionAnimation } from "./animations";
5
+
6
+ export type { SpeechRecognitionAnimation };
7
+
8
+ export type SpeechRecognitionAnimatedProps = SpeechRecognitionBaseProps & {
9
+ animation?: SpeechRecognitionAnimation;
10
+ ref?: Ref<HTMLDivElement>;
11
+ };
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ export { SpeechRecognition } from "./speech-recognition";
4
+ export type {
5
+ SpeechRecognitionBaseProps,
6
+ SpeechRecognitionProps,
7
+ SpeechRecognitionRef,
8
+ SpeechRecognitionResult,
9
+ SpeechRecognitionState,
10
+ SpeechRecognitionVariantProps,
11
+ } from "./types";
12
+ export {
13
+ speechRecognitionBtnVariants,
14
+ speechRecognitionVariants,
15
+ } from "./variants";
@@ -0,0 +1,276 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+
11
+ import { cn } from "../../lib/utils";
12
+
13
+ import type {
14
+ SpeechRecognitionBaseProps,
15
+ SpeechRecognitionRef,
16
+ SpeechRecognitionState,
17
+ } from "./types";
18
+ import {
19
+ speechRecognitionBtnActiveClasses,
20
+ speechRecognitionBtnVariants,
21
+ speechRecognitionStatusClasses,
22
+ speechRecognitionTranscriptClasses,
23
+ speechRecognitionVariants,
24
+ } from "./variants";
25
+
26
+ function getSpeechRecognition(): any {
27
+ if (typeof window === "undefined") return undefined;
28
+ return (
29
+ (window as any).SpeechRecognition ?? (window as any).webkitSpeechRecognition
30
+ );
31
+ }
32
+
33
+ function DefaultMic({
34
+ isListening,
35
+ state,
36
+ }: {
37
+ isListening: boolean;
38
+ state: SpeechRecognitionState;
39
+ }) {
40
+ return (
41
+ <svg
42
+ viewBox="0 0 24 24"
43
+ fill="none"
44
+ stroke="currentColor"
45
+ strokeWidth="2"
46
+ strokeLinecap="round"
47
+ strokeLinejoin="round"
48
+ className={`size-5 ${state === "error" ? "text-red-400" : ""}`}
49
+ >
50
+ <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
51
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
52
+ <line x1="12" y1="19" x2="12" y2="22" />
53
+ {isListening && (
54
+ <>
55
+ <line x1="9" y1="20" x2="7" y2="22" />
56
+ <line x1="15" y1="20" x2="17" y2="22" />
57
+ </>
58
+ )}
59
+ </svg>
60
+ );
61
+ }
62
+
63
+ export function SpeechRecognitionBase({
64
+ appearance,
65
+ size,
66
+ lang,
67
+ continuous = false,
68
+ interimResults = true,
69
+ autoStart = false,
70
+ onResult,
71
+ onError,
72
+ onStateChange,
73
+ renderMic,
74
+ className,
75
+ children,
76
+ ...rest
77
+ }: SpeechRecognitionBaseProps) {
78
+ const [state, setState] = useState<SpeechRecognitionState>("idle");
79
+ const [transcript, setTranscript] = useState("");
80
+ const [interimText, setInterimText] = useState("");
81
+ const recognitionRef = useRef<any>(null);
82
+ const stateRef = useRef<SpeechRecognitionState>("idle");
83
+ const localRef = useRef<HTMLDivElement>(null);
84
+
85
+ const setStateSafe = useCallback(
86
+ (newState: SpeechRecognitionState) => {
87
+ stateRef.current = newState;
88
+ setState(newState);
89
+ onStateChange?.(newState);
90
+ },
91
+ [onStateChange],
92
+ );
93
+
94
+ const start = useCallback(() => {
95
+ const SR = getSpeechRecognition();
96
+ if (!SR) {
97
+ setStateSafe("error");
98
+ onError?.("SpeechRecognition is not supported in this browser");
99
+ return;
100
+ }
101
+
102
+ if (recognitionRef.current) {
103
+ try {
104
+ recognitionRef.current.abort();
105
+ } catch {}
106
+ }
107
+
108
+ const recognition = new SR();
109
+ recognition.continuous = continuous;
110
+ recognition.interimResults = interimResults;
111
+ if (lang) recognition.lang = lang;
112
+
113
+ recognition.onstart = () => {
114
+ setStateSafe("listening");
115
+ };
116
+
117
+ recognition.onresult = (event: any) => {
118
+ let finalText = "";
119
+ let interimTextBuilder = "";
120
+
121
+ for (let i = event.resultIndex; i < event.results.length; i++) {
122
+ const result = event.results[i];
123
+ if (result.isFinal) {
124
+ finalText += result[0].transcript;
125
+ } else {
126
+ interimTextBuilder += result[0].transcript;
127
+ }
128
+ }
129
+
130
+ if (finalText) {
131
+ setTranscript((prev) => prev + finalText);
132
+ onResult?.({ transcript: finalText, isFinal: true });
133
+ setStateSafe("processing");
134
+ }
135
+ setInterimText(interimTextBuilder);
136
+ if (interimTextBuilder) {
137
+ onResult?.({ transcript: interimTextBuilder, isFinal: false });
138
+ }
139
+ };
140
+
141
+ recognition.onerror = (event: { error: string }) => {
142
+ setStateSafe("error");
143
+ onError?.(event.error);
144
+ };
145
+
146
+ recognition.onend = () => {
147
+ if (stateRef.current === "listening") {
148
+ setStateSafe("idle");
149
+ }
150
+ };
151
+
152
+ try {
153
+ recognition.start();
154
+ } catch (err) {
155
+ setStateSafe("error");
156
+ onError?.(String(err));
157
+ }
158
+
159
+ recognitionRef.current = recognition;
160
+ }, [continuous, interimResults, lang, onError, onResult, setStateSafe]);
161
+
162
+ const stop = useCallback(() => {
163
+ if (recognitionRef.current) {
164
+ try {
165
+ recognitionRef.current.stop();
166
+ } catch {}
167
+ }
168
+ setStateSafe("idle");
169
+ }, [setStateSafe]);
170
+
171
+ const abort = useCallback(() => {
172
+ if (recognitionRef.current) {
173
+ try {
174
+ recognitionRef.current.abort();
175
+ } catch {}
176
+ }
177
+ setTranscript("");
178
+ setInterimText("");
179
+ setStateSafe("idle");
180
+ }, [setStateSafe]);
181
+
182
+ const imperativeHandle: SpeechRecognitionRef = {
183
+ start,
184
+ stop,
185
+ abort,
186
+ get state() {
187
+ return stateRef.current;
188
+ },
189
+ };
190
+
191
+ useImperativeHandle((rest as any).ref, () => imperativeHandle, [
192
+ start,
193
+ stop,
194
+ abort,
195
+ ]);
196
+
197
+ useEffect(() => {
198
+ if (autoStart) {
199
+ start();
200
+ }
201
+ return () => {
202
+ if (recognitionRef.current) {
203
+ try {
204
+ recognitionRef.current.abort();
205
+ } catch {}
206
+ }
207
+ };
208
+ }, []);
209
+
210
+ const isListening = state === "listening";
211
+
212
+ const toggleListening = useCallback(() => {
213
+ if (isListening) {
214
+ stop();
215
+ } else {
216
+ start();
217
+ }
218
+ }, [isListening, start, stop]);
219
+
220
+ const displayText = transcript + (interimText ? ` ${interimText}` : "");
221
+
222
+ const statusText =
223
+ state === "idle"
224
+ ? "Click to start listening"
225
+ : state === "listening"
226
+ ? "Listening..."
227
+ : state === "processing"
228
+ ? "Processing..."
229
+ : "Error occurred";
230
+
231
+ return (
232
+ <div
233
+ ref={localRef}
234
+ data-slot="speech-recognition"
235
+ className={cn(speechRecognitionVariants({ size }), className)}
236
+ {...rest}
237
+ >
238
+ <div className="flex items-center gap-3">
239
+ <button
240
+ type="button"
241
+ data-slot="speech-recognition-btn"
242
+ className={cn(
243
+ speechRecognitionBtnVariants({ appearance, size }),
244
+ isListening && speechRecognitionBtnActiveClasses,
245
+ )}
246
+ onClick={toggleListening}
247
+ aria-label={isListening ? "Stop listening" : "Start listening"}
248
+ aria-pressed={isListening}
249
+ >
250
+ {renderMic ? (
251
+ renderMic({ isListening, state })
252
+ ) : (
253
+ <DefaultMic isListening={isListening} state={state} />
254
+ )}
255
+ </button>
256
+ <span
257
+ data-slot="speech-recognition-status"
258
+ className={speechRecognitionStatusClasses}
259
+ >
260
+ {statusText}
261
+ </span>
262
+ </div>
263
+ {displayText && (
264
+ <p
265
+ data-slot="speech-recognition-transcript"
266
+ className={speechRecognitionTranscriptClasses}
267
+ >
268
+ {displayText}
269
+ </p>
270
+ )}
271
+ {children}
272
+ </div>
273
+ );
274
+ }
275
+
276
+ SpeechRecognitionBase.displayName = "SpeechRecognition";