@gentleduck/registry-ui 0.2.1

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 (175) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/index.css +3 -0
  3. package/package.json +59 -0
  4. package/src/_old/_table/index.ts +5 -0
  5. package/src/_old/_table/table-advanced.constants.tsx +24 -0
  6. package/src/_old/_table/table-advanced.tsx +311 -0
  7. package/src/_old/_table/table-advanced.types.ts +272 -0
  8. package/src/_old/_table/table.constants.ts +2 -0
  9. package/src/_old/_table/table.hook.tsx +115 -0
  10. package/src/_old/_table/table.lib.ts +85 -0
  11. package/src/_old/_table/table.tsx +916 -0
  12. package/src/_old/_table/table.types.ts +118 -0
  13. package/src/_old/_table/todo.md +11 -0
  14. package/src/_old/_upload/index.ts +9 -0
  15. package/src/_old/_upload/todo.md +38 -0
  16. package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
  17. package/src/_old/_upload/upload-advanced.tsx +507 -0
  18. package/src/_old/_upload/upload-sonner.tsx +58 -0
  19. package/src/_old/_upload/upload.assets.tsx +239 -0
  20. package/src/_old/_upload/upload.constants.tsx +75 -0
  21. package/src/_old/_upload/upload.dto.ts +19 -0
  22. package/src/_old/_upload/upload.lib.tsx +630 -0
  23. package/src/_old/_upload/upload.tsx +491 -0
  24. package/src/_old/_upload/upload.types.ts +436 -0
  25. package/src/accordion/accordion.tsx +247 -0
  26. package/src/accordion/index.ts +1 -0
  27. package/src/alert/alert.constants.ts +17 -0
  28. package/src/alert/alert.tsx +52 -0
  29. package/src/alert/index.ts +2 -0
  30. package/src/alert-dialog/alert-dialog.tsx +107 -0
  31. package/src/alert-dialog/index.ts +1 -0
  32. package/src/aspect-ratio/aspect-ratio.tsx +33 -0
  33. package/src/aspect-ratio/index.ts +1 -0
  34. package/src/audio/audio-record.tsx +776 -0
  35. package/src/audio/audio-visualizer.tsx +377 -0
  36. package/src/audio/audio.libs.ts +5 -0
  37. package/src/audio/audio.types.ts +50 -0
  38. package/src/audio/index.ts +2 -0
  39. package/src/avatar/avatar.tsx +78 -0
  40. package/src/avatar/index.ts +1 -0
  41. package/src/badge/badge.constants.ts +38 -0
  42. package/src/badge/badge.tsx +19 -0
  43. package/src/badge/index.ts +2 -0
  44. package/src/breadcrumb/breadcrumb.tsx +119 -0
  45. package/src/breadcrumb/index.ts +1 -0
  46. package/src/button/button.constants.ts +44 -0
  47. package/src/button/button.tsx +79 -0
  48. package/src/button/button.types.ts +38 -0
  49. package/src/button/index.ts +3 -0
  50. package/src/button-group/button-group.constants.ts +26 -0
  51. package/src/button-group/button-group.tsx +65 -0
  52. package/src/button-group/index.ts +2 -0
  53. package/src/calendar/calendar.tsx +191 -0
  54. package/src/calendar/index.ts +1 -0
  55. package/src/card/card.tsx +81 -0
  56. package/src/card/index.ts +1 -0
  57. package/src/carousel/carousel.tsx +211 -0
  58. package/src/carousel/carousel.types.ts +23 -0
  59. package/src/carousel/index.ts +2 -0
  60. package/src/chart/chart.libs.ts +27 -0
  61. package/src/chart/chart.tsx +260 -0
  62. package/src/chart/chart.types.ts +38 -0
  63. package/src/chart/index.ts +3 -0
  64. package/src/checkbox/checkbox.tsx +144 -0
  65. package/src/checkbox/checkbox.types.ts +24 -0
  66. package/src/checkbox/index.ts +2 -0
  67. package/src/collapsible/collapsible.tsx +151 -0
  68. package/src/collapsible/index.ts +1 -0
  69. package/src/combobox/combobox.tsx +132 -0
  70. package/src/combobox/index.ts +1 -0
  71. package/src/command/command.tsx +192 -0
  72. package/src/command/command.types.ts +11 -0
  73. package/src/command/index.ts +2 -0
  74. package/src/context-menu/context-menu.tsx +178 -0
  75. package/src/context-menu/index.ts +1 -0
  76. package/src/dialog/dialog-responsive.tsx +137 -0
  77. package/src/dialog/dialog.tsx +97 -0
  78. package/src/dialog/index.ts +2 -0
  79. package/src/direction/direction.tsx +13 -0
  80. package/src/direction/index.ts +1 -0
  81. package/src/drawer/drawer.tsx +185 -0
  82. package/src/drawer/index.ts +1 -0
  83. package/src/dropdown-menu/dropdown-menu.tsx +181 -0
  84. package/src/dropdown-menu/index.ts +1 -0
  85. package/src/empty/empty.constants.ts +15 -0
  86. package/src/empty/empty.tsx +73 -0
  87. package/src/empty/index.ts +2 -0
  88. package/src/field/field.constants.ts +22 -0
  89. package/src/field/field.tsx +203 -0
  90. package/src/field/index.ts +2 -0
  91. package/src/hover-card/hover-card.tsx +79 -0
  92. package/src/hover-card/index.ts +1 -0
  93. package/src/input/index.ts +1 -0
  94. package/src/input/input.tsx +45 -0
  95. package/src/input-group/index.ts +1 -0
  96. package/src/input-group/input-group.tsx +170 -0
  97. package/src/input-otp/index.ts +1 -0
  98. package/src/input-otp/input-otp.tsx +66 -0
  99. package/src/item/index.ts +2 -0
  100. package/src/item/item.constants.ts +22 -0
  101. package/src/item/item.tsx +185 -0
  102. package/src/json-editor/index.ts +4 -0
  103. package/src/json-editor/json-editor.hooks.ts +21 -0
  104. package/src/json-editor/json-editor.libs.ts +34 -0
  105. package/src/json-editor/json-editor.tsx +425 -0
  106. package/src/json-editor/json-editor.types.ts +80 -0
  107. package/src/json-editor/json-editor.view.tsx +110 -0
  108. package/src/json-editor/json-text-area.tsx +7 -0
  109. package/src/kbd/index.ts +1 -0
  110. package/src/kbd/kbd.tsx +39 -0
  111. package/src/label/index.ts +1 -0
  112. package/src/label/label.tsx +28 -0
  113. package/src/menubar/index.ts +1 -0
  114. package/src/menubar/menubar.tsx +213 -0
  115. package/src/navigation-menu/index.ts +1 -0
  116. package/src/navigation-menu/navigation-menu.tsx +152 -0
  117. package/src/pagination/index.ts +2 -0
  118. package/src/pagination/pagination.tsx +191 -0
  119. package/src/pagination/pagination.types.ts +17 -0
  120. package/src/popover/index.ts +1 -0
  121. package/src/popover/popover.tsx +35 -0
  122. package/src/preview-panel/index.ts +3 -0
  123. package/src/preview-panel/preview-panel-dialog.tsx +99 -0
  124. package/src/preview-panel/preview-panel.tsx +389 -0
  125. package/src/preview-panel/preview-panel.types.ts +49 -0
  126. package/src/progress/index.ts +1 -0
  127. package/src/progress/progress.tsx +32 -0
  128. package/src/radio-group/index.ts +1 -0
  129. package/src/radio-group/radio-group.tsx +92 -0
  130. package/src/resizable/index.ts +1 -0
  131. package/src/resizable/resizable.tsx +52 -0
  132. package/src/scroll-area/index.ts +1 -0
  133. package/src/scroll-area/scroll-area.tsx +30 -0
  134. package/src/select/index.ts +1 -0
  135. package/src/select/select.tsx +138 -0
  136. package/src/separator/index.ts +1 -0
  137. package/src/separator/separator.tsx +28 -0
  138. package/src/sheet/index.ts +2 -0
  139. package/src/sheet/sheet.constants.tsx +20 -0
  140. package/src/sheet/sheet.tsx +92 -0
  141. package/src/sidebar/index.ts +4 -0
  142. package/src/sidebar/sidebar.constants.ts +30 -0
  143. package/src/sidebar/sidebar.hooks.ts +13 -0
  144. package/src/sidebar/sidebar.tsx +676 -0
  145. package/src/sidebar/sidebar.types.ts +28 -0
  146. package/src/skeleton/index.ts +1 -0
  147. package/src/skeleton/skeleton.tsx +22 -0
  148. package/src/slider/index.ts +1 -0
  149. package/src/slider/slider.tsx +57 -0
  150. package/src/sonner/index.ts +4 -0
  151. package/src/sonner/sonner.chunks.tsx +80 -0
  152. package/src/sonner/sonner.libs.ts +13 -0
  153. package/src/sonner/sonner.tsx +31 -0
  154. package/src/sonner/sonner.types.ts +9 -0
  155. package/src/switch/index.ts +1 -0
  156. package/src/switch/switch.tsx +63 -0
  157. package/src/table/index.ts +1 -0
  158. package/src/table/table.tsx +95 -0
  159. package/src/tabs/index.ts +1 -0
  160. package/src/tabs/tabs.tsx +151 -0
  161. package/src/textarea/index.ts +1 -0
  162. package/src/textarea/textarea.tsx +24 -0
  163. package/src/toggle/index.ts +2 -0
  164. package/src/toggle/toggle.constants.ts +22 -0
  165. package/src/toggle/toggle.tsx +24 -0
  166. package/src/toggle-group/index.ts +1 -0
  167. package/src/toggle-group/toggle-group.tsx +69 -0
  168. package/src/tooltip/index.ts +1 -0
  169. package/src/tooltip/tooltip.tsx +32 -0
  170. package/src/upload/index.ts +1 -0
  171. package/src/upload/upload.constants.tsx +19 -0
  172. package/src/upload/upload.libs.ts +97 -0
  173. package/src/upload/upload.tsx +340 -0
  174. package/src/upload/upload.types.ts +44 -0
  175. package/tsconfig.json +25 -0
@@ -0,0 +1,377 @@
1
+ // @ts-noCheck
2
+
3
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
4
+ import { useTheme } from 'next-themes'
5
+ import React from 'react'
6
+ import { useAudioDataProvider } from './audio-record'
7
+
8
+ export const new_audio = (url: string) => new Audio(url)
9
+
10
+ // Calculate bar data
11
+ export interface dataPoint {
12
+ max: number
13
+ min: number
14
+ }
15
+
16
+ export interface CalculateBarDataParams {
17
+ buffer: AudioBuffer
18
+ width: number
19
+ height: number
20
+ barWidth: number
21
+ gap: number
22
+ }
23
+
24
+ export const calculate_bar_data_handler = (() => {
25
+ const cache = new Map()
26
+
27
+ return ({ buffer, width, height, barWidth, gap }: CalculateBarDataParams): dataPoint[] => {
28
+ // Create a unique key based on the input parameters
29
+ const key = `${buffer.length}-${width}-${height}-${barWidth}-${gap}`
30
+
31
+ // Check if the result is already cached
32
+ if (cache.has(key)) {
33
+ return cache.get(key)
34
+ }
35
+
36
+ const bufferData = buffer.getChannelData(0)
37
+ const units = Math.floor(width / (barWidth + gap))
38
+ const step = Math.floor(bufferData.length / units)
39
+ const amp = height / 2
40
+
41
+ const data: dataPoint[] = new Array(units)
42
+ let maxDataPoint = 0
43
+
44
+ for (let i = 0; i < units; i++) {
45
+ let minSum = 0
46
+ let maxSum = 0
47
+ let minCount = 0
48
+ let maxCount = 0
49
+
50
+ const startIdx = i * step
51
+ const endIdx = Math.min(startIdx + step, bufferData.length)
52
+
53
+ for (let j = startIdx; j < endIdx; j++) {
54
+ const datum = bufferData[j]
55
+ if (datum < 0) {
56
+ minSum += datum
57
+ minCount++
58
+ } else {
59
+ maxSum += datum
60
+ maxCount++
61
+ }
62
+ }
63
+
64
+ const minAvg = minCount ? minSum / minCount : 0
65
+ const maxAvg = maxCount ? maxSum / maxCount : 0
66
+
67
+ const dataPoint = { max: maxAvg, min: minAvg }
68
+ maxDataPoint = Math.max(maxDataPoint, Math.abs(dataPoint.max), Math.abs(dataPoint.min))
69
+ data[i] = dataPoint
70
+ }
71
+
72
+ if (amp * 0.8 > maxDataPoint * amp) {
73
+ const adjustmentFactor = (amp * 0.8) / maxDataPoint
74
+ for (let i = 0; i < units; i++) {
75
+ data[i].max *= adjustmentFactor
76
+ data[i].min *= adjustmentFactor
77
+ }
78
+ }
79
+
80
+ // Store the computed result in the cache
81
+ cache.set(key, data)
82
+
83
+ return data
84
+ }
85
+ })()
86
+
87
+ // Draw Handler
88
+ export interface DrawHandlerParams {
89
+ data: dataPoint[]
90
+ canvas: HTMLCanvasElement | null
91
+ barWidth: number
92
+ gap: number
93
+ backgroundColor: string
94
+ barColor: string
95
+ barPlayedColor?: string
96
+ currentTime: number
97
+ duration: number
98
+ minBarHeight: number
99
+ animationProgress: number
100
+ }
101
+
102
+ export const draw_handler = ({
103
+ data,
104
+ canvas,
105
+ barWidth,
106
+ gap,
107
+ backgroundColor,
108
+ barColor,
109
+ barPlayedColor,
110
+ currentTime = 0,
111
+ duration = 1,
112
+ minBarHeight = 5,
113
+ animationProgress = 1,
114
+ }: DrawHandlerParams): void => {
115
+ if (!canvas || !data.length) return
116
+
117
+ const ctx = canvas.getContext('2d')
118
+ if (!ctx) return
119
+
120
+ const amp = canvas.height / 2
121
+ const playedPercent = currentTime / duration
122
+
123
+ // Clear the canvas and set background
124
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
125
+ if (backgroundColor !== 'transparent') {
126
+ ctx.fillStyle = 'transparent'
127
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
128
+ }
129
+
130
+ // Draw bars in a single loop
131
+ const totalBars = data.length
132
+ for (let i = 0; i < totalBars; i++) {
133
+ const height = Math.max(data[i].max * 2 * animationProgress, minBarHeight)
134
+ ctx.fillStyle = playedPercent > i / totalBars && barPlayedColor ? barPlayedColor : barColor
135
+ ctx.fillRect(i * (barWidth + gap), amp - height / 2, barWidth, height)
136
+ }
137
+ }
138
+
139
+ // Process Blob
140
+ export interface ProcessBlobParams {
141
+ canvasRef: React.RefObject<HTMLCanvasElement>
142
+ blob: Blob | null
143
+ barWidth: number
144
+ gap: number
145
+ backgroundColor: string
146
+ barColor: string
147
+ barPlayedColor?: string
148
+ minBarHeight: number
149
+ setLoading: React.Dispatch<React.SetStateAction<boolean>>
150
+ setData: React.Dispatch<React.SetStateAction<dataPoint[]>>
151
+ setDuration: React.Dispatch<React.SetStateAction<number>>
152
+ setAnimationProgress: React.Dispatch<React.SetStateAction<number>>
153
+ width: number
154
+ height: number
155
+ }
156
+
157
+ export const process_blob = async ({
158
+ canvasRef,
159
+ blob,
160
+ barWidth,
161
+ gap,
162
+ backgroundColor,
163
+ barColor,
164
+ barPlayedColor,
165
+ minBarHeight,
166
+ setLoading,
167
+ setData,
168
+ setDuration,
169
+ setAnimationProgress,
170
+ width,
171
+ height,
172
+ }: ProcessBlobParams): Promise<void> => {
173
+ if (!canvasRef.current || !blob) return
174
+
175
+ const defaultBars = Array.from({ length: Math.floor(width / (barWidth + gap)) }, () => ({
176
+ max: minBarHeight,
177
+ min: minBarHeight,
178
+ }))
179
+
180
+ draw_handler({
181
+ animationProgress: 1,
182
+ backgroundColor,
183
+ barColor,
184
+ barPlayedColor,
185
+ barWidth,
186
+ canvas: canvasRef.current,
187
+ currentTime: 0,
188
+ data: defaultBars,
189
+ duration: 1,
190
+ gap,
191
+ minBarHeight: 1,
192
+ })
193
+
194
+ const audioContext = new AudioContext()
195
+ const audioBuffer = await blob.arrayBuffer()
196
+
197
+ // Decode the entire audio data
198
+ audioContext.decodeAudioData(audioBuffer, (buffer) => {
199
+ if (!canvasRef.current) return
200
+
201
+ setDuration(buffer.duration)
202
+
203
+ // Calculate the waveform data for the entire audio buffer
204
+ const barsData = calculate_bar_data_handler({
205
+ barWidth,
206
+ buffer,
207
+ gap,
208
+ height,
209
+ width,
210
+ })
211
+
212
+ // Set the calculated data for rendering
213
+ setData(barsData)
214
+
215
+ // Set up for animation
216
+ let startTime: number | null = null
217
+ let animationFrameId: number | null = null
218
+
219
+ const animate = (time: number) => {
220
+ if (!startTime) startTime = time
221
+
222
+ const elapsedTime = time - startTime
223
+ const progress = Math.min(elapsedTime / 1000, 1)
224
+
225
+ // Update animation progress using a ref
226
+ setAnimationProgress(progress)
227
+
228
+ draw_handler({
229
+ animationProgress: progress,
230
+ backgroundColor,
231
+ barColor,
232
+ barPlayedColor,
233
+ barWidth,
234
+ canvas: canvasRef.current,
235
+ currentTime: 0,
236
+ data: barsData,
237
+ duration: buffer.duration,
238
+ gap,
239
+ minBarHeight,
240
+ })
241
+
242
+ if (progress < 1) {
243
+ animationFrameId = requestAnimationFrame(animate)
244
+ } else {
245
+ setLoading(false)
246
+ }
247
+ }
248
+
249
+ // Start the animation
250
+ animationFrameId = requestAnimationFrame(animate)
251
+
252
+ // Cleanup when the component unmounts or the animation is done
253
+ return () => {
254
+ if (animationFrameId) cancelAnimationFrame(animationFrameId)
255
+ }
256
+ })
257
+ }
258
+
259
+ export interface ThemeColor {
260
+ light: string
261
+ dark: string
262
+ }
263
+
264
+ // Audio Visualizer
265
+ interface AudioVisualizerProps {
266
+ blob: Blob | null
267
+ width: number
268
+ height: number
269
+ dir?: 'ltr' | 'rtl'
270
+ barWidth?: number
271
+ gap?: number
272
+ backgroundColor?: ThemeColor
273
+ barColor?: ThemeColor
274
+ barPlayedColor?: ThemeColor
275
+ currentTime?: number
276
+ minBarHeight?: number
277
+ style?: React.CSSProperties
278
+ setLoading: React.Dispatch<React.SetStateAction<boolean>>
279
+ ref?: React.ForwardedRef<HTMLCanvasElement>
280
+ setCurrentTime: React.Dispatch<React.SetStateAction<number>>
281
+ }
282
+
283
+ const AudioVisualizer: React.FC<AudioVisualizerProps> = ({
284
+ blob,
285
+ width,
286
+ height,
287
+ dir,
288
+ barWidth = 2,
289
+ gap = 1,
290
+ backgroundColor = { dark: 'transparent', light: 'transparent' },
291
+ barColor = { dark: '#ffffff69', light: 'rgb(184, 184, 184)' },
292
+ barPlayedColor = { dark: '#fafafa', light: '#18181b' },
293
+ currentTime = 0,
294
+ minBarHeight = 2,
295
+ style,
296
+ setLoading,
297
+ }) => {
298
+ const canvasRef = React.useRef<HTMLCanvasElement>(null)
299
+ const { process_audio, data, duration, animationProgress } = useAudioDataProvider()
300
+ const direction = useDirection(dir as Direction)
301
+
302
+ const { theme } = useTheme()
303
+
304
+ interface ThemeColors {
305
+ backgroundColor: string
306
+ barColor: string
307
+ barPlayedColor: string
308
+ }
309
+
310
+ const colors: Record<string, ThemeColors> = {
311
+ dark: {
312
+ backgroundColor: backgroundColor.dark,
313
+ barColor: barColor.dark,
314
+ barPlayedColor: barPlayedColor.dark,
315
+ },
316
+ light: {
317
+ backgroundColor: backgroundColor.light,
318
+ barColor: barColor.light,
319
+ barPlayedColor: barPlayedColor.light,
320
+ },
321
+ }
322
+
323
+ const currentColors = colors[theme as string] || colors.light
324
+
325
+ React.useEffect(() => {
326
+ setLoading(true)
327
+ process_audio({
328
+ backgroundColor: currentColors.backgroundColor,
329
+ barColor: currentColors.barColor,
330
+ barPlayedColor: currentColors.barPlayedColor,
331
+ barWidth,
332
+ blob,
333
+ canvasRef,
334
+ gap,
335
+ height,
336
+ minBarHeight,
337
+ setLoading,
338
+ width,
339
+ })
340
+ }, [blob])
341
+
342
+ React.useEffect(() => {
343
+ if (!canvasRef.current) return
344
+ draw_handler({
345
+ animationProgress,
346
+ backgroundColor: currentColors.backgroundColor,
347
+ barColor: currentColors.barColor,
348
+ barPlayedColor: currentColors.barPlayedColor,
349
+ barWidth,
350
+ canvas: canvasRef.current,
351
+ currentTime,
352
+ data: data.length
353
+ ? data
354
+ : Array.from({ length: Math.floor(width / (barWidth + gap)) }, () => ({
355
+ max: minBarHeight,
356
+ min: minBarHeight,
357
+ })),
358
+ duration,
359
+ gap,
360
+ minBarHeight,
361
+ })
362
+ }, [data, width, height, currentTime, duration, animationProgress, theme])
363
+
364
+ return (
365
+ <canvas
366
+ aria-label="Audio waveform visualization"
367
+ dir={direction}
368
+ height={height}
369
+ ref={canvasRef}
370
+ role="img"
371
+ style={style}
372
+ width={width}
373
+ />
374
+ )
375
+ }
376
+
377
+ export { AudioVisualizer }
@@ -0,0 +1,5 @@
1
+ export const format_time_handler = (milliseconds: number): string => {
2
+ const minutes = Math.floor(milliseconds / 60000)
3
+ const seconds = Math.floor((milliseconds % 60000) / 1000)
4
+ return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
5
+ }
@@ -0,0 +1,50 @@
1
+ export interface RecordingParams {
2
+ setRecordings: React.Dispatch<React.SetStateAction<RecordingtType[]>>
3
+ setRecordedDuration: React.Dispatch<React.SetStateAction<number>>
4
+ audioChunksRef: React.RefObject<Blob[]>
5
+ }
6
+
7
+ export interface StopRecordingHandlerParam {
8
+ setRecording: React.Dispatch<React.SetStateAction<boolean>>
9
+ // @ts-ignore
10
+ intervalRef: React.RefObject<NodeJS.Timeout | null>
11
+ mediaRecorderRef: React.RefObject<MediaRecorder | null>
12
+ durationRef: React.RefObject<number>
13
+ }
14
+
15
+ export interface DeleteRecordingHandlerParams
16
+ extends Pick<RecordingParams, 'audioChunksRef'>,
17
+ StopRecordingHandlerParam {}
18
+
19
+ export interface StopRecordingHandlerParams
20
+ extends Omit<StopRecordingHandlerParam, 'setRecording' | 'mediaRecorderRef' | 'durationRef'>,
21
+ Omit<RecordingParams, 'setRecordedDuration'> {}
22
+
23
+ export interface StartTimerParams
24
+ extends Omit<StopRecordingHandlerParam, 'setRecording' | 'mediaRecorderRef'>,
25
+ Pick<RecordingParams, 'setRecordedDuration'> {}
26
+
27
+ export interface StartRecordingHandlerParams extends StopRecordingHandlerParam, RecordingParams {}
28
+ export interface RecordingtType {
29
+ id: string
30
+ file: File | null
31
+ url: string | null
32
+ type: string
33
+ name: string
34
+ size: string
35
+ }
36
+
37
+ export interface VisualizerClickHandlerParams {
38
+ audioRef: React.RefObject<HTMLAudioElement | null>
39
+ setCurrentTime: React.Dispatch<React.SetStateAction<number>>
40
+ event: React.MouseEvent<HTMLDivElement>
41
+ }
42
+
43
+ export interface AttachmentType {
44
+ id: string
45
+ file: Blob | null
46
+ url: string | null
47
+ type: string
48
+ name: string
49
+ size: string
50
+ }
@@ -0,0 +1,2 @@
1
+ export * from './audio-record'
2
+ export * from './audio-visualizer'
@@ -0,0 +1,78 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import * as AvatarPrimitive from '@gentleduck/primitives/avatar'
5
+ import * as React from 'react'
6
+
7
+ const Avatar = React.forwardRef<
8
+ React.ComponentRef<typeof AvatarPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <AvatarPrimitive.Root
12
+ ref={ref}
13
+ data-slot="avatar"
14
+ className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
15
+ {...props}
16
+ />
17
+ ))
18
+ Avatar.displayName = AvatarPrimitive.Root.displayName
19
+
20
+ const AvatarImage = React.forwardRef<
21
+ React.ComponentRef<typeof AvatarPrimitive.Image>,
22
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
23
+ >(({ className, ...props }, ref) => (
24
+ <AvatarPrimitive.Image
25
+ ref={ref}
26
+ data-slot="avatar-img"
27
+ className={cn('aspect-square h-full w-full', className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
32
+
33
+ const AvatarFallback = React.forwardRef<
34
+ React.ComponentRef<typeof AvatarPrimitive.Fallback>,
35
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36
+ >(({ className, ...props }, ref) => (
37
+ <AvatarPrimitive.Fallback
38
+ ref={ref}
39
+ data-slot="avatar-fallback"
40
+ className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
41
+ {...props}
42
+ />
43
+ ))
44
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
45
+
46
+ export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
47
+ imgs: { src?: string; alt?: string; fallback?: string; id?: string }[]
48
+ maxVisible?: number
49
+ }
50
+
51
+ const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
52
+ ({ imgs, maxVisible = 3, className, ...props }, ref) => {
53
+ const visibleImgs = imgs.slice(0, maxVisible)
54
+ const overflowCount = imgs.length > maxVisible ? imgs.length - maxVisible : 0
55
+
56
+ return (
57
+ <div className={cn('flex items-center -space-x-5', className)} ref={ref} {...props}>
58
+ {visibleImgs.map((img) => (
59
+ <Avatar className={cn('border-2 border-border')} key={img.id}>
60
+ <AvatarImage alt={img.alt} src={img.src} />
61
+ <AvatarFallback>{img.fallback?.slice(0, 2) ?? img.alt?.slice(0, 2)}</AvatarFallback>
62
+ </Avatar>
63
+ ))}
64
+
65
+ {overflowCount > 0 && (
66
+ <div className="relative z-10 inline-block">
67
+ <div className="flex size-10 items-center justify-center rounded-full bg-primary font-medium text-primary-foreground text-sm ring-2 ring-background">
68
+ +{overflowCount}
69
+ </div>
70
+ </div>
71
+ )}
72
+ </div>
73
+ )
74
+ },
75
+ )
76
+ AvatarGroup.displayName = 'AvatarGroup'
77
+
78
+ export { Avatar, AvatarImage, AvatarFallback, AvatarGroup }
@@ -0,0 +1 @@
1
+ export * from './avatar'
@@ -0,0 +1,38 @@
1
+ import { cva } from '@gentleduck/variants'
2
+
3
+ export const badgeVariants = cva(
4
+ 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-md px-2 py-0.5 font-medium transition-colors [&_svg]-size-3 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none',
5
+ {
6
+ defaultVariants: {
7
+ border: 'default',
8
+ size: 'default',
9
+ variant: 'default',
10
+ },
11
+ variants: {
12
+ border: {
13
+ default: '',
14
+ destructive: 'border border-destructive/40 bg-destructive/40 hover:border-destructive hover:bg-destructive/65',
15
+ primary: 'border border-border/40 hover:border-border/80',
16
+ secondary: 'border border-secondary/40 bg-secondary/40 hover:border-secondary hover:bg-secondary/65',
17
+ warning: 'border border-warning/40 bg-warning/40 hover:border-warning hover:bg-warning/65',
18
+ },
19
+ size: {
20
+ default: 'px-2.5 py-0.5 text-sm',
21
+ icon: 'size-[1.6rem] items-center justify-center rounded-full p-0 [&_*]:size-[.9rem]',
22
+ lg: 'px-3.5 py-0.9 text-lg',
23
+ sm: 'px-1.5 py-0.5 text-[.7rem]',
24
+ },
25
+ variant: {
26
+ nothing: '!px-0 text-accent-foreground',
27
+ dashed:
28
+ 'border border-input border-dashed bg-background text-accent-foreground hover:bg-accent/50 hover:text-accent-foreground',
29
+ default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
30
+ secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
31
+ destructive:
32
+ 'border-transparent bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90',
33
+ outline: 'border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
34
+ warning: 'border-transparent bg-warning text-warning-foreground [a&]:hover:bg-warning/90',
35
+ },
36
+ },
37
+ },
38
+ )
@@ -0,0 +1,19 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { Slot } from '@gentleduck/primitives/slot'
3
+ import type { VariantProps } from '@gentleduck/variants'
4
+ import * as React from 'react'
5
+ import { badgeVariants } from './badge.constants'
6
+
7
+ const Badge = React.forwardRef<
8
+ HTMLDivElement,
9
+ Omit<React.HTMLProps<HTMLDivElement>, 'size'> & VariantProps<typeof badgeVariants> & { asChild?: boolean }
10
+ >(({ className, variant = 'default', size = 'default', border = 'default', asChild = false, ...props }, ref) => {
11
+ const Comp = asChild ? Slot : 'span'
12
+
13
+ return (
14
+ <Comp ref={ref} className={cn(badgeVariants({ border, size, variant }), className)} data-slot="badge" {...props} />
15
+ )
16
+ })
17
+ Badge.displayName = 'Badge'
18
+
19
+ export { Badge }
@@ -0,0 +1,2 @@
1
+ export * from './badge'
2
+ export * from './badge.constants'
@@ -0,0 +1,119 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
3
+ import { Slot } from '@gentleduck/primitives/slot'
4
+ import { ChevronRight, MoreHorizontal } from 'lucide-react'
5
+ import * as React from 'react'
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<'nav'> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ dir, ...props }, ref) => {
13
+ const direction = useDirection(dir as Direction)
14
+ return <nav ref={ref} {...props} aria-label="breadcrumb" data-slot="breadcrumb" dir={direction} />
15
+ })
16
+ Breadcrumb.displayName = 'Breadcrumb'
17
+
18
+ const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<'ol'>>(
19
+ ({ className, ...props }, ref) => (
20
+ <ol
21
+ className={cn(
22
+ 'wrap-break-word flex flex-wrap items-center gap-1.5 text-muted-foreground text-sm sm:gap-2.5',
23
+ className,
24
+ )}
25
+ ref={ref}
26
+ {...props}
27
+ data-slot="breadcrumb-list"
28
+ />
29
+ ),
30
+ )
31
+ BreadcrumbList.displayName = 'BreadcrumbList'
32
+
33
+ const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<'li'>>(
34
+ ({ className, ...props }, ref) => {
35
+ return (
36
+ <li
37
+ className={cn('inline-flex items-center gap-1.5', className)}
38
+ ref={ref}
39
+ {...props}
40
+ data-slot="breadcrumb-item"
41
+ />
42
+ )
43
+ },
44
+ )
45
+ BreadcrumbItem.displayName = 'BreadcrumbItem'
46
+
47
+ const BreadcrumbLink = React.forwardRef<
48
+ HTMLAnchorElement,
49
+ React.ComponentPropsWithoutRef<'a'> & {
50
+ asChild?: boolean
51
+ }
52
+ >(({ asChild, className, ...props }, ref) => {
53
+ const Comp = (asChild ? Slot : 'a') as React.ElementType
54
+ return (
55
+ <Comp
56
+ className={cn('transition-colors hover:text-foreground', className)}
57
+ ref={ref}
58
+ {...props}
59
+ data-slot="breadcrumb-link"
60
+ />
61
+ )
62
+ })
63
+ BreadcrumbLink.displayName = 'BreadcrumbLink'
64
+
65
+ const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<'span'>>(
66
+ ({ className, ...props }, ref) => {
67
+ return (
68
+ <span
69
+ className={cn('font-normal text-foreground', className)}
70
+ ref={ref}
71
+ {...props}
72
+ aria-current="page"
73
+ data-slot="breadcrumb-page"
74
+ />
75
+ )
76
+ },
77
+ )
78
+ BreadcrumbPage.displayName = 'BreadcrumbPage'
79
+
80
+ const BreadcrumbSeparator = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<'li'>>(
81
+ ({ children, className, ...props }, ref) => (
82
+ <li
83
+ className={cn('[&>svg]:size-3.5', className)}
84
+ ref={ref}
85
+ {...props}
86
+ aria-hidden="true"
87
+ data-slot="breadcrumb-separator"
88
+ role="presentation">
89
+ {children ?? <ChevronRight className="rtl:rotate-180" />}
90
+ </li>
91
+ ),
92
+ )
93
+ BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
94
+
95
+ const BreadcrumbEllipsis = React.forwardRef<
96
+ HTMLSpanElement,
97
+ React.ComponentPropsWithoutRef<'span'> & { text?: string }
98
+ >(({ className, text = 'More', ...props }, ref) => (
99
+ <span
100
+ className={cn('flex h-9 w-9 items-center justify-center', className)}
101
+ role="img"
102
+ aria-label={text}
103
+ ref={ref}
104
+ {...props}
105
+ data-slot="breadcrumb-ellipsis">
106
+ <MoreHorizontal aria-hidden="true" className="h-4 w-4" />
107
+ </span>
108
+ ))
109
+ BreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis'
110
+
111
+ export {
112
+ Breadcrumb,
113
+ BreadcrumbList,
114
+ BreadcrumbItem,
115
+ BreadcrumbLink,
116
+ BreadcrumbPage,
117
+ BreadcrumbSeparator,
118
+ BreadcrumbEllipsis,
119
+ }
@@ -0,0 +1 @@
1
+ export * from './breadcrumb'