@moises.ai/design-system 3.11.21 → 3.12.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moises.ai/design-system",
3
- "version": "3.11.21",
3
+ "version": "3.12.0",
4
4
  "description": "Design System package based on @radix-ui/themes with custom defaults",
5
5
  "private": false,
6
6
  "type": "module",
@@ -6,6 +6,7 @@ const SEGMENT_MIN = 5
6
6
  const SEGMENT_RANGE = 25
7
7
  const UNITY_GAIN_POS = 0.667
8
8
  const MAX_BOOST_DB = 6
9
+ const FADER_KEYBOARD_STEP = 0.02
9
10
 
10
11
  function linearToMeterLevel(linear) {
11
12
  const peakDb = 20 * Math.log10(Math.max(linear, 1e-6))
@@ -101,6 +102,9 @@ export const InputLevelMeter = memo(function InputLevelMeter({
101
102
  const [peakHoldPct, setPeakHoldPct] = useState(0)
102
103
  const [peakRising, setPeakRising] = useState(false)
103
104
 
105
+ const [thumbFocused, setThumbFocused] = useState(false)
106
+ const [hoverOpen, setHoverOpen] = useState(false)
107
+
104
108
  useEffect(() => {
105
109
  peakHoldRef.current = 0
106
110
  setPeakHoldPct(0)
@@ -184,6 +188,49 @@ export const InputLevelMeter = memo(function InputLevelMeter({
184
188
  document.body.style.cursor = ''
185
189
  }, [])
186
190
 
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
+
187
234
  const hasHandler = onVolumeChange && showHandler
188
235
 
189
236
  const meterClassName = [
@@ -194,6 +241,11 @@ export const InputLevelMeter = memo(function InputLevelMeter({
194
241
  .filter(Boolean)
195
242
  .join(' ')
196
243
 
244
+ const tooltipOpen = isPressed || thumbFocused || hoverOpen
245
+
246
+ const gainDbForAria =
247
+ volume <= 0 ? -96 : 20 * Math.log10(Math.max(volume, 1e-6))
248
+
197
249
  return (
198
250
  <div
199
251
  className={styles.container}
@@ -211,15 +263,26 @@ export const InputLevelMeter = memo(function InputLevelMeter({
211
263
  {hasHandler && (
212
264
  <Tooltip
213
265
  content={formatGainDb(volume)}
214
- open={isPressed}
266
+ open={tooltipOpen}
267
+ onOpenChange={setHoverOpen}
215
268
  side="bottom"
216
269
  align="center"
217
- sideOffset={8}
270
+ sideOffset={3}
218
271
  delayDuration={0}
219
272
  >
220
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)}
221
281
  className={`${styles.thumb} ${isPressed ? styles.pressed : hover ? styles.hovered : ''}`}
222
282
  style={{ left: `${faderPercent}%` }}
283
+ onFocus={() => setThumbFocused(true)}
284
+ onBlur={() => setThumbFocused(false)}
285
+ onKeyDown={handleThumbKeyDown}
223
286
  />
224
287
  </Tooltip>
225
288
  )}
@@ -78,6 +78,11 @@
78
78
  background: white;
79
79
  }
80
80
 
81
+ .thumb:focus-visible {
82
+ outline: 2px solid var(--neutral-alpha-8);
83
+ outline-offset: 2px;
84
+ }
85
+
81
86
  .peakDot {
82
87
  position: absolute;
83
88
  width: 1px;
@@ -2,13 +2,14 @@ import styles from './PanControl.module.css'
2
2
  import classNames from 'classnames'
3
3
  import { useRef, useCallback, useMemo, useState, useEffect } from 'react'
4
4
  import { useEventListener } from '../../utils/useEventListener'
5
- import { Tooltip } from '../../index'
5
+ import { Tooltip } from '../Tooltip/Tooltip'
6
6
 
7
7
  const KNOB_CENTER_X = 10
8
8
  const KNOB_CENTER_Y = 10
9
9
  const KNOB_RADIUS = 9.5
10
10
  const START_ANGLE = -Math.PI / 2
11
11
  const STEP_SIZE = 0.01
12
+ const KEYBOARD_STEP = STEP_SIZE * 12
12
13
 
13
14
  const clampNorm = (v) => Math.max(-1, Math.min(1, v))
14
15
  const denormalize = (normalized, minValue, maxValue) =>
@@ -30,6 +31,16 @@ export const PanControl = ({
30
31
  const pendingNormRef = useRef(value)
31
32
  const rafRef = useRef(0)
32
33
 
34
+ const [knobFocused, setKnobFocused] = useState(false)
35
+ const [hoverOpen, setHoverOpen] = useState(false)
36
+
37
+ useEffect(() => {
38
+ if (disabled) {
39
+ setHoverOpen(false)
40
+ setKnobFocused(false)
41
+ }
42
+ }, [disabled])
43
+
33
44
  useEffect(() => {
34
45
  if (isDragging) {
35
46
  const style = document.createElement('style')
@@ -111,6 +122,38 @@ export const PanControl = ({
111
122
  [scheduleEmit],
112
123
  )
113
124
 
125
+ const handleKnobKeyDown = useCallback(
126
+ (e) => {
127
+ if (disabled) return
128
+ let next = value
129
+ switch (e.key) {
130
+ case 'ArrowUp':
131
+ case 'ArrowRight':
132
+ e.preventDefault()
133
+ next = value + KEYBOARD_STEP
134
+ scheduleEmit(next)
135
+ break
136
+ case 'ArrowDown':
137
+ case 'ArrowLeft':
138
+ e.preventDefault()
139
+ next = value - KEYBOARD_STEP
140
+ scheduleEmit(next)
141
+ break
142
+ case 'Home':
143
+ e.preventDefault()
144
+ scheduleEmit(-1)
145
+ break
146
+ case 'End':
147
+ e.preventDefault()
148
+ scheduleEmit(1)
149
+ break
150
+ default:
151
+ break
152
+ }
153
+ },
154
+ [disabled, value, scheduleEmit],
155
+ )
156
+
114
157
  const { arcPath, pointerRotation, arcColor, pointerColor } = useMemo(() => {
115
158
  const actual = denormalize(value, minValue, maxValue)
116
159
  const displayNorm = clampNorm(actual)
@@ -138,6 +181,12 @@ export const PanControl = ({
138
181
  return `Pan: ${Math.abs(pct) < 1 ? 0 : pct}`
139
182
  }, [value, minValue, maxValue])
140
183
 
184
+ const displayNorm = clampNorm(denormalize(value, minValue, maxValue))
185
+ const ariaPanPct = Math.round(displayNorm * 100)
186
+
187
+ const tooltipOpen =
188
+ !disabled && (isDragging || knobFocused || hoverOpen)
189
+
141
190
  return (
142
191
  <div
143
192
  ref={containerRef}
@@ -148,13 +197,24 @@ export const PanControl = ({
148
197
  >
149
198
  <Tooltip
150
199
  content={panValueText}
151
- open={isDragging}
200
+ open={tooltipOpen}
201
+ onOpenChange={(next) => {
202
+ if (!disabled) setHoverOpen(next)
203
+ }}
152
204
  side="bottom"
153
205
  align="center"
154
- sideOffset={10}
206
+ sideOffset={3}
155
207
  delayDuration={0}
156
208
  >
157
209
  <div
210
+ role="slider"
211
+ tabIndex={disabled ? -1 : 0}
212
+ aria-label="Pan"
213
+ aria-valuemin={-100}
214
+ aria-valuemax={100}
215
+ aria-valuenow={ariaPanPct}
216
+ aria-valuetext={panValueText}
217
+ aria-disabled={disabled}
158
218
  className={classNames(
159
219
  styles.pan,
160
220
  isDragging && styles.active,
@@ -164,6 +224,9 @@ export const PanControl = ({
164
224
  onPointerMove={onPointerMove}
165
225
  onPointerUp={onPointerUp}
166
226
  onPointerCancel={onPointerUp}
227
+ onFocus={() => setKnobFocused(true)}
228
+ onBlur={() => setKnobFocused(false)}
229
+ onKeyDown={handleKnobKeyDown}
167
230
  >
168
231
  <svg width={20} height={20} viewBox="0 0 20 20" fill="none">
169
232
  <circle cx={KNOB_CENTER_X} cy={KNOB_CENTER_Y} r={10} />
@@ -37,4 +37,9 @@
37
37
  fill: #DDEAF8;
38
38
  fill-opacity: 0.0784314;
39
39
  }
40
-
40
+
41
+ .pan:focus-visible:not(.disabled) {
42
+ outline: 2px solid var(--neutral-alpha-8);
43
+ outline-offset: 2px;
44
+ border-radius: 50%;
45
+ }
@@ -2,8 +2,8 @@
2
2
  box-sizing: border-box;
3
3
  width: 20px;
4
4
  min-width: 20px;
5
+ height: 20px;
5
6
  min-height: 20px;
6
- height: 100%;
7
7
  margin: 0;
8
8
  padding: 2px;
9
9
  border: none;
@@ -7,7 +7,7 @@ export const MinusIcon = ({ width = 16, height = 16, className, ...props }) => (
7
7
  fill="none"
8
8
  className={className} {...props}>
9
9
  <g >
10
- <path d="M3.33325 8H12.6666" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
10
+ <path d="M3.33325 8H12.6666" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
11
11
  </g>
12
12
  </svg>
13
13
  )
@@ -11,12 +11,12 @@ export const Share2Icon = ({
11
11
  fill="none"
12
12
  xmlns="http://www.w3.org/2000/svg"
13
13
  >
14
- <g clip-path="url(#clip0_6624_4145)">
14
+ <g clipPath="url(#clip0_6624_4145)">
15
15
  <path
16
16
  d="M10.1875 2.89583L8.00001 0.708328M8.00001 0.708328L5.81251 2.89583M8.00001 0.708328V10.1875M10.9167 5.08333C12.5275 5.08333 13.8333 6.38916 13.8333 7.99999V12.2917C13.8333 13.9485 12.4902 15.2917 10.8333 15.2917H5.16656C3.50968 15.2917 2.16653 13.9485 2.16656 12.2916L2.16663 7.99999C2.16666 6.38915 3.47251 5.08333 5.08335 5.08333"
17
17
  stroke="#B0B4BA"
18
- stroke-linecap="round"
19
- stroke-linejoin="round"
18
+ strokeLinecap="round"
19
+ strokeLinejoin="round"
20
20
  />
21
21
  </g>
22
22
  <defs>