@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.
- package/CHANGELOG.md +62 -0
- package/index.css +3 -0
- package/package.json +59 -0
- package/src/_old/_table/index.ts +5 -0
- package/src/_old/_table/table-advanced.constants.tsx +24 -0
- package/src/_old/_table/table-advanced.tsx +311 -0
- package/src/_old/_table/table-advanced.types.ts +272 -0
- package/src/_old/_table/table.constants.ts +2 -0
- package/src/_old/_table/table.hook.tsx +115 -0
- package/src/_old/_table/table.lib.ts +85 -0
- package/src/_old/_table/table.tsx +916 -0
- package/src/_old/_table/table.types.ts +118 -0
- package/src/_old/_table/todo.md +11 -0
- package/src/_old/_upload/index.ts +9 -0
- package/src/_old/_upload/todo.md +38 -0
- package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
- package/src/_old/_upload/upload-advanced.tsx +507 -0
- package/src/_old/_upload/upload-sonner.tsx +58 -0
- package/src/_old/_upload/upload.assets.tsx +239 -0
- package/src/_old/_upload/upload.constants.tsx +75 -0
- package/src/_old/_upload/upload.dto.ts +19 -0
- package/src/_old/_upload/upload.lib.tsx +630 -0
- package/src/_old/_upload/upload.tsx +491 -0
- package/src/_old/_upload/upload.types.ts +436 -0
- package/src/accordion/accordion.tsx +247 -0
- package/src/accordion/index.ts +1 -0
- package/src/alert/alert.constants.ts +17 -0
- package/src/alert/alert.tsx +52 -0
- package/src/alert/index.ts +2 -0
- package/src/alert-dialog/alert-dialog.tsx +107 -0
- package/src/alert-dialog/index.ts +1 -0
- package/src/aspect-ratio/aspect-ratio.tsx +33 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/audio/audio-record.tsx +776 -0
- package/src/audio/audio-visualizer.tsx +377 -0
- package/src/audio/audio.libs.ts +5 -0
- package/src/audio/audio.types.ts +50 -0
- package/src/audio/index.ts +2 -0
- package/src/avatar/avatar.tsx +78 -0
- package/src/avatar/index.ts +1 -0
- package/src/badge/badge.constants.ts +38 -0
- package/src/badge/badge.tsx +19 -0
- package/src/badge/index.ts +2 -0
- package/src/breadcrumb/breadcrumb.tsx +119 -0
- package/src/breadcrumb/index.ts +1 -0
- package/src/button/button.constants.ts +44 -0
- package/src/button/button.tsx +79 -0
- package/src/button/button.types.ts +38 -0
- package/src/button/index.ts +3 -0
- package/src/button-group/button-group.constants.ts +26 -0
- package/src/button-group/button-group.tsx +65 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar.tsx +191 -0
- package/src/calendar/index.ts +1 -0
- package/src/card/card.tsx +81 -0
- package/src/card/index.ts +1 -0
- package/src/carousel/carousel.tsx +211 -0
- package/src/carousel/carousel.types.ts +23 -0
- package/src/carousel/index.ts +2 -0
- package/src/chart/chart.libs.ts +27 -0
- package/src/chart/chart.tsx +260 -0
- package/src/chart/chart.types.ts +38 -0
- package/src/chart/index.ts +3 -0
- package/src/checkbox/checkbox.tsx +144 -0
- package/src/checkbox/checkbox.types.ts +24 -0
- package/src/checkbox/index.ts +2 -0
- package/src/collapsible/collapsible.tsx +151 -0
- package/src/collapsible/index.ts +1 -0
- package/src/combobox/combobox.tsx +132 -0
- package/src/combobox/index.ts +1 -0
- package/src/command/command.tsx +192 -0
- package/src/command/command.types.ts +11 -0
- package/src/command/index.ts +2 -0
- package/src/context-menu/context-menu.tsx +178 -0
- package/src/context-menu/index.ts +1 -0
- package/src/dialog/dialog-responsive.tsx +137 -0
- package/src/dialog/dialog.tsx +97 -0
- package/src/dialog/index.ts +2 -0
- package/src/direction/direction.tsx +13 -0
- package/src/direction/index.ts +1 -0
- package/src/drawer/drawer.tsx +185 -0
- package/src/drawer/index.ts +1 -0
- package/src/dropdown-menu/dropdown-menu.tsx +181 -0
- package/src/dropdown-menu/index.ts +1 -0
- package/src/empty/empty.constants.ts +15 -0
- package/src/empty/empty.tsx +73 -0
- package/src/empty/index.ts +2 -0
- package/src/field/field.constants.ts +22 -0
- package/src/field/field.tsx +203 -0
- package/src/field/index.ts +2 -0
- package/src/hover-card/hover-card.tsx +79 -0
- package/src/hover-card/index.ts +1 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +45 -0
- package/src/input-group/index.ts +1 -0
- package/src/input-group/input-group.tsx +170 -0
- package/src/input-otp/index.ts +1 -0
- package/src/input-otp/input-otp.tsx +66 -0
- package/src/item/index.ts +2 -0
- package/src/item/item.constants.ts +22 -0
- package/src/item/item.tsx +185 -0
- package/src/json-editor/index.ts +4 -0
- package/src/json-editor/json-editor.hooks.ts +21 -0
- package/src/json-editor/json-editor.libs.ts +34 -0
- package/src/json-editor/json-editor.tsx +425 -0
- package/src/json-editor/json-editor.types.ts +80 -0
- package/src/json-editor/json-editor.view.tsx +110 -0
- package/src/json-editor/json-text-area.tsx +7 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.tsx +39 -0
- package/src/label/index.ts +1 -0
- package/src/label/label.tsx +28 -0
- package/src/menubar/index.ts +1 -0
- package/src/menubar/menubar.tsx +213 -0
- package/src/navigation-menu/index.ts +1 -0
- package/src/navigation-menu/navigation-menu.tsx +152 -0
- package/src/pagination/index.ts +2 -0
- package/src/pagination/pagination.tsx +191 -0
- package/src/pagination/pagination.types.ts +17 -0
- package/src/popover/index.ts +1 -0
- package/src/popover/popover.tsx +35 -0
- package/src/preview-panel/index.ts +3 -0
- package/src/preview-panel/preview-panel-dialog.tsx +99 -0
- package/src/preview-panel/preview-panel.tsx +389 -0
- package/src/preview-panel/preview-panel.types.ts +49 -0
- package/src/progress/index.ts +1 -0
- package/src/progress/progress.tsx +32 -0
- package/src/radio-group/index.ts +1 -0
- package/src/radio-group/radio-group.tsx +92 -0
- package/src/resizable/index.ts +1 -0
- package/src/resizable/resizable.tsx +52 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +30 -0
- package/src/select/index.ts +1 -0
- package/src/select/select.tsx +138 -0
- package/src/separator/index.ts +1 -0
- package/src/separator/separator.tsx +28 -0
- package/src/sheet/index.ts +2 -0
- package/src/sheet/sheet.constants.tsx +20 -0
- package/src/sheet/sheet.tsx +92 -0
- package/src/sidebar/index.ts +4 -0
- package/src/sidebar/sidebar.constants.ts +30 -0
- package/src/sidebar/sidebar.hooks.ts +13 -0
- package/src/sidebar/sidebar.tsx +676 -0
- package/src/sidebar/sidebar.types.ts +28 -0
- package/src/skeleton/index.ts +1 -0
- package/src/skeleton/skeleton.tsx +22 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +57 -0
- package/src/sonner/index.ts +4 -0
- package/src/sonner/sonner.chunks.tsx +80 -0
- package/src/sonner/sonner.libs.ts +13 -0
- package/src/sonner/sonner.tsx +31 -0
- package/src/sonner/sonner.types.ts +9 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +63 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +95 -0
- package/src/tabs/index.ts +1 -0
- package/src/tabs/tabs.tsx +151 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +24 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.constants.ts +22 -0
- package/src/toggle/toggle.tsx +24 -0
- package/src/toggle-group/index.ts +1 -0
- package/src/toggle-group/toggle-group.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +32 -0
- package/src/upload/index.ts +1 -0
- package/src/upload/upload.constants.tsx +19 -0
- package/src/upload/upload.libs.ts +97 -0
- package/src/upload/upload.tsx +340 -0
- package/src/upload/upload.types.ts +44 -0
- 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,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,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,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'
|