@fpkit/acss 0.4.4

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 (297) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/chunk-77CZU5XZ.cjs +9 -0
  4. package/dist/chunk-77CZU5XZ.cjs.map +1 -0
  5. package/dist/chunk-D43FJIRQ.cjs +31 -0
  6. package/dist/chunk-D43FJIRQ.cjs.map +1 -0
  7. package/dist/chunk-GJWMCDFS.js +9 -0
  8. package/dist/chunk-GJWMCDFS.js.map +1 -0
  9. package/dist/chunk-PCDUGD3C.js +5 -0
  10. package/dist/chunk-PCDUGD3C.js.map +1 -0
  11. package/dist/hooks.cjs +10 -0
  12. package/dist/hooks.cjs.map +1 -0
  13. package/dist/hooks.d.cts +32 -0
  14. package/dist/hooks.d.ts +32 -0
  15. package/dist/hooks.js +8 -0
  16. package/dist/hooks.js.map +1 -0
  17. package/dist/icon-e6044c73.d.ts +227 -0
  18. package/dist/icons.cjs +73 -0
  19. package/dist/icons.cjs.map +1 -0
  20. package/dist/icons.d.cts +252 -0
  21. package/dist/icons.d.ts +252 -0
  22. package/dist/icons.js +4 -0
  23. package/dist/icons.js.map +1 -0
  24. package/dist/index.cjs +59 -0
  25. package/dist/index.cjs.map +1 -0
  26. package/dist/index.d.cts +566 -0
  27. package/dist/index.d.ts +566 -0
  28. package/dist/index.js +11 -0
  29. package/dist/index.js.map +1 -0
  30. package/libs/chunk-GCGKYLDG.js +7 -0
  31. package/libs/chunk-GCGKYLDG.js.map +1 -0
  32. package/libs/chunk-PDD4N5P5.cjs +10 -0
  33. package/libs/chunk-PDD4N5P5.cjs.map +1 -0
  34. package/libs/chunk-QHIABQNQ.js +8 -0
  35. package/libs/chunk-QHIABQNQ.js.map +1 -0
  36. package/libs/chunk-ZOHIKF6I.cjs +31 -0
  37. package/libs/chunk-ZOHIKF6I.cjs.map +1 -0
  38. package/libs/components/badge/badge.css +1 -0
  39. package/libs/components/badge/badge.css.map +1 -0
  40. package/libs/components/badge/badge.min.css +3 -0
  41. package/libs/components/breadcrumbs/breadcrumb.css +1 -0
  42. package/libs/components/breadcrumbs/breadcrumb.css.map +1 -0
  43. package/libs/components/breadcrumbs/breadcrumb.min.css +3 -0
  44. package/libs/components/buttons/button.css +1 -0
  45. package/libs/components/buttons/button.css.map +1 -0
  46. package/libs/components/buttons/button.min.css +3 -0
  47. package/libs/components/cards/card-style.css +1 -0
  48. package/libs/components/cards/card-style.css.map +1 -0
  49. package/libs/components/cards/card-style.min.css +3 -0
  50. package/libs/components/cards/card.css +1 -0
  51. package/libs/components/cards/card.css.map +1 -0
  52. package/libs/components/cards/card.min.css +3 -0
  53. package/libs/components/details/details.css +1 -0
  54. package/libs/components/details/details.css.map +1 -0
  55. package/libs/components/details/details.min.css +3 -0
  56. package/libs/components/form/form.css +1 -0
  57. package/libs/components/form/form.css.map +1 -0
  58. package/libs/components/form/form.min.css +3 -0
  59. package/libs/components/icons/icon.css +1 -0
  60. package/libs/components/icons/icon.css.map +1 -0
  61. package/libs/components/icons/icon.min.css +3 -0
  62. package/libs/components/images/img.css +1 -0
  63. package/libs/components/images/img.css.map +1 -0
  64. package/libs/components/images/img.min.css +3 -0
  65. package/libs/components/layout/landmarks.css +1 -0
  66. package/libs/components/layout/landmarks.css.map +1 -0
  67. package/libs/components/layout/landmarks.min.css +3 -0
  68. package/libs/components/link/link.css +1 -0
  69. package/libs/components/link/link.css.map +1 -0
  70. package/libs/components/link/link.min.css +3 -0
  71. package/libs/components/nav/nav.css +1 -0
  72. package/libs/components/nav/nav.css.map +1 -0
  73. package/libs/components/nav/nav.min.css +3 -0
  74. package/libs/components/progress/progress.css +1 -0
  75. package/libs/components/progress/progress.css.map +1 -0
  76. package/libs/components/progress/progress.min.css +3 -0
  77. package/libs/components/styles/index.css +1 -0
  78. package/libs/components/styles/index.css.map +1 -0
  79. package/libs/components/styles/index.min.css +3 -0
  80. package/libs/components/tag/tag.css +1 -0
  81. package/libs/components/tag/tag.css.map +1 -0
  82. package/libs/components/tag/tag.min.css +3 -0
  83. package/libs/components/text-to-speech/text-to-speech.css +1 -0
  84. package/libs/components/text-to-speech/text-to-speech.css.map +1 -0
  85. package/libs/components/text-to-speech/text-to-speech.min.css +3 -0
  86. package/libs/hooks.cjs +12 -0
  87. package/libs/hooks.cjs.map +1 -0
  88. package/libs/hooks.d.cts +32 -0
  89. package/libs/hooks.d.ts +32 -0
  90. package/libs/hooks.js +3 -0
  91. package/libs/hooks.js.map +1 -0
  92. package/libs/icons-1f5afc0c.d.ts +318 -0
  93. package/libs/icons.cjs +12 -0
  94. package/libs/icons.cjs.map +1 -0
  95. package/libs/icons.d.cts +2 -0
  96. package/libs/icons.d.ts +2 -0
  97. package/libs/icons.js +3 -0
  98. package/libs/icons.js.map +1 -0
  99. package/libs/index.cjs +71 -0
  100. package/libs/index.cjs.map +1 -0
  101. package/libs/index.css +1 -0
  102. package/libs/index.css.map +1 -0
  103. package/libs/index.d.cts +551 -0
  104. package/libs/index.d.ts +551 -0
  105. package/libs/index.js +11 -0
  106. package/libs/index.js.map +1 -0
  107. package/package.json +125 -0
  108. package/src/App.css +42 -0
  109. package/src/App.tsx +35 -0
  110. package/src/__snapshots__/App.test.tsx.snap +56 -0
  111. package/src/components/.gitkeep +0 -0
  112. package/src/components/__snapshots__/fp.test.tsx.snap +3 -0
  113. package/src/components/badge/badge.scss +20 -0
  114. package/src/components/badge/badge.stories.tsx +54 -0
  115. package/src/components/badge/badge.tsx +17 -0
  116. package/src/components/breadcrumbs/bc-item.tsx +20 -0
  117. package/src/components/breadcrumbs/breadcrumb.scss +35 -0
  118. package/src/components/breadcrumbs/breadcrumb.stories.tsx +92 -0
  119. package/src/components/breadcrumbs/breadcrumb.tsx +218 -0
  120. package/src/components/buttons/button.scss +115 -0
  121. package/src/components/buttons/button.stories.tsx +57 -0
  122. package/src/components/buttons/button.test.tsx +104 -0
  123. package/src/components/buttons/button.tsx +64 -0
  124. package/src/components/cards/card-style.scss +0 -0
  125. package/src/components/cards/card.scss +43 -0
  126. package/src/components/cards/card.stories.tsx +114 -0
  127. package/src/components/cards/card.test.tsx +30 -0
  128. package/src/components/cards/card.tsx +135 -0
  129. package/src/components/cards/flex-card.tsx +15 -0
  130. package/src/components/details/details.scss +75 -0
  131. package/src/components/details/details.stories.tsx +122 -0
  132. package/src/components/details/details.tsx +77 -0
  133. package/src/components/form/README.mdx +70 -0
  134. package/src/components/form/fields.tsx +45 -0
  135. package/src/components/form/form.scss +87 -0
  136. package/src/components/form/form.stories.tsx +49 -0
  137. package/src/components/form/form.tsx +71 -0
  138. package/src/components/form/input.stories.tsx +155 -0
  139. package/src/components/form/inputs.tsx +84 -0
  140. package/src/components/form/select.stories.tsx +38 -0
  141. package/src/components/form/select.tsx +112 -0
  142. package/src/components/form/textarea.tsx +87 -0
  143. package/src/components/fp.test.tsx +56 -0
  144. package/src/components/fp.tsx +78 -0
  145. package/src/components/heading/heading.stories.tsx +75 -0
  146. package/src/components/heading/heading.tsx +27 -0
  147. package/src/components/icons/components/add.tsx +42 -0
  148. package/src/components/icons/components/arrow-down.tsx +52 -0
  149. package/src/components/icons/components/arrow-left.tsx +49 -0
  150. package/src/components/icons/components/arrow-right.tsx +52 -0
  151. package/src/components/icons/components/arrow-up.tsx +49 -0
  152. package/src/components/icons/components/chat.tsx +44 -0
  153. package/src/components/icons/components/code.tsx +50 -0
  154. package/src/components/icons/components/copy.tsx +51 -0
  155. package/src/components/icons/components/down.tsx +33 -0
  156. package/src/components/icons/components/home.tsx +57 -0
  157. package/src/components/icons/components/left.tsx +43 -0
  158. package/src/components/icons/components/minus.tsx +42 -0
  159. package/src/components/icons/components/pause-solid.tsx +48 -0
  160. package/src/components/icons/components/pause.tsx +63 -0
  161. package/src/components/icons/components/play-solid.tsx +44 -0
  162. package/src/components/icons/components/play.tsx +51 -0
  163. package/src/components/icons/components/remove.tsx +42 -0
  164. package/src/components/icons/components/resume-solid.tsx +52 -0
  165. package/src/components/icons/components/resume.tsx +57 -0
  166. package/src/components/icons/components/right.tsx +43 -0
  167. package/src/components/icons/components/star.tsx +38 -0
  168. package/src/components/icons/components/stop-solid.tsx +44 -0
  169. package/src/components/icons/components/stop.tsx +54 -0
  170. package/src/components/icons/components/svg.tsx +44 -0
  171. package/src/components/icons/components/up.tsx +31 -0
  172. package/src/components/icons/components/user.tsx +46 -0
  173. package/src/components/icons/icon.scss +15 -0
  174. package/src/components/icons/icon.stories.tsx +208 -0
  175. package/src/components/icons/icon.tsx +100 -0
  176. package/src/components/icons/index.ts +29 -0
  177. package/src/components/icons/types.ts +12 -0
  178. package/src/components/images/README.mdx +43 -0
  179. package/src/components/images/figure.stories.tsx +34 -0
  180. package/src/components/images/figure.tsx +44 -0
  181. package/src/components/images/img.scss +43 -0
  182. package/src/components/images/img.stories.tsx +24 -0
  183. package/src/components/images/img.test.tsx +43 -0
  184. package/src/components/images/img.tsx +93 -0
  185. package/src/components/images/place-holder.png +0 -0
  186. package/src/components/kit.tsx +56 -0
  187. package/src/components/layout/_header.scss +72 -0
  188. package/src/components/layout/footer.stories.tsx +34 -0
  189. package/src/components/layout/landmarks.scss +51 -0
  190. package/src/components/layout/landmarks.stories.tsx +54 -0
  191. package/src/components/layout/landmarks.tsx +149 -0
  192. package/src/components/layout/main.stories.tsx +90 -0
  193. package/src/components/link/link.scss +92 -0
  194. package/src/components/link/link.stories.tsx +74 -0
  195. package/src/components/link/link.tsx +48 -0
  196. package/src/components/list/list.stories.tsx +52 -0
  197. package/src/components/list/list.tsx +74 -0
  198. package/src/components/modal/dialog.tsx +50 -0
  199. package/src/components/modal/modal.tsx +85 -0
  200. package/src/components/nav/nav.scss +90 -0
  201. package/src/components/nav/nav.stories.tsx +96 -0
  202. package/src/components/nav/nav.tsx +76 -0
  203. package/src/components/popover/node_modules/.vitest/results.json +1 -0
  204. package/src/components/popover/popover.stories.tsx +31 -0
  205. package/src/components/popover/popover.test.tsx +39 -0
  206. package/src/components/popover/popover.tsx +85 -0
  207. package/src/components/progress/progress.scss +70 -0
  208. package/src/components/progress/progress.stories.tsx +51 -0
  209. package/src/components/progress/progress.tsx +82 -0
  210. package/src/components/readme.stories.mdx +7 -0
  211. package/src/components/styles/index.css +520 -0
  212. package/src/components/styles/index.css.map +1 -0
  213. package/src/components/tables/table-elements.tsx +57 -0
  214. package/src/components/tables/table.tsx +57 -0
  215. package/src/components/tag/tag.scss +56 -0
  216. package/src/components/tag/tag.stories.tsx +39 -0
  217. package/src/components/tag/tag.tsx +25 -0
  218. package/src/components/text/text.stories.tsx +67 -0
  219. package/src/components/text/text.tsx +93 -0
  220. package/src/components/text-to-speech/README.mdx +192 -0
  221. package/src/components/text-to-speech/TextInput.tsx +19 -0
  222. package/src/components/text-to-speech/TextToSpeech.stories.tsx +145 -0
  223. package/src/components/text-to-speech/TextToSpeech.tsx +94 -0
  224. package/src/components/text-to-speech/text-to-speech.scss +31 -0
  225. package/src/components/text-to-speech/useTextToSpeech.mdx +182 -0
  226. package/src/components/text-to-speech/useTextToSpeech.tsx +176 -0
  227. package/src/components/text-to-speech/views/TextToSpeechControls.tsx +117 -0
  228. package/src/components/ui.tsx +67 -0
  229. package/src/favicon.svg +15 -0
  230. package/src/hooks/popover/__snapshots__/popover.test.tsx.snap +88 -0
  231. package/src/hooks/popover/node_modules/.vitest/results.json +1 -0
  232. package/src/hooks/popover/popover.tsx +71 -0
  233. package/src/hooks/popover/use-popover.tsx +83 -0
  234. package/src/hooks.ts +1 -0
  235. package/src/icons.ts +1 -0
  236. package/src/index.css +13 -0
  237. package/src/index.scss +19 -0
  238. package/src/index.ts +35 -0
  239. package/src/libs/content.ts +30 -0
  240. package/src/logo.svg +7 -0
  241. package/src/main.tsx +10 -0
  242. package/src/patterns/.gitkeep +0 -0
  243. package/src/patterns/page/page-header.stories.tsx +44 -0
  244. package/src/patterns/page/page-header.tsx +78 -0
  245. package/src/sass/_elements.scss +17 -0
  246. package/src/sass/_globals.scss +162 -0
  247. package/src/sass/_layout.scss +51 -0
  248. package/src/sass/_loading-animation.scss +35 -0
  249. package/src/sass/_mixins.scss +10 -0
  250. package/src/sass/_properties.scss +106 -0
  251. package/src/sass/_reset.scss +183 -0
  252. package/src/sass/_type.scss +43 -0
  253. package/src/setupTest.ts +1 -0
  254. package/src/styles/badge/badge.css +22 -0
  255. package/src/styles/badge/badge.css.map +1 -0
  256. package/src/styles/breadcrumbs/breadcrumb.css +42 -0
  257. package/src/styles/breadcrumbs/breadcrumb.css.map +1 -0
  258. package/src/styles/buttons/button.css +93 -0
  259. package/src/styles/buttons/button.css.map +1 -0
  260. package/src/styles/cards/card-style.css +3 -0
  261. package/src/styles/cards/card-style.css.map +1 -0
  262. package/src/styles/cards/card.css +48 -0
  263. package/src/styles/cards/card.css.map +1 -0
  264. package/src/styles/details/details.css +69 -0
  265. package/src/styles/details/details.css.map +1 -0
  266. package/src/styles/dropdowns/dropdown.css.map +1 -0
  267. package/src/styles/form/form.css +93 -0
  268. package/src/styles/form/form.css.map +1 -0
  269. package/src/styles/form/style.css.map +1 -0
  270. package/src/styles/icons/icon.css +16 -0
  271. package/src/styles/icons/icon.css.map +1 -0
  272. package/src/styles/images/img.css +42 -0
  273. package/src/styles/images/img.css.map +1 -0
  274. package/src/styles/index.css +1330 -0
  275. package/src/styles/index.css.map +1 -0
  276. package/src/styles/layout/landmarks.css +155 -0
  277. package/src/styles/layout/landmarks.css.map +1 -0
  278. package/src/styles/link/link.css +88 -0
  279. package/src/styles/link/link.css.map +1 -0
  280. package/src/styles/nav/nav.css +85 -0
  281. package/src/styles/nav/nav.css.map +1 -0
  282. package/src/styles/progress/progress.css +54 -0
  283. package/src/styles/progress/progress.css.map +1 -0
  284. package/src/styles/progress/sass/progress.css.map +1 -0
  285. package/src/styles/styles/index.css +562 -0
  286. package/src/styles/styles/index.css.map +1 -0
  287. package/src/styles/tag/badge.css.map +1 -0
  288. package/src/styles/tag/tag.css +71 -0
  289. package/src/styles/tag/tag.css.map +1 -0
  290. package/src/styles/text-to-speech/text-to-speech.css +32 -0
  291. package/src/styles/text-to-speech/text-to-speech.css.map +1 -0
  292. package/src/test/setup.ts +6 -0
  293. package/src/types/component-props.ts +36 -0
  294. package/src/types/index.ts +2 -0
  295. package/src/types/input-props.ts +28 -0
  296. package/src/types/shared.ts +57 -0
  297. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,182 @@
1
+
2
+
3
+ ```bash
4
+ npm install fpkit-react
5
+ ```
6
+
7
+ ## Usage
8
+
9
+ To use the `useTextToSpeech` hook in your React application, follow these steps:
10
+
11
+ 1. Import the hook into your component:
12
+
13
+ ```jsx
14
+ import { useTextToSpeech } from 'fpkit-react';
15
+ ```
16
+
17
+ 2. Initialize the hook:
18
+
19
+ ```jsx
20
+ const { speak, speaking, paused, availableVoices, availableLanguages, cancel } = useTextToSpeech();
21
+ ```
22
+
23
+ 3. Use the hook's functions to control speech:
24
+
25
+ ```jsx
26
+ speak('Hello, world!');
27
+ ```
28
+
29
+ 4. Customize speech options:
30
+
31
+ ```jsx
32
+ speak('Hello, world!', {
33
+ voice: availableVoices[0],
34
+ language: availableLanguages[0],
35
+ pitch: 1,
36
+ rate: 1,
37
+ });
38
+ ```
39
+
40
+ 5. Handle speaking and paused states:
41
+
42
+ ```jsx
43
+ <div>
44
+ {speaking ? 'Speaking...' : 'Paused'}
45
+ <button onClick={cancel}>Cancel</button>
46
+ </div>
47
+ ```
48
+
49
+ ## Example
50
+
51
+ Here's a simple example that demonstrates the use of the `useTextToSpeech` hook:
52
+
53
+ ```jsx
54
+ import React, { useState } from 'react';
55
+ import { useTextToSpeech } from 'fpkit-react';
56
+
57
+ const App = () => {
58
+ const { speak, speaking, paused, availableVoices, availableLanguages, cancel } = useTextToSpeech();
59
+ const [text, setText] = useState('');
60
+
61
+ return (
62
+ <div>
63
+ <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
64
+ <button onClick={() => speak(text)}>Speak</button>
65
+ <button onClick={cancel}>Cancel</button>
66
+ <p>{speaking ? 'Speaking...' : paused ? 'Paused' : 'Not speaking'}</p>
67
+ <select>
68
+ {availableVoices.map((voice) => (
69
+ <option key={voice.name} value={voice.name}>
70
+ {voice.name}
71
+ </option>
72
+ ))}
73
+ </select>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default App;
79
+ ```
80
+
81
+ This example creates a simple text-to-speech application with an input field, a speak button, a cancel button, and a dropdown menu to select voices.
82
+
83
+ ## API
84
+
85
+ ### `speak(text, options)`
86
+
87
+ - `text`: The text to be spoken.
88
+ - `options`: An object with the following optional properties:
89
+ - `voice`: The voice to use for the speech.
90
+ - `language`: The language to use for the speech.
91
+ - `pitch`: The pitch of the speech (0.1 to 2).
92
+ - `rate`: The rate of the speech (0.1 to 10).
93
+
94
+ ### `speaking`
95
+
96
+ - A boolean value indicating whether the speech is currently being spoken.
97
+
98
+ ### `paused`
99
+
100
+ - A boolean value indicating whether the speech is currently paused.
101
+
102
+ ### `availableVoices`
103
+
104
+ - An array of available voices for the speech.
105
+
106
+ ### `availableLanguages`
107
+
108
+ - An array of available languages for the speech.
109
+
110
+ ### `cancel()`
111
+
112
+ - A function to cancel the speech.
113
+
114
+ # useTextToSpeech Hook
115
+
116
+ The `useTextToSpeech` hook provides a simple way to add text-to-speech capabilities to your React components.
117
+
118
+ ## API
119
+
120
+ ```typescript
121
+ const {
122
+ speak,
123
+ pause,
124
+ resume,
125
+ cancel,
126
+ isSpeaking,
127
+ isPaused,
128
+ error
129
+ } = useTextToSpeech(options);
130
+ ```
131
+
132
+ ### Parameters
133
+
134
+ - `options` (optional): An object containing configuration options for the text-to-speech functionality.
135
+ - `rate` (optional): Speech rate (0.1 to 10). Default is 1.
136
+ - `pitch` (optional): Speech pitch (0 to 2). Default is 1.
137
+ - `volume` (optional): Speech volume (0 to 1). Default is 1.
138
+ - `voice` (optional): A `SpeechSynthesisVoice` object to use for speech.
139
+
140
+ ### Return Value
141
+
142
+ - `speak`: Function to start speaking the provided text.
143
+ - `pause`: Function to pause the current speech.
144
+ - `resume`: Function to resume paused speech.
145
+ - `cancel`: Function to cancel the current speech.
146
+ - `isSpeaking`: Boolean indicating if speech is currently in progress.
147
+ - `isPaused`: Boolean indicating if speech is currently paused.
148
+ - `error`: Any error that occurred during speech synthesis.
149
+
150
+ ## Best Practices
151
+
152
+ 1. Always provide visual feedback about the current state (speaking, paused, idle).
153
+ 2. Handle errors gracefully and inform the user when something goes wrong.
154
+ 3. Use appropriate ARIA attributes for accessibility when implementing custom controls.
155
+ 4. Consider providing options to customize the voice, if supported by the browser.
156
+ 5. Test the component across different browsers and devices to ensure consistent behavior.
157
+
158
+ ---
159
+
160
+ ## Browser Support
161
+
162
+ The text-to-speech functionality relies on the Web Speech API, which is supported in most modern browsers. However, it's always a good idea to check for compatibility and provide fallback content or functionality for unsupported browsers.
163
+
164
+ ## Notes
165
+
166
+ - The `useTextToSpeech` hook requires a user gesture to be triggered, such as a button click.
167
+ - The `availableVoices` and `availableLanguages` arrays are dynamically generated based on the browser's capabilities.
168
+ - The `pitch` and `rate` values are normalized to a range of 0.1 to 2 and 0.1 to 10, respectively.
169
+
170
+ ## Browser Support
171
+
172
+ The Web Speech API is supported in most modern browsers, including Chrome, Edge, and some versions of Firefox. However, the availability of voices and languages may vary by browser.
173
+
174
+ ## Contributing
175
+
176
+ We welcome contributions to the FPKit React library. If you find any issues or have suggestions for improvements, please open an issue or submit a pull request on our GitHub repository.
177
+
178
+ ## License
179
+
180
+ This project is licensed under the MIT License. See the LICENSE file for more details.
181
+
182
+
@@ -0,0 +1,176 @@
1
+ import { useState, useEffect } from 'react'
2
+
3
+ /**
4
+ * Options for configuring speech synthesis.
5
+ * @interface SpeechOptions
6
+ */
7
+ interface SpeechOptions {
8
+ /** The language for speech synthesis (e.g., 'en-US') */
9
+ lang?: string
10
+
11
+ /** The pitch of the voice (0 to 2) */
12
+ pitch?: number
13
+ /** The speed of the voice (0.1 to 10) */
14
+ rate?: number
15
+
16
+ /** The voice to use for speech synthesis */
17
+ voice?: SpeechSynthesisVoice
18
+ }
19
+ /**
20
+ * Custom hook to handle text-to-speech functionality.
21
+ *
22
+ * @param {SpeechSynthesisVoice} [initialVoice] - The initial voice to use for speech synthesis.
23
+ * @returns {Object} An object containing methods to control speech synthesis and state variables.
24
+ */
25
+ export const useTextToSpeech = (initialVoice?: SpeechSynthesisVoice) => {
26
+ const [availableVoices, setAvailableVoices] = useState<
27
+ SpeechSynthesisVoice[]
28
+ >([])
29
+ const [currentVoice, setCurrentVoice] = useState<
30
+ SpeechSynthesisVoice | undefined
31
+ >(initialVoice)
32
+
33
+ const [isSpeaking, setIsSpeaking] = useState<boolean>(false)
34
+ const [isPaused, setIsPaused] = useState<boolean>(false)
35
+ const [utterance, setUtterance] = useState<SpeechSynthesisUtterance | null>(
36
+ null,
37
+ )
38
+
39
+ useEffect(() => {
40
+ const updateVoices = () => {
41
+ const voices = window.speechSynthesis.getVoices()
42
+ setAvailableVoices(voices)
43
+
44
+ // Set default voice to Google US English if available
45
+ const googleVoice = voices.find(
46
+ (voice) => voice.name === 'Google US English',
47
+ )
48
+ if (googleVoice) {
49
+ setCurrentVoice(googleVoice)
50
+ } else {
51
+ // Fallback to the first English voice if Google voice is not available
52
+ const englishVoice = voices.find((voice) =>
53
+ voice.lang.startsWith('en-'),
54
+ )
55
+ if (englishVoice) {
56
+ setCurrentVoice(englishVoice)
57
+ }
58
+ }
59
+ }
60
+
61
+ updateVoices()
62
+ window.speechSynthesis.onvoiceschanged = updateVoices
63
+
64
+ return () => {
65
+ window.speechSynthesis.onvoiceschanged = null
66
+ }
67
+ }, [])
68
+
69
+ /**
70
+ * Gets the list of available languages for speech synthesis.
71
+ * @returns {string[]} An array of available language codes.
72
+ */
73
+ const getAvailableLanguages = () => {
74
+ return [...new Set(availableVoices.map((voice) => voice.lang))]
75
+ }
76
+
77
+ /**
78
+ * Initiates speech synthesis for the given text.
79
+ *
80
+ * @param {string} text - The text to be spoken.
81
+ * @param {SpeechOptions} [options={}] - Options for speech synthesis.
82
+ * @param {Function} [onEnd] - Callback function to be called when speech ends.
83
+ */
84
+ const speak = (
85
+ text: string,
86
+ options: SpeechOptions = {},
87
+ onEnd?: () => void,
88
+ ) => {
89
+ const utterance = new SpeechSynthesisUtterance(text)
90
+
91
+ utterance.lang = options.lang ?? 'en-US'
92
+ utterance.pitch = options.pitch ?? 1
93
+ utterance.rate = options.rate ?? 1
94
+ utterance.voice = currentVoice ?? options.voice ?? null
95
+
96
+ utterance.onend = () => {
97
+ setIsSpeaking(false)
98
+ setIsPaused(false)
99
+ if (onEnd) {
100
+ onEnd()
101
+ }
102
+ }
103
+
104
+ if ('speechSynthesis' in window) {
105
+ window.speechSynthesis.speak(utterance)
106
+ setUtterance(utterance)
107
+ setIsSpeaking(true)
108
+ setIsPaused(false)
109
+ } else {
110
+ console.error('Speech synthesis not supported')
111
+ // Handle the error appropriately
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Changes the current voice used for speech synthesis.
117
+ * @param {SpeechSynthesisVoice} voice - The new voice to use.
118
+ */
119
+ const changeVoice = (voice: SpeechSynthesisVoice) => {
120
+ setCurrentVoice(voice)
121
+ }
122
+
123
+ /**
124
+ * Pauses the ongoing speech synthesis.
125
+ */
126
+ const pause = () => {
127
+ if (isSpeaking && !isPaused) {
128
+ window.speechSynthesis.pause()
129
+ setIsPaused(true)
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Resumes the paused speech synthesis.
135
+ */
136
+ const resume = () => {
137
+ if (isSpeaking && isPaused) {
138
+ window.speechSynthesis.resume()
139
+ setIsPaused(false)
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Cancels the ongoing speech synthesis.
145
+ */
146
+ const cancel = () => {
147
+ if (isSpeaking) {
148
+ window.speechSynthesis.cancel()
149
+ setIsSpeaking(false)
150
+ setIsPaused(false)
151
+ }
152
+ }
153
+
154
+ return {
155
+ /** Initiates speech synthesis for the given text */
156
+ speak,
157
+ /** Pauses the ongoing speech synthesis */
158
+ pause,
159
+ /** Resumes the paused speech synthesis */
160
+ resume,
161
+ /** Cancels the ongoing speech synthesis */
162
+ cancel,
163
+ /** Indicates whether speech synthesis is currently active */
164
+ isSpeaking,
165
+ /** Indicates whether speech synthesis is currently paused */
166
+ isPaused,
167
+ /** Array of available voices for speech synthesis */
168
+ availableVoices,
169
+ /** Changes the current voice used for speech synthesis */
170
+ changeVoice,
171
+ /** The currently selected voice for speech synthesis */
172
+ currentVoice,
173
+ /** Gets the list of available languages for speech synthesis */
174
+ getAvailableLanguages,
175
+ }
176
+ }
@@ -0,0 +1,117 @@
1
+ import React from 'react'
2
+ import Icon from '#components/icons/icon'
3
+ import UI from '#components/ui'
4
+ import { FC } from 'react'
5
+
6
+ /**
7
+ * Props for the TextToSpeechControls component.
8
+ * @interface TextToSpeechControlsProps
9
+ */
10
+ interface TextToSpeechControlsProps {
11
+ /** Optional label for the controls */
12
+ label?: string | React.ReactNode
13
+ /** Indicates if the text-to-speech is currently speaking */
14
+ isSpeaking: boolean
15
+ /** Indicates if the text-to-speech is paused */
16
+ isPaused: boolean
17
+ /** Function to start speaking */
18
+ onSpeak: () => void
19
+ /** Function to pause speaking */
20
+ onPause: () => void
21
+ /** Function to resume speaking */
22
+ onResume: () => void
23
+ /** Function to cancel speaking */
24
+ onCancel: () => void
25
+ }
26
+
27
+ /**
28
+ * TTSButtonComponent props
29
+ * @interface TTSButtonComponentProps
30
+ */
31
+ interface TTSButtonComponentProps {
32
+ /** The content of the button */
33
+ children: React.ReactNode
34
+ /** Function to call when the button is clicked */
35
+ onClick: () => void
36
+ }
37
+
38
+ /**
39
+ * TTSButtonComponent is a reusable button component for text-to-speech controls.
40
+ * @param {TTSButtonComponentProps} props - The component props
41
+ * @returns {React.ReactElement} The rendered button
42
+ */
43
+ export const TTSButtonComponent: React.FC<TTSButtonComponentProps> = ({
44
+ children,
45
+ onClick,
46
+ }) => {
47
+ return (
48
+ <UI
49
+ as="button"
50
+ type="button"
51
+ className="tts-border"
52
+ data-btn="sm text pill"
53
+ onClick={onClick}
54
+ >
55
+ {children}
56
+ </UI>
57
+ )
58
+ }
59
+
60
+ export const TTSButton = React.memo(TTSButtonComponent)
61
+
62
+ /**
63
+ * TextToSpeechControlsComponent interface extends FC<TextToSpeechControlsProps>
64
+ * and includes a TTSButton property.
65
+ * @interface TextToSpeechControlsComponent
66
+ * @extends {FC<TextToSpeechControlsProps>}
67
+ */
68
+ interface TextToSpeechControlsComponent extends FC<TextToSpeechControlsProps> {
69
+ /** The TTSButton component used within TextToSpeechControls */
70
+ TTSButton: typeof TTSButton
71
+ }
72
+
73
+ /**
74
+ * TextToSpeechControls component provides a user interface for controlling text-to-speech functionality.
75
+ * @param {TextToSpeechControlsProps} props - The component props
76
+ * @returns {React.ReactElement} The rendered TextToSpeechControls component
77
+ */
78
+ const TextToSpeechControls: TextToSpeechControlsComponent = ({
79
+ label,
80
+ isSpeaking,
81
+ isPaused,
82
+ onSpeak,
83
+ onPause,
84
+ onResume,
85
+ onCancel,
86
+ }) => {
87
+ const iconSize = 16
88
+
89
+ return (
90
+ <UI as="div" data-tts>
91
+ {label && <p>{label}</p>}
92
+ {!isSpeaking && (
93
+ <TTSButton aria-label="Speak" onClick={onSpeak}>
94
+ <Icon.PlaySolid size={iconSize} />
95
+ </TTSButton>
96
+ )}
97
+ {isSpeaking && !isPaused && (
98
+ <TTSButton aria-label="Pause" onClick={onPause}>
99
+ <Icon.PauseSolid size={iconSize} />
100
+ </TTSButton>
101
+ )}
102
+ {isPaused && (
103
+ <TTSButton aria-label="Resume" onClick={onResume}>
104
+ <Icon.ResumeSolid size={iconSize} />
105
+ </TTSButton>
106
+ )}
107
+ <TTSButton aria-label="Stop" onClick={onCancel}>
108
+ <Icon.StopSolid size={iconSize} />
109
+ </TTSButton>
110
+ </UI>
111
+ )
112
+ }
113
+
114
+ TextToSpeechControls.displayName = 'TextToSpeechControls'
115
+ TextToSpeechControls.TTSButton = TTSButton
116
+
117
+ export default TextToSpeechControls
@@ -0,0 +1,67 @@
1
+ /* eslint-enable react/display-name */
2
+ import React from 'react'
3
+
4
+ type PolymorphicRef<C extends React.ElementType> =
5
+ React.ComponentPropsWithRef<C>['ref']
6
+
7
+ type AsProp<C extends React.ElementType> = {
8
+ as?: C
9
+ }
10
+
11
+ type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P)
12
+
13
+ type PolymorphicComponentProp<
14
+ C extends React.ElementType,
15
+ Props = {},
16
+ > = React.PropsWithChildren<Props & AsProp<C>> &
17
+ Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>
18
+
19
+ type PolymorphicComponentPropWithRef<
20
+ C extends React.ElementType,
21
+ Props = {},
22
+ > = PolymorphicComponentProp<C, Props> & {
23
+ ref?: PolymorphicRef<C>
24
+ }
25
+
26
+ type FPProps<C extends React.ElementType> = PolymorphicComponentPropWithRef<
27
+ C,
28
+ {
29
+ renderStyles?: boolean
30
+ styles?: React.CSSProperties
31
+ classes?: string
32
+ id?: string
33
+ children?: React.ReactNode
34
+ }
35
+ >
36
+
37
+ /*
38
+ * FPComponent type definition
39
+ *
40
+ * Defines the component function signature for the FP component.
41
+ *
42
+ * @typeParam C - The HTML element type to render
43
+ * @param props - The component props
44
+ * @returns React component
45
+ */
46
+ type FPComponent = <C extends React.ElementType = 'span'>(
47
+ props: FPProps<C>,
48
+ ) => React.ReactElement | any
49
+
50
+ const FP: FPComponent = React.forwardRef(
51
+ <C extends React.ElementType>(
52
+ { as, styles, classes, children, defaultStyles, ...props }: FPProps<C>,
53
+ ref?: PolymorphicRef<C>,
54
+ ) => {
55
+ const Component = as || 'div'
56
+
57
+ const styleObj: React.CSSProperties = { ...defaultStyles, ...styles }
58
+
59
+ return (
60
+ <Component ref={ref} style={styleObj} className={classes} {...props}>
61
+ {children}
62
+ </Component>
63
+ )
64
+ },
65
+ )
66
+
67
+ export default FP
@@ -0,0 +1,15 @@
1
+ <svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
3
+ <path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
4
+ <defs>
5
+ <linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
6
+ <stop stop-color="#41D1FF"/>
7
+ <stop offset="1" stop-color="#BD34FE"/>
8
+ </linearGradient>
9
+ <linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
10
+ <stop stop-color="#FFEA83"/>
11
+ <stop offset="0.0833333" stop-color="#FFDD35"/>
12
+ <stop offset="1" stop-color="#FFA800"/>
13
+ </linearGradient>
14
+ </defs>
15
+ </svg>
@@ -0,0 +1,88 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`Popover > should show popover on button click 1`] = `
4
+ {
5
+ "asFragment": [Function],
6
+ "baseElement": <body>
7
+ <div>
8
+ <div
9
+ data-testid="popover"
10
+ >
11
+ <button>
12
+ Hover here
13
+ </button>
14
+ <div
15
+ style="display: block; position: absolute; background: rgb(0, 0, 0); border: 1px solid #ccc; padding: 10px; color: rgb(255, 255, 255); top: 1px; left: 0px; transition: opacity .5s ease-in-out; opacity: 1; transform: translateY(0px);"
16
+ >
17
+ This is a popover.
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </body>,
22
+ "container": <div>
23
+ <div
24
+ data-testid="popover"
25
+ >
26
+ <button>
27
+ Hover here
28
+ </button>
29
+ <div
30
+ style="display: block; position: absolute; background: rgb(0, 0, 0); border: 1px solid #ccc; padding: 10px; color: rgb(255, 255, 255); top: 1px; left: 0px; transition: opacity .5s ease-in-out; opacity: 1; transform: translateY(0px);"
31
+ >
32
+ This is a popover.
33
+ </div>
34
+ </div>
35
+ </div>,
36
+ "debug": [Function],
37
+ "findAllByAltText": [Function],
38
+ "findAllByDisplayValue": [Function],
39
+ "findAllByLabelText": [Function],
40
+ "findAllByPlaceholderText": [Function],
41
+ "findAllByRole": [Function],
42
+ "findAllByTestId": [Function],
43
+ "findAllByText": [Function],
44
+ "findAllByTitle": [Function],
45
+ "findByAltText": [Function],
46
+ "findByDisplayValue": [Function],
47
+ "findByLabelText": [Function],
48
+ "findByPlaceholderText": [Function],
49
+ "findByRole": [Function],
50
+ "findByTestId": [Function],
51
+ "findByText": [Function],
52
+ "findByTitle": [Function],
53
+ "getAllByAltText": [Function],
54
+ "getAllByDisplayValue": [Function],
55
+ "getAllByLabelText": [Function],
56
+ "getAllByPlaceholderText": [Function],
57
+ "getAllByRole": [Function],
58
+ "getAllByTestId": [Function],
59
+ "getAllByText": [Function],
60
+ "getAllByTitle": [Function],
61
+ "getByAltText": [Function],
62
+ "getByDisplayValue": [Function],
63
+ "getByLabelText": [Function],
64
+ "getByPlaceholderText": [Function],
65
+ "getByRole": [Function],
66
+ "getByTestId": [Function],
67
+ "getByText": [Function],
68
+ "getByTitle": [Function],
69
+ "queryAllByAltText": [Function],
70
+ "queryAllByDisplayValue": [Function],
71
+ "queryAllByLabelText": [Function],
72
+ "queryAllByPlaceholderText": [Function],
73
+ "queryAllByRole": [Function],
74
+ "queryAllByTestId": [Function],
75
+ "queryAllByText": [Function],
76
+ "queryAllByTitle": [Function],
77
+ "queryByAltText": [Function],
78
+ "queryByDisplayValue": [Function],
79
+ "queryByLabelText": [Function],
80
+ "queryByPlaceholderText": [Function],
81
+ "queryByRole": [Function],
82
+ "queryByTestId": [Function],
83
+ "queryByText": [Function],
84
+ "queryByTitle": [Function],
85
+ "rerender": [Function],
86
+ "unmount": [Function],
87
+ }
88
+ `;
@@ -0,0 +1 @@
1
+ {"version":"0.31.1","results":[[":popover.test.tsx",{"duration":0,"failed":true}]]}