@moises.ai/design-system 3.11.8 → 3.11.10
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/dist/{ChevronDownFilledIcon-qgjYUwMz.js → TrackPreviousIcon-QO8TnAez.js} +2457 -2609
- package/dist/icons.js +1 -1
- package/dist/index.js +3179 -3112
- package/package.json +1 -1
- package/src/components/InputLevelMeter/InputLevelMeter.jsx +29 -7
- package/src/components/InputLevelMeter/InputLevelMeter.module.css +4 -0
- package/src/components/InputLevelMeter/InputLevelMeter.stories.jsx +290 -3
- package/src/components/PeakLevel/PeakLevel.jsx +71 -0
- package/src/components/PeakLevel/PeakLevel.module.css +49 -0
- package/src/components/PeakLevel/PeakLevel.stories.jsx +192 -0
- package/src/icons/AlertIcon.jsx +2 -7
- package/src/icons/BarsIcon.jsx +1 -6
- package/src/icons/CameraRollIcon.jsx +1 -6
- package/src/icons/CartIcon.jsx +2 -7
- package/src/icons/ClipIcon.jsx +2 -7
- package/src/icons/CloudDownloadGradientIcon.jsx +2 -5
- package/src/icons/CodeIcon.jsx +2 -7
- package/src/icons/CountdownIcon.jsx +2 -7
- package/src/icons/CropIcon.jsx +2 -7
- package/src/icons/DialogueIcon.jsx +2 -7
- package/src/icons/DotsVerticalIcon.jsx +2 -7
- package/src/icons/FolderIcon.jsx +2 -7
- package/src/icons/GoalsIcon.jsx +2 -7
- package/src/icons/IsolateDrumsGradientIcon.jsx +2 -5
- package/src/icons/IsolateDrumsIcon.jsx +2 -7
- package/src/icons/Knob2Icon.jsx +2 -7
- package/src/icons/Knob3Icon.jsx +2 -7
- package/src/icons/KnobIcon.jsx +2 -7
- package/src/icons/LibraryIcon.jsx +2 -7
- package/src/icons/LockIcon.jsx +2 -7
- package/src/icons/LoopIcon.jsx +2 -7
- package/src/icons/MoreIcon.jsx +2 -7
- package/src/icons/MusicIcon.jsx +1 -6
- package/src/icons/NoInternetSignalIcon.jsx +2 -7
- package/src/icons/NoKeysIcon.jsx +2 -7
- package/src/icons/NoMusicIcon.jsx +2 -7
- package/src/icons/NoPianoIcon.jsx +2 -7
- package/src/icons/NoSoundtrackIcon.jsx +2 -7
- package/src/icons/PianoIcon.jsx +1 -6
- package/src/icons/PlayBackSpeedIcon.jsx +2 -7
- package/src/icons/PlayCircleIcon.jsx +2 -7
- package/src/icons/RadioIcon.jsx +2 -7
- package/src/icons/RedoIcon.jsx +2 -7
- package/src/icons/RefreshBackIcon.jsx +2 -7
- package/src/icons/RocketIcon.jsx +2 -7
- package/src/icons/SearchIcon.jsx +2 -7
- package/src/icons/Share2Icon.jsx +30 -0
- package/src/icons/SoundtrackGradientIcon.jsx +2 -5
- package/src/icons/SparkleIcon.jsx +2 -7
- package/src/icons/SparklesGradientIcon.jsx +2 -5
- package/src/icons/SpatialAudioIcon.jsx +2 -7
- package/src/icons/SpeakerLoudIcon.jsx +2 -7
- package/src/icons/SpeedChangerIcon.jsx +2 -7
- package/src/icons/SpliterGradientIcon.jsx +2 -5
- package/src/icons/StringsIcon.jsx +1 -11
- package/src/icons/TargetIcon.jsx +2 -7
- package/src/icons/TrackPreviousIcon.jsx +18 -0
- package/src/icons/TrimIcon.jsx +2 -7
- package/src/icons/UndoIcon.jsx +2 -7
- package/src/icons/UnlockIcon.jsx +2 -7
- package/src/icons/UploadIcon.jsx +2 -7
- package/src/icons/UserIcon.jsx +2 -7
- package/src/icons/VocalsBackgroundIcon.jsx +2 -7
- package/src/icons/VocalsIcon.jsx +1 -6
- package/src/icons/ZoomCloseIcon.jsx +2 -7
- package/src/icons/ZoomInIcon.jsx +2 -7
- package/src/icons.jsx +2 -0
- package/src/index.jsx +1 -0
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { memo, useCallback, useRef, useState } from 'react'
|
|
2
|
+
import { Tooltip } from '../Tooltip/Tooltip'
|
|
2
3
|
import styles from './InputLevelMeter.module.css'
|
|
3
4
|
|
|
4
5
|
const SEGMENT_MIN = 5
|
|
@@ -56,6 +57,13 @@ function faderPositionToGain(pos) {
|
|
|
56
57
|
return 10 ** (db / 20)
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
function formatGainDb(gain) {
|
|
61
|
+
if (gain <= 0) return '-∞ dB'
|
|
62
|
+
const db = 20 * Math.log10(gain)
|
|
63
|
+
if (db <= -96) return '-∞ dB'
|
|
64
|
+
return `${db >= 0 ? '+' : ''}${db.toFixed(1)} dB`
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
function MeterRow({ level }) {
|
|
60
68
|
const pct = getNormalizedLevel(level)
|
|
61
69
|
return (
|
|
@@ -114,6 +122,7 @@ export const InputLevelMeter = memo(function InputLevelMeter({
|
|
|
114
122
|
e.currentTarget.setPointerCapture(e.pointerId)
|
|
115
123
|
isPressedRef.current = true
|
|
116
124
|
setIsPressed(true)
|
|
125
|
+
document.body.style.cursor = 'ew-resize'
|
|
117
126
|
const rect = meterRef.current?.getBoundingClientRect()
|
|
118
127
|
if (rect) {
|
|
119
128
|
const pos = Math.max(
|
|
@@ -144,14 +153,19 @@ export const InputLevelMeter = memo(function InputLevelMeter({
|
|
|
144
153
|
const handlePointerUp = useCallback(() => {
|
|
145
154
|
isPressedRef.current = false
|
|
146
155
|
setIsPressed(false)
|
|
156
|
+
document.body.style.cursor = ''
|
|
147
157
|
}, [])
|
|
148
158
|
|
|
149
|
-
const
|
|
159
|
+
const hasHandler = onVolumeChange && showHandler
|
|
160
|
+
|
|
161
|
+
const meterClassName = [
|
|
162
|
+
styles.bars,
|
|
163
|
+
hasHandler ? styles.interactive : '',
|
|
164
|
+
isPressed ? styles.pressed : '',
|
|
165
|
+
]
|
|
150
166
|
.filter(Boolean)
|
|
151
167
|
.join(' ')
|
|
152
168
|
|
|
153
|
-
const hasHandler = onVolumeChange && showHandler
|
|
154
|
-
|
|
155
169
|
return (
|
|
156
170
|
<div
|
|
157
171
|
className={styles.container}
|
|
@@ -167,10 +181,18 @@ export const InputLevelMeter = memo(function InputLevelMeter({
|
|
|
167
181
|
<MeterRow level={rightLevel} />
|
|
168
182
|
|
|
169
183
|
{hasHandler && (
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
<Tooltip
|
|
185
|
+
content={formatGainDb(volume)}
|
|
186
|
+
open={isPressed}
|
|
187
|
+
side="top"
|
|
188
|
+
sideOffset={8}
|
|
189
|
+
delayDuration={0}
|
|
190
|
+
>
|
|
191
|
+
<div
|
|
192
|
+
className={`${styles.thumb} ${isPressed ? styles.pressed : ''}`}
|
|
193
|
+
style={{ left: `${faderPercent}%` }}
|
|
194
|
+
/>
|
|
195
|
+
</Tooltip>
|
|
174
196
|
)}
|
|
175
197
|
|
|
176
198
|
{showPeakHold && (
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from 'react'
|
|
1
2
|
import { InputLevelMeter } from './InputLevelMeter'
|
|
2
3
|
import { useSimulatedInputLevel } from './useSimulatedInputLevel'
|
|
3
4
|
|
|
@@ -10,18 +11,304 @@ export default {
|
|
|
10
11
|
},
|
|
11
12
|
decorators: [
|
|
12
13
|
(Story) => (
|
|
13
|
-
<div style={{ padding: '16px', background: '#1d1d1d', width: '
|
|
14
|
+
<div style={{ padding: '16px', background: '#1d1d1d', width: '240px' }}>
|
|
14
15
|
<Story />
|
|
15
16
|
</div>
|
|
16
17
|
),
|
|
17
18
|
],
|
|
19
|
+
argTypes: {
|
|
20
|
+
linear: { control: false },
|
|
21
|
+
volume: {
|
|
22
|
+
control: { type: 'range', min: 0, max: 2, step: 0.01 },
|
|
23
|
+
},
|
|
24
|
+
showHandler: { control: 'boolean' },
|
|
25
|
+
showMeter: { control: 'boolean' },
|
|
26
|
+
onVolumeChange: { action: 'volumeChanged' },
|
|
27
|
+
},
|
|
18
28
|
}
|
|
19
29
|
|
|
20
|
-
function AnimatedMeter() {
|
|
30
|
+
function AnimatedMeter(props) {
|
|
21
31
|
const linear = useSimulatedInputLevel()
|
|
22
|
-
return <InputLevelMeter linear={linear} />
|
|
32
|
+
return <InputLevelMeter linear={linear} {...props} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function InteractiveMeter({ initialVolume = 1, ...props }) {
|
|
36
|
+
const [volume, setVolume] = useState(initialVolume)
|
|
37
|
+
const linear = useSimulatedInputLevel()
|
|
38
|
+
|
|
39
|
+
const handleVolumeChange = useCallback(([v]) => {
|
|
40
|
+
setVolume(v)
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
45
|
+
<InputLevelMeter
|
|
46
|
+
linear={linear}
|
|
47
|
+
volume={volume}
|
|
48
|
+
onVolumeChange={handleVolumeChange}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
<span
|
|
52
|
+
style={{ color: 'rgba(255,255,255,0.5)', fontSize: '11px', fontFamily: 'monospace' }}
|
|
53
|
+
>
|
|
54
|
+
gain: {volume.toFixed(3)} | dB: {volume > 0 ? (20 * Math.log10(volume)).toFixed(1) : '-∞'}
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function useFixedLevel(left, right) {
|
|
61
|
+
const [linear, setLinear] = useState([left, right])
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
setLinear([left, right])
|
|
64
|
+
}, [left, right])
|
|
65
|
+
return linear
|
|
23
66
|
}
|
|
24
67
|
|
|
25
68
|
export const Default = {
|
|
26
69
|
render: () => <AnimatedMeter />,
|
|
27
70
|
}
|
|
71
|
+
|
|
72
|
+
export const WithVolumeControl = {
|
|
73
|
+
render: () => <InteractiveMeter />,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const Silent = {
|
|
77
|
+
render: () => <InputLevelMeter linear={[0, 0]} />,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const LowLevel = {
|
|
81
|
+
render: () => {
|
|
82
|
+
function LowLevelMeter() {
|
|
83
|
+
const linear = useFixedLevel(0.01, 0.008)
|
|
84
|
+
return <InputLevelMeter linear={linear} />
|
|
85
|
+
}
|
|
86
|
+
return <LowLevelMeter />
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const MediumLevel = {
|
|
91
|
+
render: () => {
|
|
92
|
+
function MediumLevelMeter() {
|
|
93
|
+
const linear = useFixedLevel(0.15, 0.12)
|
|
94
|
+
return <InputLevelMeter linear={linear} />
|
|
95
|
+
}
|
|
96
|
+
return <MediumLevelMeter />
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const HotLevel = {
|
|
101
|
+
render: () => {
|
|
102
|
+
function HotLevelMeter() {
|
|
103
|
+
const linear = useFixedLevel(0.7, 0.65)
|
|
104
|
+
return <InputLevelMeter linear={linear} />
|
|
105
|
+
}
|
|
106
|
+
return <HotLevelMeter />
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const Clipping = {
|
|
111
|
+
render: () => {
|
|
112
|
+
function ClippingMeter() {
|
|
113
|
+
const linear = useFixedLevel(1.0, 0.95)
|
|
114
|
+
return <InputLevelMeter linear={linear} />
|
|
115
|
+
}
|
|
116
|
+
return <ClippingMeter />
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const MonoLeft = {
|
|
121
|
+
render: () => {
|
|
122
|
+
function MonoLeftMeter() {
|
|
123
|
+
const linear = useFixedLevel(0.5, 0)
|
|
124
|
+
return <InputLevelMeter linear={linear} />
|
|
125
|
+
}
|
|
126
|
+
return <MonoLeftMeter />
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const MonoRight = {
|
|
131
|
+
render: () => {
|
|
132
|
+
function MonoRightMeter() {
|
|
133
|
+
const linear = useFixedLevel(0, 0.5)
|
|
134
|
+
return <InputLevelMeter linear={linear} />
|
|
135
|
+
}
|
|
136
|
+
return <MonoRightMeter />
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const AsymmetricLevels = {
|
|
141
|
+
render: () => {
|
|
142
|
+
function AsymmetricMeter() {
|
|
143
|
+
const linear = useFixedLevel(0.8, 0.2)
|
|
144
|
+
return <InputLevelMeter linear={linear} />
|
|
145
|
+
}
|
|
146
|
+
return <AsymmetricMeter />
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const WithoutHandler = {
|
|
151
|
+
render: () => <AnimatedMeter showHandler={false} />,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const WithoutMeterDot = {
|
|
155
|
+
render: () => <AnimatedMeter showMeter={false} />,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const MeterOnly = {
|
|
159
|
+
render: () => <AnimatedMeter showHandler={false} showMeter={false} />,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const VolumeAtZero = {
|
|
163
|
+
render: () => <InteractiveMeter initialVolume={0} />,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const VolumeAtUnity = {
|
|
167
|
+
render: () => <InteractiveMeter initialVolume={1} />,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const VolumeBoosted = {
|
|
171
|
+
render: () => <InteractiveMeter initialVolume={1.5} />,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const MaxBoost = {
|
|
175
|
+
render: () => <InteractiveMeter initialVolume={2} />,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const PulsingSignal = {
|
|
179
|
+
render: () => {
|
|
180
|
+
function PulsingMeter() {
|
|
181
|
+
const [linear, setLinear] = useState([0, 0])
|
|
182
|
+
const frameRef = useRef(null)
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
let t = 0
|
|
186
|
+
function animate() {
|
|
187
|
+
t += 0.04
|
|
188
|
+
const val = Math.abs(Math.sin(t)) * 0.9
|
|
189
|
+
setLinear([val, val * 0.85])
|
|
190
|
+
frameRef.current = requestAnimationFrame(animate)
|
|
191
|
+
}
|
|
192
|
+
frameRef.current = requestAnimationFrame(animate)
|
|
193
|
+
return () => cancelAnimationFrame(frameRef.current)
|
|
194
|
+
}, [])
|
|
195
|
+
|
|
196
|
+
return <InputLevelMeter linear={linear} />
|
|
197
|
+
}
|
|
198
|
+
return <PulsingMeter />
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const SpikeAndDecay = {
|
|
203
|
+
render: () => {
|
|
204
|
+
function SpikeDecayMeter() {
|
|
205
|
+
const [linear, setLinear] = useState([0, 0])
|
|
206
|
+
const frameRef = useRef(null)
|
|
207
|
+
const currentRef = useRef([0, 0])
|
|
208
|
+
const tickRef = useRef(0)
|
|
209
|
+
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
function animate() {
|
|
212
|
+
tickRef.current++
|
|
213
|
+
|
|
214
|
+
if (tickRef.current % 90 === 0) {
|
|
215
|
+
currentRef.current = [0.95, 0.9]
|
|
216
|
+
} else {
|
|
217
|
+
currentRef.current = currentRef.current.map((v) =>
|
|
218
|
+
Math.max(0, v * 0.97),
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
setLinear([...currentRef.current])
|
|
223
|
+
frameRef.current = requestAnimationFrame(animate)
|
|
224
|
+
}
|
|
225
|
+
frameRef.current = requestAnimationFrame(animate)
|
|
226
|
+
return () => cancelAnimationFrame(frameRef.current)
|
|
227
|
+
}, [])
|
|
228
|
+
|
|
229
|
+
return <InputLevelMeter linear={linear} />
|
|
230
|
+
}
|
|
231
|
+
return <SpikeDecayMeter />
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export const MultipleMeters = {
|
|
236
|
+
render: () => {
|
|
237
|
+
function MultiMeter() {
|
|
238
|
+
const linear = useSimulatedInputLevel()
|
|
239
|
+
const [volumes, setVolumes] = useState([1, 0.7, 0.4, 0.1])
|
|
240
|
+
|
|
241
|
+
const makeHandler = (idx) => ([v]) => {
|
|
242
|
+
setVolumes((prev) => {
|
|
243
|
+
const next = [...prev]
|
|
244
|
+
next[idx] = v
|
|
245
|
+
return next
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const labels = ['Master', 'Vocals', 'Guitar', 'Drums']
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
253
|
+
{volumes.map((vol, i) => (
|
|
254
|
+
<div key={i}>
|
|
255
|
+
<span
|
|
256
|
+
style={{
|
|
257
|
+
color: 'rgba(255,255,255,0.5)',
|
|
258
|
+
fontSize: '10px',
|
|
259
|
+
fontFamily: 'monospace',
|
|
260
|
+
marginBottom: '4px',
|
|
261
|
+
display: 'block',
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
{labels[i]}
|
|
265
|
+
</span>
|
|
266
|
+
<InputLevelMeter
|
|
267
|
+
linear={linear.map((l) => l * vol)}
|
|
268
|
+
volume={vol}
|
|
269
|
+
onVolumeChange={makeHandler(i)}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
))}
|
|
273
|
+
</div>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
return <MultiMeter />
|
|
277
|
+
},
|
|
278
|
+
decorators: [
|
|
279
|
+
(Story) => (
|
|
280
|
+
<div style={{ padding: '16px', background: '#1d1d1d', width: '240px' }}>
|
|
281
|
+
<Story />
|
|
282
|
+
</div>
|
|
283
|
+
),
|
|
284
|
+
],
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export const Playground = {
|
|
288
|
+
args: {
|
|
289
|
+
volume: 1,
|
|
290
|
+
showHandler: true,
|
|
291
|
+
showMeter: true,
|
|
292
|
+
},
|
|
293
|
+
render: (args) => {
|
|
294
|
+
function PlaygroundMeter() {
|
|
295
|
+
const linear = useSimulatedInputLevel()
|
|
296
|
+
const [volume, setVolume] = useState(args.volume)
|
|
297
|
+
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
setVolume(args.volume)
|
|
300
|
+
}, [args.volume])
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<InputLevelMeter
|
|
304
|
+
linear={linear}
|
|
305
|
+
volume={volume}
|
|
306
|
+
onVolumeChange={([v]) => setVolume(v)}
|
|
307
|
+
showHandler={args.showHandler}
|
|
308
|
+
showMeter={args.showMeter}
|
|
309
|
+
/>
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
return <PlaygroundMeter />
|
|
313
|
+
},
|
|
314
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { memo, useRef } from 'react'
|
|
2
|
+
import styles from './PeakLevel.module.css'
|
|
3
|
+
|
|
4
|
+
const PEAK_HOLD_THRESHOLD_HIGH = 0
|
|
5
|
+
const PEAK_HOLD_THRESHOLD_MEDIUM = -1
|
|
6
|
+
|
|
7
|
+
function linearToPeakDb(linear) {
|
|
8
|
+
if (!Array.isArray(linear)) return null
|
|
9
|
+
const peak = Math.max(linear[0] ?? 0, linear[1] ?? 0)
|
|
10
|
+
if (peak <= 0) return null
|
|
11
|
+
const db = 20 * Math.log10(peak)
|
|
12
|
+
if (db <= -96) return null
|
|
13
|
+
return db
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getLevel(db) {
|
|
17
|
+
if (db == null) return 'reset'
|
|
18
|
+
if (db >= PEAK_HOLD_THRESHOLD_HIGH) return 'high'
|
|
19
|
+
if (db >= PEAK_HOLD_THRESHOLD_MEDIUM) return 'medium'
|
|
20
|
+
return 'regular'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatPeakDb(db) {
|
|
24
|
+
if (db == null) return '--'
|
|
25
|
+
return db.toFixed(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const PeakLevel = memo(function PeakLevel({
|
|
29
|
+
linear,
|
|
30
|
+
value,
|
|
31
|
+
onClick,
|
|
32
|
+
}) {
|
|
33
|
+
const peakHoldRef = useRef(null)
|
|
34
|
+
|
|
35
|
+
let displayDb = value ?? null
|
|
36
|
+
|
|
37
|
+
if (linear !== undefined) {
|
|
38
|
+
const currentDb = linearToPeakDb(linear)
|
|
39
|
+
|
|
40
|
+
if (currentDb != null) {
|
|
41
|
+
if (peakHoldRef.current == null || currentDb > peakHoldRef.current) {
|
|
42
|
+
peakHoldRef.current = currentDb
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (linear[0] <= 0 && linear[1] <= 0) {
|
|
47
|
+
peakHoldRef.current = null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
displayDb = peakHoldRef.current
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const level = getLevel(displayDb)
|
|
54
|
+
const text = formatPeakDb(displayDb)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={styles.container}
|
|
59
|
+
onClick={onClick}
|
|
60
|
+
role={onClick ? 'button' : undefined}
|
|
61
|
+
tabIndex={onClick ? 0 : undefined}
|
|
62
|
+
style={onClick ? { cursor: 'pointer' } : undefined}
|
|
63
|
+
>
|
|
64
|
+
<span className={`${styles.value} ${styles[level]}`}>
|
|
65
|
+
{text}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
PeakLevel.displayName = 'PeakLevel'
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
width: 24px;
|
|
6
|
+
padding: 2px 0;
|
|
7
|
+
border-radius: 4px;
|
|
8
|
+
user-select: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.value {
|
|
12
|
+
font-size: 12px;
|
|
13
|
+
font-weight: 500;
|
|
14
|
+
line-height: 16px;
|
|
15
|
+
letter-spacing: 0.04px;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.reset {
|
|
20
|
+
color: rgba(214, 235, 253, 0.19);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.regular {
|
|
24
|
+
color: #00dae8;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.medium {
|
|
28
|
+
color: #ffc53d;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.high {
|
|
32
|
+
color: #ec5d5e;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.container:hover .reset {
|
|
36
|
+
color: rgba(214, 235, 253, 0.19);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.container:hover .regular {
|
|
40
|
+
color: #75ebf5;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.container:hover .medium {
|
|
44
|
+
color: #ffe7b3;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.container:hover .high {
|
|
48
|
+
color: #ff9592;
|
|
49
|
+
}
|