@moises.ai/design-system 4.14.3 → 4.14.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moises.ai/design-system",
3
- "version": "4.14.3",
3
+ "version": "4.14.5",
4
4
  "description": "Design System package based on @radix-ui/themes with custom defaults",
5
5
  "private": false,
6
6
  "type": "module",
@@ -37,21 +37,20 @@ export const Callout = ({
37
37
 
38
38
  const contentRef = useRef(null)
39
39
  const actionsRef = useRef(null)
40
- const [contentAlign, setContentAlign] = useState('flex-start')
40
+ const [outerAlign, setOuterAlign] = useState('start')
41
41
 
42
42
  useLayoutEffect(() => {
43
-
44
43
  if (!hasActions) {
45
- setContentAlign('flex-start')
44
+ setOuterAlign('start')
46
45
  return
47
46
  }
48
47
 
49
48
  if (!contentRef.current || !actionsRef.current) return
50
49
 
51
50
  const updateAlign = () => {
52
- const contentHeight = contentRef.current.offsetHeight
53
- const actionsHeight = actionsRef.current.offsetHeight
54
- setContentAlign(contentHeight < actionsHeight ? 'center' : 'flex-start')
51
+ const contentHeight = contentRef.current.scrollHeight
52
+ const actionsHeight = actionsRef.current.scrollHeight
53
+ setOuterAlign(contentHeight <= actionsHeight ? 'center' : 'start')
55
54
  }
56
55
 
57
56
  updateAlign()
@@ -90,11 +89,10 @@ export const Callout = ({
90
89
  )}
91
90
  {...props}
92
91
  >
93
- <Flex justify="between" gap="3" width="100%">
92
+ <Flex justify="between" gap="3" width="100%" align={outerAlign}>
94
93
  <Flex
95
94
  direction="row"
96
95
  gap="2"
97
- align={contentAlign}
98
96
  width="100%"
99
97
  >
100
98
  {renderIcon()}
@@ -1,4 +1,11 @@
1
- import { memo, useCallback, useEffect, useRef, useState } from 'react'
1
+ import {
2
+ forwardRef,
3
+ memo,
4
+ useCallback,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ } from 'react'
2
9
  import { Tooltip } from '../Tooltip/Tooltip'
3
10
  import styles from './InputLevelMeter.module.css'
4
11
 
@@ -7,6 +14,8 @@ const SEGMENT_RANGE = 25
7
14
  const UNITY_GAIN_POS = 0.667
8
15
  const MAX_BOOST_DB = 6
9
16
  const FADER_KEYBOARD_STEP = 0.02
17
+ const THUMB_DOUBLE_TAP_MS = 400
18
+ const THUMB_DOUBLE_TAP_MAX_PX = 12
10
19
 
11
20
  function linearToMeterLevel(linear) {
12
21
  const peakDb = 20 * Math.log10(Math.max(linear, 1e-6))
@@ -81,10 +90,134 @@ function MeterRow({ level }) {
81
90
  )
82
91
  }
83
92
 
93
+ const MeterThumb = memo(
94
+ forwardRef(function MeterThumb(
95
+ {
96
+ faderPercent,
97
+ isPressed,
98
+ hover,
99
+ volume,
100
+ doubleTapStateRef,
101
+ onVolumeChange,
102
+ onVolumeReset,
103
+ },
104
+ ref,
105
+ ) {
106
+ const [thumbFocused, setThumbFocused] = useState(false)
107
+ const [hoverOpen, setHoverOpen] = useState(false)
108
+
109
+ const tooltipOpen = isPressed || thumbFocused || hoverOpen
110
+
111
+ const handlePointerDownCapture = useCallback(
112
+ (e) => {
113
+ if (!onVolumeReset || e.button !== 0) return
114
+ const now = performance.now()
115
+ const prev = doubleTapStateRef.current
116
+ if (
117
+ prev &&
118
+ now - prev.t < THUMB_DOUBLE_TAP_MS &&
119
+ Math.hypot(e.clientX - prev.x, e.clientY - prev.y) <
120
+ THUMB_DOUBLE_TAP_MAX_PX
121
+ ) {
122
+ e.preventDefault()
123
+ e.stopPropagation()
124
+ doubleTapStateRef.current = null
125
+ onVolumeReset()
126
+ return
127
+ }
128
+ doubleTapStateRef.current = {
129
+ t: now,
130
+ x: e.clientX,
131
+ y: e.clientY,
132
+ }
133
+ },
134
+ [doubleTapStateRef, onVolumeReset],
135
+ )
136
+
137
+ const handleKeyDown = useCallback(
138
+ (e) => {
139
+ if (!onVolumeChange) return
140
+ const pos = gainToFaderPosition(volume)
141
+ let nextPos = pos
142
+ switch (e.key) {
143
+ case 'ArrowRight':
144
+ case 'ArrowUp':
145
+ e.preventDefault()
146
+ nextPos = Math.min(1, pos + FADER_KEYBOARD_STEP)
147
+ onVolumeChange([faderPositionToGain(nextPos)])
148
+ break
149
+ case 'ArrowLeft':
150
+ case 'ArrowDown':
151
+ e.preventDefault()
152
+ nextPos = Math.max(0, pos - FADER_KEYBOARD_STEP)
153
+ onVolumeChange([faderPositionToGain(nextPos)])
154
+ break
155
+ case 'Home':
156
+ e.preventDefault()
157
+ onVolumeChange([faderPositionToGain(0)])
158
+ break
159
+ case 'End':
160
+ e.preventDefault()
161
+ onVolumeChange([faderPositionToGain(1)])
162
+ break
163
+ case 'PageUp':
164
+ e.preventDefault()
165
+ nextPos = Math.min(1, pos + FADER_KEYBOARD_STEP * 4)
166
+ onVolumeChange([faderPositionToGain(nextPos)])
167
+ break
168
+ case 'PageDown':
169
+ e.preventDefault()
170
+ nextPos = Math.max(0, pos - FADER_KEYBOARD_STEP * 4)
171
+ onVolumeChange([faderPositionToGain(nextPos)])
172
+ break
173
+ default:
174
+ break
175
+ }
176
+ },
177
+ [onVolumeChange, volume],
178
+ )
179
+
180
+ const gainDbForAria =
181
+ volume <= 0 ? -96 : 20 * Math.log10(Math.max(volume, 1e-6))
182
+
183
+ return (
184
+ <Tooltip
185
+ content={formatGainDb(volume)}
186
+ open={tooltipOpen}
187
+ onOpenChange={setHoverOpen}
188
+ side="bottom"
189
+ align="center"
190
+ sideOffset={3}
191
+ delayDuration={0}
192
+ >
193
+ <div
194
+ ref={ref}
195
+ role="slider"
196
+ tabIndex={0}
197
+ aria-label="Input gain"
198
+ aria-valuemin={-96}
199
+ aria-valuemax={MAX_BOOST_DB}
200
+ aria-valuenow={Math.round(gainDbForAria * 10) / 10}
201
+ aria-valuetext={formatGainDb(volume)}
202
+ className={`${styles.thumb} ${isPressed ? styles.pressed : hover ? styles.hovered : ''}`}
203
+ style={{ left: `${faderPercent}%` }}
204
+ onPointerDownCapture={handlePointerDownCapture}
205
+ onFocus={() => setThumbFocused(true)}
206
+ onBlur={() => setThumbFocused(false)}
207
+ onKeyDown={handleKeyDown}
208
+ />
209
+ </Tooltip>
210
+ )
211
+ }),
212
+ )
213
+
214
+ MeterThumb.displayName = 'MeterThumb'
215
+
84
216
  export const InputLevelMeter = memo(function InputLevelMeter({
85
217
  linear = [0, 0],
86
218
  volume = 1,
87
219
  onVolumeChange,
220
+ onVolumeReset,
88
221
  showHandler = true,
89
222
  showMeter = true,
90
223
  hover = false,
@@ -97,14 +230,13 @@ export const InputLevelMeter = memo(function InputLevelMeter({
97
230
  const [isPressed, setIsPressed] = useState(false)
98
231
  const isPressedRef = useRef(false)
99
232
  const meterRef = useRef(null)
233
+ const thumbRef = useRef(null)
234
+ const thumbDoubleTapStateRef = useRef(null)
100
235
  const peakHoldRef = useRef(0)
101
236
  const peakTimerRef = useRef(null)
102
237
  const [peakHoldPct, setPeakHoldPct] = useState(0)
103
238
  const [peakRising, setPeakRising] = useState(false)
104
239
 
105
- const [thumbFocused, setThumbFocused] = useState(false)
106
- const [hoverOpen, setHoverOpen] = useState(false)
107
-
108
240
  useEffect(() => {
109
241
  peakHoldRef.current = 0
110
242
  setPeakHoldPct(0)
@@ -151,6 +283,13 @@ export const InputLevelMeter = memo(function InputLevelMeter({
151
283
  const handlePointerDown = useCallback(
152
284
  (e) => {
153
285
  if (!onVolumeChange) return
286
+ if (
287
+ thumbRef.current &&
288
+ e.target instanceof Element &&
289
+ !thumbRef.current.contains(e.target)
290
+ ) {
291
+ thumbDoubleTapStateRef.current = null
292
+ }
154
293
  e.currentTarget.setPointerCapture(e.pointerId)
155
294
  isPressedRef.current = true
156
295
  setIsPressed(true)
@@ -188,49 +327,6 @@ export const InputLevelMeter = memo(function InputLevelMeter({
188
327
  document.body.style.cursor = ''
189
328
  }, [])
190
329
 
191
- const handleThumbKeyDown = useCallback(
192
- (e) => {
193
- if (!onVolumeChange) return
194
- const pos = gainToFaderPosition(volume)
195
- let nextPos = pos
196
- switch (e.key) {
197
- case 'ArrowRight':
198
- case 'ArrowUp':
199
- e.preventDefault()
200
- nextPos = Math.min(1, pos + FADER_KEYBOARD_STEP)
201
- onVolumeChange([faderPositionToGain(nextPos)])
202
- break
203
- case 'ArrowLeft':
204
- case 'ArrowDown':
205
- e.preventDefault()
206
- nextPos = Math.max(0, pos - FADER_KEYBOARD_STEP)
207
- onVolumeChange([faderPositionToGain(nextPos)])
208
- break
209
- case 'Home':
210
- e.preventDefault()
211
- onVolumeChange([faderPositionToGain(0)])
212
- break
213
- case 'End':
214
- e.preventDefault()
215
- onVolumeChange([faderPositionToGain(1)])
216
- break
217
- case 'PageUp':
218
- e.preventDefault()
219
- nextPos = Math.min(1, pos + FADER_KEYBOARD_STEP * 4)
220
- onVolumeChange([faderPositionToGain(nextPos)])
221
- break
222
- case 'PageDown':
223
- e.preventDefault()
224
- nextPos = Math.max(0, pos - FADER_KEYBOARD_STEP * 4)
225
- onVolumeChange([faderPositionToGain(nextPos)])
226
- break
227
- default:
228
- break
229
- }
230
- },
231
- [onVolumeChange, volume],
232
- )
233
-
234
330
  const hasHandler = onVolumeChange && showHandler
235
331
 
236
332
  const meterClassName = [
@@ -241,11 +337,6 @@ export const InputLevelMeter = memo(function InputLevelMeter({
241
337
  .filter(Boolean)
242
338
  .join(' ')
243
339
 
244
- const tooltipOpen = isPressed || thumbFocused || hoverOpen
245
-
246
- const gainDbForAria =
247
- volume <= 0 ? -96 : 20 * Math.log10(Math.max(volume, 1e-6))
248
-
249
340
  return (
250
341
  <div
251
342
  className={styles.container}
@@ -261,30 +352,16 @@ export const InputLevelMeter = memo(function InputLevelMeter({
261
352
  <MeterRow level={rightLevel} />
262
353
 
263
354
  {hasHandler && (
264
- <Tooltip
265
- content={formatGainDb(volume)}
266
- open={tooltipOpen}
267
- onOpenChange={setHoverOpen}
268
- side="bottom"
269
- align="center"
270
- sideOffset={3}
271
- delayDuration={0}
272
- >
273
- <div
274
- role="slider"
275
- tabIndex={0}
276
- aria-label="Input gain"
277
- aria-valuemin={-96}
278
- aria-valuemax={MAX_BOOST_DB}
279
- aria-valuenow={Math.round(gainDbForAria * 10) / 10}
280
- aria-valuetext={formatGainDb(volume)}
281
- className={`${styles.thumb} ${isPressed ? styles.pressed : hover ? styles.hovered : ''}`}
282
- style={{ left: `${faderPercent}%` }}
283
- onFocus={() => setThumbFocused(true)}
284
- onBlur={() => setThumbFocused(false)}
285
- onKeyDown={handleThumbKeyDown}
286
- />
287
- </Tooltip>
355
+ <MeterThumb
356
+ ref={thumbRef}
357
+ faderPercent={faderPercent}
358
+ isPressed={isPressed}
359
+ hover={hover}
360
+ volume={volume}
361
+ doubleTapStateRef={thumbDoubleTapStateRef}
362
+ onVolumeChange={onVolumeChange}
363
+ onVolumeReset={onVolumeReset}
364
+ />
288
365
  )}
289
366
 
290
367
  <div
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef, useCallback } from 'react'
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
2
  import { InputLevelMeter } from './InputLevelMeter'
3
3
  import { useSimulatedInputLevel } from './useSimulatedInputLevel'
4
4
 
@@ -47,12 +47,18 @@ function InteractiveMeter({ initialVolume = 1, ...props }) {
47
47
  linear={linear}
48
48
  volume={volume}
49
49
  onVolumeChange={handleVolumeChange}
50
+ onVolumeReset={() => setVolume(1)}
50
51
  {...props}
51
52
  />
52
53
  <span
53
- style={{ color: 'rgba(255,255,255,0.5)', fontSize: '11px', fontFamily: 'monospace' }}
54
+ style={{
55
+ color: 'rgba(255,255,255,0.5)',
56
+ fontSize: '11px',
57
+ fontFamily: 'monospace',
58
+ }}
54
59
  >
55
- gain: {volume.toFixed(3)} | dB: {volume > 0 ? (20 * Math.log10(volume)).toFixed(1) : '-∞'}
60
+ gain: {volume.toFixed(3)} | dB:{' '}
61
+ {volume > 0 ? (20 * Math.log10(volume)).toFixed(1) : '-∞'}
56
62
  </span>
57
63
  </div>
58
64
  )
@@ -152,13 +158,29 @@ export const HoverState = {
152
158
  render: () => (
153
159
  <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
154
160
  <div>
155
- <span style={{ color: 'rgba(255,255,255,0.4)', fontSize: '10px', fontFamily: 'monospace', display: 'block', marginBottom: '4px' }}>
161
+ <span
162
+ style={{
163
+ color: 'rgba(255,255,255,0.4)',
164
+ fontSize: '10px',
165
+ fontFamily: 'monospace',
166
+ display: 'block',
167
+ marginBottom: '4px',
168
+ }}
169
+ >
156
170
  Default
157
171
  </span>
158
172
  <AnimatedMeter />
159
173
  </div>
160
174
  <div>
161
- <span style={{ color: 'rgba(255,255,255,0.4)', fontSize: '10px', fontFamily: 'monospace', display: 'block', marginBottom: '4px' }}>
175
+ <span
176
+ style={{
177
+ color: 'rgba(255,255,255,0.4)',
178
+ fontSize: '10px',
179
+ fontFamily: 'monospace',
180
+ display: 'block',
181
+ marginBottom: '4px',
182
+ }}
183
+ >
162
184
  hover=true (parent hovered)
163
185
  </span>
164
186
  <AnimatedMeter hover />
@@ -258,13 +280,15 @@ export const MultipleMeters = {
258
280
  const linear = useSimulatedInputLevel()
259
281
  const [volumes, setVolumes] = useState([1, 0.7, 0.4, 0.1])
260
282
 
261
- const makeHandler = (idx) => ([v]) => {
262
- setVolumes((prev) => {
263
- const next = [...prev]
264
- next[idx] = v
265
- return next
266
- })
267
- }
283
+ const makeHandler =
284
+ (idx) =>
285
+ ([v]) => {
286
+ setVolumes((prev) => {
287
+ const next = [...prev]
288
+ next[idx] = v
289
+ return next
290
+ })
291
+ }
268
292
 
269
293
  const labels = ['Master', 'Vocals', 'Guitar', 'Drums']
270
294