@revikornmann/muka-ui 0.16.0 → 0.18.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.
Files changed (204) hide show
  1. package/dist/cjs/components/ActionSheet/ActionSheet.js +1 -1
  2. package/dist/cjs/components/Breadcrumb/Breadcrumb.css +11 -3
  3. package/dist/cjs/components/Breadcrumb/Breadcrumb.js +19 -1
  4. package/dist/cjs/components/Breadcrumb/Breadcrumb.js.map +1 -1
  5. package/dist/cjs/components/ChatComposer/ChatComposer.js +12 -4
  6. package/dist/cjs/components/ChatComposer/ChatComposer.js.map +1 -1
  7. package/dist/cjs/components/ContextSelect/ContextSelect.css +117 -0
  8. package/dist/cjs/components/ContextSelect/ContextSelect.js +49 -0
  9. package/dist/cjs/components/ContextSelect/ContextSelect.js.map +1 -0
  10. package/dist/cjs/components/ContextSelect/index.js +11 -0
  11. package/dist/cjs/components/ContextSelect/index.js.map +1 -0
  12. package/dist/cjs/components/EmojiPicker/EmojiPicker.css +19 -0
  13. package/dist/cjs/components/EmojiPicker/EmojiPicker.js +54 -0
  14. package/dist/cjs/components/EmojiPicker/EmojiPicker.js.map +1 -0
  15. package/dist/cjs/components/EmojiPicker/index.js +12 -0
  16. package/dist/cjs/components/EmojiPicker/index.js.map +1 -0
  17. package/dist/cjs/components/Icon/custom/ArrowDropUpDownIcon.js +11 -0
  18. package/dist/cjs/components/Icon/custom/ArrowDropUpDownIcon.js.map +1 -0
  19. package/dist/cjs/components/Icon/iconRegistry.js +6 -0
  20. package/dist/cjs/components/Icon/iconRegistry.js.map +1 -1
  21. package/dist/cjs/components/Menu/Menu.css +112 -2
  22. package/dist/cjs/components/Menu/Menu.js +171 -39
  23. package/dist/cjs/components/Menu/Menu.js.map +1 -1
  24. package/dist/cjs/components/Menu/index.js +5 -1
  25. package/dist/cjs/components/Menu/index.js.map +1 -1
  26. package/dist/cjs/components/PhotoDraftStrip/PhotoDraftStrip.css +27 -0
  27. package/dist/cjs/components/PhotoDraftStrip/PhotoDraftStrip.js +36 -0
  28. package/dist/cjs/components/PhotoDraftStrip/PhotoDraftStrip.js.map +1 -0
  29. package/dist/cjs/components/PhotoDraftStrip/index.js +11 -0
  30. package/dist/cjs/components/PhotoDraftStrip/index.js.map +1 -0
  31. package/dist/cjs/components/RecordingBar/RecordingBar.css +47 -0
  32. package/dist/cjs/components/RecordingBar/RecordingBar.js +22 -0
  33. package/dist/cjs/components/RecordingBar/RecordingBar.js.map +1 -0
  34. package/dist/cjs/components/RecordingBar/index.js +11 -0
  35. package/dist/cjs/components/RecordingBar/index.js.map +1 -0
  36. package/dist/cjs/components/Scrollbar/Scrollbar.css +123 -0
  37. package/dist/cjs/components/Scrollbar/Scrollbar.js +118 -0
  38. package/dist/cjs/components/Scrollbar/Scrollbar.js.map +1 -0
  39. package/dist/cjs/components/Scrollbar/index.js +11 -0
  40. package/dist/cjs/components/Scrollbar/index.js.map +1 -0
  41. package/dist/cjs/components/VoiceMessage/VoiceMessage.css +29 -0
  42. package/dist/cjs/components/VoiceMessage/VoiceMessage.js +85 -0
  43. package/dist/cjs/components/VoiceMessage/VoiceMessage.js.map +1 -0
  44. package/dist/cjs/components/VoiceMessage/index.js +11 -0
  45. package/dist/cjs/components/VoiceMessage/index.js.map +1 -0
  46. package/dist/cjs/components/VoiceRecorder/audio.js +61 -0
  47. package/dist/cjs/components/VoiceRecorder/audio.js.map +1 -0
  48. package/dist/cjs/components/VoiceRecorder/index.js +16 -0
  49. package/dist/cjs/components/VoiceRecorder/index.js.map +1 -0
  50. package/dist/cjs/components/VoiceRecorder/useVoiceRecorder.js +213 -0
  51. package/dist/cjs/components/VoiceRecorder/useVoiceRecorder.js.map +1 -0
  52. package/dist/cjs/components/Waveform/Waveform.css +23 -0
  53. package/dist/cjs/components/Waveform/Waveform.js +36 -0
  54. package/dist/cjs/components/Waveform/Waveform.js.map +1 -0
  55. package/dist/cjs/components/Waveform/index.js +11 -0
  56. package/dist/cjs/components/Waveform/index.js.map +1 -0
  57. package/dist/cjs/components/index.js +27 -15
  58. package/dist/cjs/components/index.js.map +1 -1
  59. package/dist/esm/components/ActionSheet/ActionSheet.js +1 -1
  60. package/dist/esm/components/Breadcrumb/Breadcrumb.css +11 -3
  61. package/dist/esm/components/Breadcrumb/Breadcrumb.js +19 -1
  62. package/dist/esm/components/Breadcrumb/Breadcrumb.js.map +1 -1
  63. package/dist/esm/components/ChatComposer/ChatComposer.js +12 -4
  64. package/dist/esm/components/ChatComposer/ChatComposer.js.map +1 -1
  65. package/dist/esm/components/ContextSelect/ContextSelect.css +117 -0
  66. package/dist/esm/components/ContextSelect/ContextSelect.js +45 -0
  67. package/dist/esm/components/ContextSelect/ContextSelect.js.map +1 -0
  68. package/dist/esm/components/ContextSelect/index.js +3 -0
  69. package/dist/esm/components/ContextSelect/index.js.map +1 -0
  70. package/dist/esm/components/EmojiPicker/EmojiPicker.css +19 -0
  71. package/dist/esm/components/EmojiPicker/EmojiPicker.js +50 -0
  72. package/dist/esm/components/EmojiPicker/EmojiPicker.js.map +1 -0
  73. package/dist/esm/components/EmojiPicker/index.js +3 -0
  74. package/dist/esm/components/EmojiPicker/index.js.map +1 -0
  75. package/dist/esm/components/Icon/custom/ArrowDropUpDownIcon.js +7 -0
  76. package/dist/esm/components/Icon/custom/ArrowDropUpDownIcon.js.map +1 -0
  77. package/dist/esm/components/Icon/iconRegistry.js +6 -0
  78. package/dist/esm/components/Icon/iconRegistry.js.map +1 -1
  79. package/dist/esm/components/Menu/Menu.css +112 -2
  80. package/dist/esm/components/Menu/Menu.js +170 -40
  81. package/dist/esm/components/Menu/Menu.js.map +1 -1
  82. package/dist/esm/components/Menu/index.js +1 -1
  83. package/dist/esm/components/Menu/index.js.map +1 -1
  84. package/dist/esm/components/PhotoDraftStrip/PhotoDraftStrip.css +27 -0
  85. package/dist/esm/components/PhotoDraftStrip/PhotoDraftStrip.js +32 -0
  86. package/dist/esm/components/PhotoDraftStrip/PhotoDraftStrip.js.map +1 -0
  87. package/dist/esm/components/PhotoDraftStrip/index.js +3 -0
  88. package/dist/esm/components/PhotoDraftStrip/index.js.map +1 -0
  89. package/dist/esm/components/RecordingBar/RecordingBar.css +47 -0
  90. package/dist/esm/components/RecordingBar/RecordingBar.js +18 -0
  91. package/dist/esm/components/RecordingBar/RecordingBar.js.map +1 -0
  92. package/dist/esm/components/RecordingBar/index.js +3 -0
  93. package/dist/esm/components/RecordingBar/index.js.map +1 -0
  94. package/dist/esm/components/Scrollbar/Scrollbar.css +123 -0
  95. package/dist/esm/components/Scrollbar/Scrollbar.js +114 -0
  96. package/dist/esm/components/Scrollbar/Scrollbar.js.map +1 -0
  97. package/dist/esm/components/Scrollbar/index.js +3 -0
  98. package/dist/esm/components/Scrollbar/index.js.map +1 -0
  99. package/dist/esm/components/VoiceMessage/VoiceMessage.css +29 -0
  100. package/dist/esm/components/VoiceMessage/VoiceMessage.js +81 -0
  101. package/dist/esm/components/VoiceMessage/VoiceMessage.js.map +1 -0
  102. package/dist/esm/components/VoiceMessage/index.js +3 -0
  103. package/dist/esm/components/VoiceMessage/index.js.map +1 -0
  104. package/dist/esm/components/VoiceRecorder/audio.js +55 -0
  105. package/dist/esm/components/VoiceRecorder/audio.js.map +1 -0
  106. package/dist/esm/components/VoiceRecorder/index.js +4 -0
  107. package/dist/esm/components/VoiceRecorder/index.js.map +1 -0
  108. package/dist/esm/components/VoiceRecorder/useVoiceRecorder.js +210 -0
  109. package/dist/esm/components/VoiceRecorder/useVoiceRecorder.js.map +1 -0
  110. package/dist/esm/components/Waveform/Waveform.css +23 -0
  111. package/dist/esm/components/Waveform/Waveform.js +32 -0
  112. package/dist/esm/components/Waveform/Waveform.js.map +1 -0
  113. package/dist/esm/components/Waveform/index.js +3 -0
  114. package/dist/esm/components/Waveform/index.js.map +1 -0
  115. package/dist/esm/components/index.js +9 -2
  116. package/dist/esm/components/index.js.map +1 -1
  117. package/dist/styles/components/Breadcrumb.css +11 -3
  118. package/dist/styles/components/ContextSelect.css +117 -0
  119. package/dist/styles/components/EmojiPicker.css +19 -0
  120. package/dist/styles/components/Menu.css +112 -2
  121. package/dist/styles/components/PhotoDraftStrip.css +27 -0
  122. package/dist/styles/components/RecordingBar.css +47 -0
  123. package/dist/styles/components/Scrollbar.css +123 -0
  124. package/dist/styles/components/VoiceMessage.css +29 -0
  125. package/dist/styles/components/Waveform.css +23 -0
  126. package/dist/styles/index.css +919 -393
  127. package/dist/styles/muka-dark.css +919 -393
  128. package/dist/styles/muka-light.css +919 -393
  129. package/dist/styles/tokens-bouwplan-dark.css +4 -2
  130. package/dist/styles/tokens-bouwplan-light.css +4 -2
  131. package/dist/styles/tokens-fscl-dark.css +4 -2
  132. package/dist/styles/tokens-fscl-light.css +4 -2
  133. package/dist/styles/tokens-grip-dark.css +4 -2
  134. package/dist/styles/tokens-grip-light.css +4 -2
  135. package/dist/styles/tokens-muka-dark.css +4 -2
  136. package/dist/styles/tokens-muka-light.css +4 -2
  137. package/dist/styles/tokens-wireframe-dark.css +4 -2
  138. package/dist/styles/tokens-wireframe-light.css +4 -2
  139. package/dist/styles/wireframe-dark.css +919 -393
  140. package/dist/styles/wireframe-light.css +919 -393
  141. package/dist/types/components/ActionSheet/ActionSheet.d.ts +1 -1
  142. package/dist/types/components/Breadcrumb/Breadcrumb.d.ts +27 -3
  143. package/dist/types/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
  144. package/dist/types/components/ChatComposer/ChatComposer.d.ts +7 -0
  145. package/dist/types/components/ChatComposer/ChatComposer.d.ts.map +1 -1
  146. package/dist/types/components/ContextSelect/ContextSelect.d.ts +62 -0
  147. package/dist/types/components/ContextSelect/ContextSelect.d.ts.map +1 -0
  148. package/dist/types/components/ContextSelect/index.d.ts +3 -0
  149. package/dist/types/components/ContextSelect/index.d.ts.map +1 -0
  150. package/dist/types/components/DataTable/DataTable.d.ts +1 -1
  151. package/dist/types/components/EmojiPicker/EmojiPicker.d.ts +28 -0
  152. package/dist/types/components/EmojiPicker/EmojiPicker.d.ts.map +1 -0
  153. package/dist/types/components/EmojiPicker/index.d.ts +3 -0
  154. package/dist/types/components/EmojiPicker/index.d.ts.map +1 -0
  155. package/dist/types/components/Icon/custom/ArrowDropUpDownIcon.d.ts +9 -0
  156. package/dist/types/components/Icon/custom/ArrowDropUpDownIcon.d.ts.map +1 -0
  157. package/dist/types/components/Icon/iconRegistry.d.ts.map +1 -1
  158. package/dist/types/components/Menu/Menu.d.ts +58 -28
  159. package/dist/types/components/Menu/Menu.d.ts.map +1 -1
  160. package/dist/types/components/Menu/index.d.ts +2 -2
  161. package/dist/types/components/Menu/index.d.ts.map +1 -1
  162. package/dist/types/components/PhotoDraftStrip/PhotoDraftStrip.d.ts +51 -0
  163. package/dist/types/components/PhotoDraftStrip/PhotoDraftStrip.d.ts.map +1 -0
  164. package/dist/types/components/PhotoDraftStrip/index.d.ts +3 -0
  165. package/dist/types/components/PhotoDraftStrip/index.d.ts.map +1 -0
  166. package/dist/types/components/RecordingBar/RecordingBar.d.ts +22 -0
  167. package/dist/types/components/RecordingBar/RecordingBar.d.ts.map +1 -0
  168. package/dist/types/components/RecordingBar/index.d.ts +3 -0
  169. package/dist/types/components/RecordingBar/index.d.ts.map +1 -0
  170. package/dist/types/components/Scrollbar/Scrollbar.d.ts +19 -0
  171. package/dist/types/components/Scrollbar/Scrollbar.d.ts.map +1 -0
  172. package/dist/types/components/Scrollbar/index.d.ts +3 -0
  173. package/dist/types/components/Scrollbar/index.d.ts.map +1 -0
  174. package/dist/types/components/VoiceMessage/VoiceMessage.d.ts +34 -0
  175. package/dist/types/components/VoiceMessage/VoiceMessage.d.ts.map +1 -0
  176. package/dist/types/components/VoiceMessage/index.d.ts +3 -0
  177. package/dist/types/components/VoiceMessage/index.d.ts.map +1 -0
  178. package/dist/types/components/VoiceRecorder/audio.d.ts +20 -0
  179. package/dist/types/components/VoiceRecorder/audio.d.ts.map +1 -0
  180. package/dist/types/components/VoiceRecorder/index.d.ts +4 -0
  181. package/dist/types/components/VoiceRecorder/index.d.ts.map +1 -0
  182. package/dist/types/components/VoiceRecorder/useVoiceRecorder.d.ts +48 -0
  183. package/dist/types/components/VoiceRecorder/useVoiceRecorder.d.ts.map +1 -0
  184. package/dist/types/components/Waveform/Waveform.d.ts +31 -0
  185. package/dist/types/components/Waveform/Waveform.d.ts.map +1 -0
  186. package/dist/types/components/Waveform/index.d.ts +3 -0
  187. package/dist/types/components/Waveform/index.d.ts.map +1 -0
  188. package/dist/types/components/index.d.ts +10 -4
  189. package/dist/types/components/index.d.ts.map +1 -1
  190. package/package.json +1 -1
  191. package/tokens/README.md +1 -1
  192. package/tokens/t4-components/menu.json +12 -2
  193. package/dist/cjs/components/ContextMenu/ContextMenu.js +0 -186
  194. package/dist/cjs/components/ContextMenu/ContextMenu.js.map +0 -1
  195. package/dist/cjs/components/ContextMenu/index.js +0 -17
  196. package/dist/cjs/components/ContextMenu/index.js.map +0 -1
  197. package/dist/esm/components/ContextMenu/ContextMenu.js +0 -145
  198. package/dist/esm/components/ContextMenu/ContextMenu.js.map +0 -1
  199. package/dist/esm/components/ContextMenu/index.js +0 -2
  200. package/dist/esm/components/ContextMenu/index.js.map +0 -1
  201. package/dist/types/components/ContextMenu/ContextMenu.d.ts +0 -188
  202. package/dist/types/components/ContextMenu/ContextMenu.d.ts.map +0 -1
  203. package/dist/types/components/ContextMenu/index.d.ts +0 -3
  204. package/dist/types/components/ContextMenu/index.d.ts.map +0 -1
@@ -0,0 +1,123 @@
1
+ /* Scrollbar — Figma menu scrollbar (32px rail, chevrons, grip thumb) */
2
+
3
+ .muka-scrollbar {
4
+ display: flex;
5
+ flex-shrink: 0;
6
+ border-left: var(--border-width-regular) solid var(--color-border-default);
7
+ background-color: var(--color-surface-level3);
8
+ user-select: none;
9
+ }
10
+
11
+ .muka-scrollbar--vertical {
12
+ flex-direction: column;
13
+ width: var(--size-2xl, 32px);
14
+ align-self: stretch;
15
+ }
16
+
17
+ .muka-scrollbar--horizontal {
18
+ flex-direction: row;
19
+ height: var(--size-2xl, 32px);
20
+ width: 100%;
21
+ border-left: none;
22
+ border-top: var(--border-width-regular) solid var(--color-border-default);
23
+ }
24
+
25
+ .muka-scrollbar__button {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ flex-shrink: 0;
30
+ width: 100%;
31
+ height: var(--size-2xl, 32px);
32
+ padding: 0;
33
+ border: none;
34
+ background-color: var(--button-color-tertiary-background-default, var(--color-surface-level3));
35
+ color: var(--color-text-default-default);
36
+ cursor: pointer;
37
+ }
38
+
39
+ .muka-scrollbar--horizontal .muka-scrollbar__button {
40
+ width: var(--size-2xl, 32px);
41
+ height: 100%;
42
+ }
43
+
44
+ .muka-scrollbar__button:hover {
45
+ background-color: var(--color-surface-hover);
46
+ }
47
+
48
+ .muka-scrollbar__divider {
49
+ flex-shrink: 0;
50
+ width: 100%;
51
+ height: 1px;
52
+ background-color: var(--color-border-default);
53
+ }
54
+
55
+ .muka-scrollbar--horizontal .muka-scrollbar__divider {
56
+ width: 1px;
57
+ height: 100%;
58
+ }
59
+
60
+ .muka-scrollbar__track {
61
+ position: relative;
62
+ display: flex;
63
+ flex-direction: column;
64
+ flex: 1 1 auto;
65
+ min-height: 0;
66
+ width: 100%;
67
+ }
68
+
69
+ .muka-scrollbar--horizontal .muka-scrollbar__track {
70
+ flex-direction: row;
71
+ min-width: 0;
72
+ height: 100%;
73
+ }
74
+
75
+ .muka-scrollbar__thumb {
76
+ position: absolute;
77
+ left: 50%;
78
+ top: 0;
79
+ z-index: 1;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ width: 14px;
84
+ margin-left: -7px;
85
+ cursor: grab;
86
+ touch-action: none;
87
+ }
88
+
89
+ .muka-scrollbar--horizontal .muka-scrollbar__thumb {
90
+ left: 0;
91
+ top: 50%;
92
+ width: auto;
93
+ height: 14px;
94
+ margin-left: 0;
95
+ margin-top: -7px;
96
+ }
97
+
98
+ .muka-scrollbar__thumb:active {
99
+ cursor: grabbing;
100
+ }
101
+
102
+ .muka-scrollbar__grip {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 2px;
106
+ width: 14px;
107
+ }
108
+
109
+ .muka-scrollbar__grip::before,
110
+ .muka-scrollbar__grip::after {
111
+ content: '';
112
+ display: block;
113
+ width: 100%;
114
+ height: 2px;
115
+ border-radius: var(--border-radius-md);
116
+ background-color: var(--color-text-subtle-default);
117
+ }
118
+
119
+ .muka-scrollbar__spacer {
120
+ flex: 1 1 auto;
121
+ min-height: 0;
122
+ background-color: var(--color-surface-level2);
123
+ }
@@ -0,0 +1,114 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useState } from 'react';
4
+ import { Icon } from '../Icon';
5
+ import './Scrollbar.css';
6
+ /**
7
+ * Scrollbar — custom vertical scrollbar matching the Figma menu scrollbar
8
+ * (chevron buttons + grip thumb on a 32px rail).
9
+ */
10
+ export const Scrollbar = ({ viewportRef, orientation = 'vertical', step = 48, className = '', }) => {
11
+ const [metrics, setMetrics] = useState({
12
+ scrollTop: 0,
13
+ scrollLeft: 0,
14
+ scrollHeight: 0,
15
+ scrollWidth: 0,
16
+ clientHeight: 0,
17
+ clientWidth: 0,
18
+ });
19
+ const updateMetrics = useCallback(() => {
20
+ const el = viewportRef.current;
21
+ if (!el)
22
+ return;
23
+ setMetrics({
24
+ scrollTop: el.scrollTop,
25
+ scrollLeft: el.scrollLeft,
26
+ scrollHeight: el.scrollHeight,
27
+ scrollWidth: el.scrollWidth,
28
+ clientHeight: el.clientHeight,
29
+ clientWidth: el.clientWidth,
30
+ });
31
+ }, [viewportRef]);
32
+ useEffect(() => {
33
+ const el = viewportRef.current;
34
+ if (!el)
35
+ return;
36
+ updateMetrics();
37
+ el.addEventListener('scroll', updateMetrics, { passive: true });
38
+ const resizeObserver = new ResizeObserver(updateMetrics);
39
+ resizeObserver.observe(el);
40
+ if (el.firstElementChild) {
41
+ resizeObserver.observe(el.firstElementChild);
42
+ }
43
+ return () => {
44
+ el.removeEventListener('scroll', updateMetrics);
45
+ resizeObserver.disconnect();
46
+ };
47
+ }, [viewportRef, updateMetrics]);
48
+ const isVertical = orientation === 'vertical';
49
+ const maxScroll = isVertical
50
+ ? metrics.scrollHeight - metrics.clientHeight
51
+ : metrics.scrollWidth - metrics.clientWidth;
52
+ if (maxScroll <= 0) {
53
+ return null;
54
+ }
55
+ const trackSize = isVertical ? metrics.clientHeight : metrics.clientWidth;
56
+ // Rail body between the two 32px chevron buttons + 2×1px dividers ≈ track - 66
57
+ const railBody = Math.max(trackSize - 66, 24);
58
+ const thumbRatio = isVertical
59
+ ? metrics.clientHeight / metrics.scrollHeight
60
+ : metrics.clientWidth / metrics.scrollWidth;
61
+ const thumbSize = Math.max(railBody * thumbRatio, 28);
62
+ const scrollPos = isVertical ? metrics.scrollTop : metrics.scrollLeft;
63
+ const thumbOffset = (scrollPos / maxScroll) * (railBody - thumbSize);
64
+ const scrollBy = (delta) => {
65
+ const el = viewportRef.current;
66
+ if (!el)
67
+ return;
68
+ if (isVertical) {
69
+ el.scrollBy({ top: delta, behavior: 'smooth' });
70
+ }
71
+ else {
72
+ el.scrollBy({ left: delta, behavior: 'smooth' });
73
+ }
74
+ };
75
+ const handleThumbPointerDown = (event) => {
76
+ event.preventDefault();
77
+ event.stopPropagation();
78
+ const el = viewportRef.current;
79
+ if (!el)
80
+ return;
81
+ const startPos = isVertical ? event.clientY : event.clientX;
82
+ const startScroll = isVertical ? el.scrollTop : el.scrollLeft;
83
+ const movable = railBody - thumbSize;
84
+ const onMove = (e) => {
85
+ const current = isVertical ? e.clientY : e.clientX;
86
+ const delta = current - startPos;
87
+ const next = startScroll + (delta / movable) * maxScroll;
88
+ if (isVertical) {
89
+ el.scrollTop = next;
90
+ }
91
+ else {
92
+ el.scrollLeft = next;
93
+ }
94
+ };
95
+ const onUp = () => {
96
+ window.removeEventListener('pointermove', onMove);
97
+ window.removeEventListener('pointerup', onUp);
98
+ };
99
+ window.addEventListener('pointermove', onMove);
100
+ window.addEventListener('pointerup', onUp);
101
+ };
102
+ const rootClasses = [
103
+ 'muka-scrollbar',
104
+ `muka-scrollbar--${orientation}`,
105
+ className,
106
+ ]
107
+ .filter(Boolean)
108
+ .join(' ');
109
+ return (_jsxs("div", { className: rootClasses, "aria-hidden": "true", children: [_jsx("button", { type: "button", className: "muka-scrollbar__button muka-scrollbar__button--start", tabIndex: -1, onClick: () => scrollBy(-step), "aria-label": isVertical ? 'Scroll up' : 'Scroll left', children: _jsx(Icon, { name: isVertical ? 'arrow-up-s' : 'arrow-left-s', size: "sm" }) }), _jsx("div", { className: "muka-scrollbar__divider" }), _jsxs("div", { className: "muka-scrollbar__track", children: [_jsx("div", { className: "muka-scrollbar__thumb", style: isVertical
110
+ ? { height: thumbSize, transform: `translateY(${thumbOffset}px)` }
111
+ : { width: thumbSize, transform: `translateX(${thumbOffset}px)` }, onPointerDown: handleThumbPointerDown, children: _jsx("span", { className: "muka-scrollbar__grip" }) }), _jsx("div", { className: "muka-scrollbar__spacer" })] }), _jsx("div", { className: "muka-scrollbar__divider" }), _jsx("button", { type: "button", className: "muka-scrollbar__button muka-scrollbar__button--end", tabIndex: -1, onClick: () => scrollBy(step), "aria-label": isVertical ? 'Scroll down' : 'Scroll right', children: _jsx(Icon, { name: isVertical ? 'arrow-down-s' : 'arrow-right-s', size: "sm" }) })] }));
112
+ };
113
+ export default Scrollbar;
114
+ //# sourceMappingURL=Scrollbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scrollbar.js","sourceRoot":"","sources":["../../../../components/Scrollbar/Scrollbar.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { Scrollbar } from './Scrollbar';
2
+ export { default } from './Scrollbar';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../components/Scrollbar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAuB,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,29 @@
1
+ /* VoiceMessage Component Styles */
2
+
3
+ .muka-voice-message {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: var(--spacing-3);
7
+ width: 100%;
8
+ min-width: min(16rem, 60vw);
9
+ }
10
+
11
+ .muka-voice-message__wave {
12
+ flex: 1 1 auto;
13
+ min-width: 0;
14
+ }
15
+
16
+ .muka-voice-message__time {
17
+ flex: 0 0 auto;
18
+ white-space: nowrap;
19
+ font-size: var(--text-label-sm-fontSize, 0.75rem);
20
+ line-height: var(--text-label-sm-lineHeight, 1);
21
+ font-variant-numeric: tabular-nums;
22
+ color: var(--color-text-subtle-default);
23
+ }
24
+
25
+ /* On the tinted own bubble the surface is the action color, so the timer flips
26
+ * to the inverse text token for contrast. */
27
+ .muka-voice-message--own .muka-voice-message__time {
28
+ color: var(--color-text-default-inverse);
29
+ }
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Button } from '../Button';
5
+ import { Icon } from '../Icon';
6
+ import { Waveform } from '../Waveform';
7
+ import { formatClock } from '../VoiceRecorder';
8
+ import './VoiceMessage.css';
9
+ /**
10
+ * VoiceMessage — inline play/pause player for a sent voice note.
11
+ *
12
+ * Composes a Muka `Button` with the shared token-based `Waveform`; the media
13
+ * stays with the app (`src` is whatever URL it hands over — Muka never fetches
14
+ * or signs it). Play/pause only: there is no transcription/speech-to-text.
15
+ */
16
+ export const VoiceMessage = ({ src, durationSec, peaks, variant, playLabel = 'Play voice message', pauseLabel = 'Pause voice message', unavailableLabel = 'Voice message unavailable', className = '', }) => {
17
+ const audioRef = useRef(null);
18
+ const [playing, setPlaying] = useState(false);
19
+ const [currentSec, setCurrentSec] = useState(0);
20
+ const [totalSec, setTotalSec] = useState(durationSec);
21
+ useEffect(() => {
22
+ const audio = audioRef.current;
23
+ if (!audio)
24
+ return;
25
+ const onTime = () => setCurrentSec(audio.currentTime);
26
+ const onLoaded = () => {
27
+ if (Number.isFinite(audio.duration) && audio.duration > 0) {
28
+ setTotalSec(audio.duration);
29
+ }
30
+ };
31
+ const onEnded = () => {
32
+ setPlaying(false);
33
+ setCurrentSec(0);
34
+ };
35
+ audio.addEventListener('timeupdate', onTime);
36
+ audio.addEventListener('loadedmetadata', onLoaded);
37
+ audio.addEventListener('ended', onEnded);
38
+ return () => {
39
+ audio.removeEventListener('timeupdate', onTime);
40
+ audio.removeEventListener('loadedmetadata', onLoaded);
41
+ audio.removeEventListener('ended', onEnded);
42
+ };
43
+ }, [src]);
44
+ const toggle = () => {
45
+ const audio = audioRef.current;
46
+ if (!audio)
47
+ return;
48
+ if (playing) {
49
+ audio.pause();
50
+ setPlaying(false);
51
+ }
52
+ else {
53
+ void audio
54
+ .play()
55
+ .then(() => setPlaying(true))
56
+ .catch(() => setPlaying(false));
57
+ }
58
+ };
59
+ const effectiveTotal = totalSec || durationSec;
60
+ const progress = effectiveTotal > 0 ? Math.min(1, currentSec / effectiveTotal) : 0;
61
+ const remaining = playing || currentSec > 0 ? effectiveTotal - currentSec : effectiveTotal;
62
+ // On own (tinted) bubbles the surface is the action color, so the played bars
63
+ // use the inverse text token for contrast; elsewhere the action color reads
64
+ // well against the light card.
65
+ const activeColor = variant === 'own'
66
+ ? 'var(--color-text-default-inverse)'
67
+ : 'var(--color-action-default)';
68
+ const inactiveColor = variant === 'own'
69
+ ? 'var(--color-black-alpha-3, rgba(0, 0, 0, 0.25))'
70
+ : 'var(--color-border-contrast)';
71
+ const classes = [
72
+ 'muka-voice-message',
73
+ `muka-voice-message--${variant}`,
74
+ className,
75
+ ]
76
+ .filter(Boolean)
77
+ .join(' ');
78
+ return (_jsxs("div", { className: classes, children: [_jsx(Button, { variant: variant === 'own' ? 'secondary' : 'primary', size: "md", iconOnly: true, "aria-label": playing ? pauseLabel : playLabel, onClick: toggle, disabled: !src, children: _jsx(Icon, { name: playing ? 'pause' : 'play', variant: "fill", size: "sm" }) }), _jsx("div", { className: "muka-voice-message__wave", children: _jsx(Waveform, { peaks: peaks, progress: progress, activeColor: activeColor, inactiveColor: inactiveColor, "aria-label": src ? undefined : unavailableLabel }) }), _jsx("span", { className: "muka-voice-message__time", children: formatClock(remaining) }), src && _jsx("audio", { ref: audioRef, src: src, preload: "metadata" })] }));
79
+ };
80
+ export default VoiceMessage;
81
+ //# sourceMappingURL=VoiceMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceMessage.js","sourceRoot":"","sources":["../../../../components/VoiceMessage/VoiceMessage.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { VoiceMessage, } from './VoiceMessage';
2
+ export { default } from './VoiceMessage';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../components/VoiceMessage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAGb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+ /**
3
+ * Audio helpers shared by the voice-recording hook and the players.
4
+ *
5
+ * Pure, DOM-light utilities: peak downsampling for a compact persisted waveform
6
+ * and a clock formatter for the recorder timer / player countdown. No upload,
7
+ * no network, no speech-to-text.
8
+ */
9
+ /** Default number of persisted waveform bars. Apps may override per clip. */
10
+ export const VOICE_PEAK_COUNT = 48;
11
+ /**
12
+ * Downsample a stream of amplitude samples (each already 0..1) into exactly
13
+ * `count` normalized peaks by taking the max of each bucket, then scaling so the
14
+ * loudest bar reaches 1. Pure — unit-testable independently of the DOM.
15
+ */
16
+ export function downsamplePeaks(samples, count = VOICE_PEAK_COUNT) {
17
+ if (samples.length === 0 || count <= 0)
18
+ return [];
19
+ const buckets = new Array(count).fill(0);
20
+ const size = samples.length / count;
21
+ for (let i = 0; i < count; i += 1) {
22
+ const start = Math.floor(i * size);
23
+ const end = Math.min(samples.length, Math.max(start + 1, Math.floor((i + 1) * size)));
24
+ let peak = 0;
25
+ for (let j = start; j < end; j += 1) {
26
+ const v = Math.abs(samples[j]);
27
+ if (v > peak)
28
+ peak = v;
29
+ }
30
+ buckets[i] = peak;
31
+ }
32
+ const max = buckets.reduce((m, v) => (v > m ? v : m), 0);
33
+ if (max <= 0)
34
+ return buckets;
35
+ return buckets.map((v) => Math.min(1, v / max));
36
+ }
37
+ /** Format a number of seconds as `m:ss` for the recorder timer / player. */
38
+ export function formatClock(totalSeconds) {
39
+ const safe = Number.isFinite(totalSeconds) && totalSeconds > 0
40
+ ? Math.floor(totalSeconds)
41
+ : 0;
42
+ const minutes = Math.floor(safe / 60);
43
+ const seconds = safe % 60;
44
+ const paddedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
45
+ return `${minutes}:${paddedSeconds}`;
46
+ }
47
+ /** True when this browser can record audio at all (SSR-safe). */
48
+ export function isVoiceRecordingSupported() {
49
+ return (typeof window !== 'undefined' &&
50
+ typeof navigator !== 'undefined' &&
51
+ !!navigator.mediaDevices &&
52
+ typeof navigator.mediaDevices.getUserMedia === 'function' &&
53
+ typeof MediaRecorder !== 'undefined');
54
+ }
55
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../../../../components/VoiceRecorder/audio.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;GAMG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAiB,EACjB,QAAgB,gBAAgB;IAEhC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAClD,MAAM,OAAO,GAAa,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAClB,OAAO,CAAC,MAAM,EACd,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAChD,CAAC;QACF,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,GAAG,IAAI;gBAAE,IAAI,GAAG,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACpB,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,YAAoB;IAC9C,MAAM,IAAI,GACR,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC;QAC/C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;QAC1B,CAAC,CAAC,CAAC,CAAC;IACR,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;IAC1B,MAAM,aAAa,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC;IAClE,OAAO,GAAG,OAAO,IAAI,aAAa,EAAE,CAAC;AACvC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,yBAAyB;IACvC,OAAO,CACL,OAAO,MAAM,KAAK,WAAW;QAC7B,OAAO,SAAS,KAAK,WAAW;QAChC,CAAC,CAAC,SAAS,CAAC,YAAY;QACxB,OAAO,SAAS,CAAC,YAAY,CAAC,YAAY,KAAK,UAAU;QACzD,OAAO,aAAa,KAAK,WAAW,CACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { useVoiceRecorder, } from './useVoiceRecorder';
2
+ export { downsamplePeaks, formatClock, isVoiceRecordingSupported, VOICE_PEAK_COUNT, } from './audio';
3
+ export { default } from './useVoiceRecorder';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../components/VoiceRecorder/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,GAIjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,WAAW,EACX,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,210 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { downsamplePeaks, isVoiceRecordingSupported } from './audio';
4
+ /** Number of live bars kept for the moving waveform while recording. */
5
+ const LIVE_PEAK_WINDOW = 40;
6
+ const MIME_CANDIDATES = [
7
+ { mime: 'audio/webm;codecs=opus', ext: 'webm' },
8
+ { mime: 'audio/webm', ext: 'webm' },
9
+ { mime: 'audio/ogg;codecs=opus', ext: 'ogg' },
10
+ { mime: 'audio/mp4', ext: 'm4a' },
11
+ ];
12
+ function pickMimeType() {
13
+ if (typeof MediaRecorder !== 'undefined' &&
14
+ typeof MediaRecorder.isTypeSupported === 'function') {
15
+ for (const candidate of MIME_CANDIDATES) {
16
+ if (MediaRecorder.isTypeSupported(candidate.mime))
17
+ return candidate;
18
+ }
19
+ }
20
+ // Let the browser choose a default container; webm is the safe guess for ext.
21
+ return { mime: '', ext: 'webm' };
22
+ }
23
+ /**
24
+ * `useVoiceRecorder` — headless hook exposing recorder status, the live waveform
25
+ * and the finished {@link VoiceClip}. Feed `elapsedMs` + `livePeaks` into a
26
+ * `RecordingBar`, and persist the returned clip's `peaks` for `VoiceMessage`.
27
+ */
28
+ export function useVoiceRecorder() {
29
+ const [status, setStatus] = useState('idle');
30
+ const [elapsedMs, setElapsedMs] = useState(0);
31
+ const [livePeaks, setLivePeaks] = useState([]);
32
+ const [error, setError] = useState(null);
33
+ const recorderRef = useRef(null);
34
+ const streamRef = useRef(null);
35
+ const chunksRef = useRef([]);
36
+ const audioCtxRef = useRef(null);
37
+ const analyserRef = useRef(null);
38
+ const rafRef = useRef(null);
39
+ const startedAtRef = useRef(0);
40
+ const allSamplesRef = useRef([]);
41
+ const mimeRef = useRef({ mime: '', ext: 'webm' });
42
+ const statusRef = useRef('idle');
43
+ const setPhase = useCallback((next) => {
44
+ statusRef.current = next;
45
+ setStatus(next);
46
+ }, []);
47
+ const teardown = useCallback(() => {
48
+ if (rafRef.current !== null) {
49
+ cancelAnimationFrame(rafRef.current);
50
+ rafRef.current = null;
51
+ }
52
+ if (audioCtxRef.current) {
53
+ void audioCtxRef.current.close().catch(() => { });
54
+ audioCtxRef.current = null;
55
+ }
56
+ analyserRef.current = null;
57
+ if (streamRef.current) {
58
+ streamRef.current.getTracks().forEach((t) => t.stop());
59
+ streamRef.current = null;
60
+ }
61
+ }, []);
62
+ useEffect(() => () => teardown(), [teardown]);
63
+ const sampleLoop = useCallback(() => {
64
+ const analyser = analyserRef.current;
65
+ if (!analyser)
66
+ return;
67
+ const buffer = new Uint8Array(analyser.fftSize);
68
+ analyser.getByteTimeDomainData(buffer);
69
+ // RMS around the 128 midpoint → 0..1 amplitude.
70
+ let sumSquares = 0;
71
+ for (let i = 0; i < buffer.length; i += 1) {
72
+ const centered = (buffer[i] - 128) / 128;
73
+ sumSquares += centered * centered;
74
+ }
75
+ const rms = Math.sqrt(sumSquares / buffer.length);
76
+ const amplitude = Math.min(1, rms * 2.2);
77
+ allSamplesRef.current.push(amplitude);
78
+ setLivePeaks((prev) => {
79
+ const next = [...prev, amplitude];
80
+ return next.length > LIVE_PEAK_WINDOW
81
+ ? next.slice(next.length - LIVE_PEAK_WINDOW)
82
+ : next;
83
+ });
84
+ setElapsedMs(Date.now() - startedAtRef.current);
85
+ rafRef.current = requestAnimationFrame(sampleLoop);
86
+ }, []);
87
+ const start = useCallback(async () => {
88
+ const phase = statusRef.current;
89
+ if (phase === 'recording' || phase === 'requesting')
90
+ return;
91
+ if (!isVoiceRecordingSupported()) {
92
+ setPhase('error');
93
+ setError('Recording is not available in this browser.');
94
+ return;
95
+ }
96
+ setPhase('requesting');
97
+ setError(null);
98
+ setElapsedMs(0);
99
+ setLivePeaks([]);
100
+ chunksRef.current = [];
101
+ allSamplesRef.current = [];
102
+ let stream;
103
+ try {
104
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
105
+ }
106
+ catch {
107
+ setPhase('error');
108
+ setError('Microphone access was denied.');
109
+ return;
110
+ }
111
+ streamRef.current = stream;
112
+ const chosen = pickMimeType();
113
+ mimeRef.current = chosen;
114
+ let recorder;
115
+ try {
116
+ recorder = chosen.mime
117
+ ? new MediaRecorder(stream, { mimeType: chosen.mime })
118
+ : new MediaRecorder(stream);
119
+ }
120
+ catch {
121
+ recorder = new MediaRecorder(stream);
122
+ }
123
+ recorderRef.current = recorder;
124
+ recorder.ondataavailable = (event) => {
125
+ if (event.data && event.data.size > 0)
126
+ chunksRef.current.push(event.data);
127
+ };
128
+ // Live waveform via an analyser tapped off the same stream.
129
+ try {
130
+ const AudioCtx = window.AudioContext ||
131
+ window
132
+ .webkitAudioContext;
133
+ if (AudioCtx) {
134
+ const ctx = new AudioCtx();
135
+ audioCtxRef.current = ctx;
136
+ const source = ctx.createMediaStreamSource(stream);
137
+ const analyser = ctx.createAnalyser();
138
+ analyser.fftSize = 1024;
139
+ source.connect(analyser);
140
+ analyserRef.current = analyser;
141
+ }
142
+ }
143
+ catch {
144
+ // Waveform is a nicety; recording still works without it.
145
+ }
146
+ startedAtRef.current = Date.now();
147
+ recorder.start(100);
148
+ setPhase('recording');
149
+ rafRef.current = requestAnimationFrame(sampleLoop);
150
+ }, [sampleLoop, setPhase]);
151
+ const stop = useCallback(async () => {
152
+ const recorder = recorderRef.current;
153
+ if (!recorder || statusRef.current !== 'recording') {
154
+ teardown();
155
+ setPhase('idle');
156
+ return null;
157
+ }
158
+ setPhase('stopping');
159
+ const durationSec = Math.max(0, (Date.now() - startedAtRef.current) / 1000);
160
+ const blob = await new Promise((resolve) => {
161
+ recorder.onstop = () => {
162
+ const type = mimeRef.current.mime || recorder.mimeType || 'audio/webm';
163
+ resolve(new Blob(chunksRef.current, { type }));
164
+ };
165
+ recorder.stop();
166
+ });
167
+ if (rafRef.current !== null) {
168
+ cancelAnimationFrame(rafRef.current);
169
+ rafRef.current = null;
170
+ }
171
+ const peaks = downsamplePeaks(allSamplesRef.current);
172
+ teardown();
173
+ recorderRef.current = null;
174
+ setPhase('idle');
175
+ setLivePeaks([]);
176
+ setElapsedMs(0);
177
+ if (blob.size === 0)
178
+ return null;
179
+ return {
180
+ blob,
181
+ mimeType: blob.type,
182
+ ext: mimeRef.current.ext,
183
+ durationSec,
184
+ peaks,
185
+ };
186
+ }, [setPhase, teardown]);
187
+ const cancel = useCallback(() => {
188
+ const recorder = recorderRef.current;
189
+ if (recorder && recorder.state !== 'inactive') {
190
+ recorder.onstop = null;
191
+ try {
192
+ recorder.stop();
193
+ }
194
+ catch {
195
+ // already stopping
196
+ }
197
+ }
198
+ recorderRef.current = null;
199
+ chunksRef.current = [];
200
+ allSamplesRef.current = [];
201
+ teardown();
202
+ setPhase('idle');
203
+ setLivePeaks([]);
204
+ setElapsedMs(0);
205
+ setError(null);
206
+ }, [setPhase, teardown]);
207
+ return { status, elapsedMs, livePeaks, error, start, stop, cancel };
208
+ }
209
+ export default useVoiceRecorder;
210
+ //# sourceMappingURL=useVoiceRecorder.js.map