@moises.ai/design-system 4.16.10 → 4.17.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/dist/{ChordDiagramGridIcon-CxpGQmg4.js → DynamicMicrophone2Icon-BBVZw0ZO.js} +794 -782
- package/dist/icons.js +484 -483
- package/dist/index.js +5454 -5762
- package/package.json +1 -1
- package/src/components/DropdownButton/DropdownButton.jsx +7 -0
- package/src/components/DropdownButton/DropdownButton.stories.jsx +4 -1
- package/src/components/InstrumentSelector/InstrumentSelector.stories.jsx +9 -1
- package/src/components/MessageWithAction/MessageWithAction.jsx +7 -0
- package/src/components/MessageWithAction/MessageWithAction.stories.jsx +4 -1
- package/src/components/Tooltip/Tooltip.jsx +35 -9
- package/src/components/Tooltip/Tooltip.module.css +48 -0
- package/src/components/Tooltip/Tooltip.stories.jsx +42 -0
- package/src/components/TrackControlsToggle/TrackControlsToggle.stories.jsx +7 -3
- package/src/icons/DynamicMicrophone2Icon.jsx +13 -0
- package/src/icons.jsx +1 -0
- package/src/index.jsx +2 -2
- package/src/lib/menu/renderItem.jsx +1 -3
- package/src/components/PlayerBar/PlayerBar.jsx +0 -250
- package/src/components/PlayerBar/PlayerBar.module.css +0 -135
- package/src/components/PlayerBar/PlayerBar.stories.jsx +0 -519
- package/src/components/PlayerMasterVolume/PlayerMasterVolume.jsx +0 -54
- package/src/components/PlayerMasterVolume/PlayerMasterVolume.module.css +0 -31
- package/src/components/PlayerMasterVolume/PlayerMasterVolume.stories.jsx +0 -182
- package/src/components/PlayerSlider/PlayerSlider.jsx +0 -80
- package/src/components/PlayerSlider/PlayerSlider.module.css +0 -180
- package/src/components/PlayerSlider/PlayerSlider.stories.jsx +0 -97
- package/src/components/PlayerSlider/PlayerSliderWaveform.jsx +0 -70
- package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.jsx +0 -79
- package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.module.css +0 -23
- package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.stories.jsx +0 -225
- package/src/components/PlayerSmartMetronome/SegmentedField.jsx +0 -19
- package/src/components/PlayerTempoPitch/PlayerTempoPitch.jsx +0 -123
- package/src/components/PlayerTempoPitch/PlayerTempoPitch.module.css +0 -25
- package/src/components/PlayerTempoPitch/PlayerTempoPitch.stories.jsx +0 -215
- package/src/components/PlayerTempoPitch/StepperField.jsx +0 -57
- package/src/components/PlayerTempoPitch/constants.js +0 -40
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
.PlayerMasterVolume {
|
|
2
|
-
min-width: 286px;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
.neutralAlpha11 {
|
|
6
|
-
color: var(--neutral-alpha-11);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.accordionButton {
|
|
10
|
-
padding: 6px 0;
|
|
11
|
-
|
|
12
|
-
&:hover {
|
|
13
|
-
cursor: pointer;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.neutralAlpha9 {
|
|
18
|
-
color: var(--neutral-alpha-9);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.accordionContent {
|
|
22
|
-
padding: 12px 0 0 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.flex1 {
|
|
26
|
-
flex: 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.flex2 {
|
|
30
|
-
flex: 2;
|
|
31
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
-
import { PlayerMasterVolume } from './PlayerMasterVolume'
|
|
3
|
-
|
|
4
|
-
const DEFAULT_STEMS = [
|
|
5
|
-
{ name: 'Vocals', volume: 80 },
|
|
6
|
-
{ name: 'Drums', volume: 65 },
|
|
7
|
-
{ name: 'Bass', volume: 70 },
|
|
8
|
-
{ name: 'Others', volume: 55 },
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
function StatefulPlayerMasterVolume({
|
|
12
|
-
initialVolume = 1,
|
|
13
|
-
initialStems = DEFAULT_STEMS,
|
|
14
|
-
onVolumeChange,
|
|
15
|
-
onStemVolumeChange,
|
|
16
|
-
}) {
|
|
17
|
-
const [volume, setVolume] = useState(initialVolume)
|
|
18
|
-
const [stems, setStems] = useState(initialStems)
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
setVolume(initialVolume)
|
|
22
|
-
}, [initialVolume])
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
setStems(initialStems)
|
|
26
|
-
}, [initialStems])
|
|
27
|
-
|
|
28
|
-
const handleVolumeChange = useCallback(
|
|
29
|
-
([value]) => {
|
|
30
|
-
setVolume(value)
|
|
31
|
-
onVolumeChange?.([value])
|
|
32
|
-
},
|
|
33
|
-
[onVolumeChange],
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
const handleStemVolumeChange = useCallback(
|
|
37
|
-
(name, value) => {
|
|
38
|
-
setStems((prev) =>
|
|
39
|
-
prev.map((stem) =>
|
|
40
|
-
stem.name === name ? { ...stem, volume: value } : stem,
|
|
41
|
-
),
|
|
42
|
-
)
|
|
43
|
-
onStemVolumeChange?.(name, value)
|
|
44
|
-
},
|
|
45
|
-
[onStemVolumeChange],
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<PlayerMasterVolume
|
|
50
|
-
volume={volume}
|
|
51
|
-
stems={stems}
|
|
52
|
-
onVolumeChange={handleVolumeChange}
|
|
53
|
-
onStemVolumeChange={handleStemVolumeChange}
|
|
54
|
-
/>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default {
|
|
59
|
-
title: 'New Mixer/PlayerMasterVolume',
|
|
60
|
-
component: PlayerMasterVolume,
|
|
61
|
-
parameters: {
|
|
62
|
-
layout: 'centered',
|
|
63
|
-
docs: {
|
|
64
|
-
description: {
|
|
65
|
-
component: `
|
|
66
|
-
Master volume popover for New Mixer. Pass \`volume\` and update it from \`onVolumeChange\` (receives a gain array from \`InputLevelMeter\`). Stem sliders are controlled via \`stems\` and \`onStemVolumeChange\`.
|
|
67
|
-
`,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
decorators: [
|
|
72
|
-
(Story) => (
|
|
73
|
-
<div
|
|
74
|
-
style={{
|
|
75
|
-
width: 286,
|
|
76
|
-
padding: 16,
|
|
77
|
-
background: '#1d1d1d',
|
|
78
|
-
borderRadius: 8,
|
|
79
|
-
boxSizing: 'border-box',
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<Story />
|
|
83
|
-
</div>
|
|
84
|
-
),
|
|
85
|
-
],
|
|
86
|
-
tags: ['autodocs'],
|
|
87
|
-
argTypes: {
|
|
88
|
-
volume: {
|
|
89
|
-
description: 'Master gain passed to InputLevelMeter (0 = mute, 1 = unity).',
|
|
90
|
-
table: { type: { summary: 'number' }, defaultValue: { summary: 1 } },
|
|
91
|
-
control: { type: 'range', min: 0, max: 2, step: 0.01 },
|
|
92
|
-
},
|
|
93
|
-
stems: {
|
|
94
|
-
description: 'Stem rows rendered when the accordion is open.',
|
|
95
|
-
table: { type: { summary: '{ name: string, volume: number }[]' } },
|
|
96
|
-
control: false,
|
|
97
|
-
},
|
|
98
|
-
onVolumeChange: {
|
|
99
|
-
description: 'Called with `[gain]` when the master fader moves.',
|
|
100
|
-
table: { type: { summary: '([gain: number]) => void' } },
|
|
101
|
-
action: 'volume changed',
|
|
102
|
-
},
|
|
103
|
-
onStemVolumeChange: {
|
|
104
|
-
description: 'Called with stem name and slider value (0–100).',
|
|
105
|
-
table: { type: { summary: '(name: string, value: number) => void' } },
|
|
106
|
-
action: 'stem volume changed',
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export const Default = {
|
|
112
|
-
args: {
|
|
113
|
-
volume: 1,
|
|
114
|
-
initialStems: DEFAULT_STEMS,
|
|
115
|
-
},
|
|
116
|
-
render: (args) => (
|
|
117
|
-
<StatefulPlayerMasterVolume
|
|
118
|
-
initialVolume={args.volume}
|
|
119
|
-
initialStems={args.initialStems}
|
|
120
|
-
onVolumeChange={args.onVolumeChange}
|
|
121
|
-
onStemVolumeChange={args.onStemVolumeChange}
|
|
122
|
-
/>
|
|
123
|
-
),
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export const VolumeAtZero = {
|
|
127
|
-
args: {
|
|
128
|
-
volume: 0,
|
|
129
|
-
},
|
|
130
|
-
render: (args) => (
|
|
131
|
-
<StatefulPlayerMasterVolume
|
|
132
|
-
initialVolume={args.volume}
|
|
133
|
-
onVolumeChange={args.onVolumeChange}
|
|
134
|
-
onStemVolumeChange={args.onStemVolumeChange}
|
|
135
|
-
/>
|
|
136
|
-
),
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const VolumeBoosted = {
|
|
140
|
-
args: {
|
|
141
|
-
volume: 1.5,
|
|
142
|
-
},
|
|
143
|
-
render: (args) => (
|
|
144
|
-
<StatefulPlayerMasterVolume
|
|
145
|
-
initialVolume={args.volume}
|
|
146
|
-
onVolumeChange={args.onVolumeChange}
|
|
147
|
-
onStemVolumeChange={args.onStemVolumeChange}
|
|
148
|
-
/>
|
|
149
|
-
),
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export const CustomStems = {
|
|
153
|
-
args: {
|
|
154
|
-
volume: 1,
|
|
155
|
-
initialStems: [
|
|
156
|
-
{ name: 'Vocals', volume: 100 },
|
|
157
|
-
{ name: 'Guitar', volume: 40 },
|
|
158
|
-
{ name: 'Keys', volume: 60 },
|
|
159
|
-
],
|
|
160
|
-
},
|
|
161
|
-
render: (args) => (
|
|
162
|
-
<StatefulPlayerMasterVolume
|
|
163
|
-
initialVolume={args.volume}
|
|
164
|
-
initialStems={args.initialStems}
|
|
165
|
-
onVolumeChange={args.onVolumeChange}
|
|
166
|
-
onStemVolumeChange={args.onStemVolumeChange}
|
|
167
|
-
/>
|
|
168
|
-
),
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export const Playground = {
|
|
172
|
-
args: {
|
|
173
|
-
volume: 1,
|
|
174
|
-
},
|
|
175
|
-
render: (args) => (
|
|
176
|
-
<StatefulPlayerMasterVolume
|
|
177
|
-
initialVolume={args.volume}
|
|
178
|
-
onVolumeChange={args.onVolumeChange}
|
|
179
|
-
onStemVolumeChange={args.onStemVolumeChange}
|
|
180
|
-
/>
|
|
181
|
-
),
|
|
182
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { Slider } from 'radix-ui'
|
|
3
|
-
import { PlayerSliderWaveform } from './PlayerSliderWaveform'
|
|
4
|
-
import styles from './PlayerSlider.module.css'
|
|
5
|
-
import classNames from 'classnames'
|
|
6
|
-
|
|
7
|
-
const clamp = (value, min, max) => Math.min(Math.max(value, min), max)
|
|
8
|
-
|
|
9
|
-
const getProgress = ({ currentTime, duration }) => {
|
|
10
|
-
if (!Number.isFinite(duration) || duration <= 0) return 0
|
|
11
|
-
if (!Number.isFinite(currentTime)) return 0
|
|
12
|
-
|
|
13
|
-
return clamp((currentTime / duration) * 100, 0, 100)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const PlayerSlider = ({
|
|
17
|
-
currentTime = 0,
|
|
18
|
-
duration = 0,
|
|
19
|
-
audio,
|
|
20
|
-
onSeek,
|
|
21
|
-
className,
|
|
22
|
-
}) => {
|
|
23
|
-
const [isInteracting, setIsInteracting] = useState(false)
|
|
24
|
-
const progressValue = getProgress({ currentTime, duration })
|
|
25
|
-
|
|
26
|
-
const handleSeek = ([value]) => {
|
|
27
|
-
onSeek((value / 100) * duration)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const handleInteractionStart = () => {
|
|
31
|
-
setIsInteracting(true)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const handleInteractionEnd = () => {
|
|
35
|
-
setIsInteracting(false)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div
|
|
40
|
-
className={classNames(styles.playerSlider, className)}
|
|
41
|
-
data-interacting={isInteracting ? 'true' : undefined}
|
|
42
|
-
>
|
|
43
|
-
<Slider.Root
|
|
44
|
-
className={styles.sliderRoot}
|
|
45
|
-
data-interacting={isInteracting ? 'true' : undefined}
|
|
46
|
-
min={0}
|
|
47
|
-
max={100}
|
|
48
|
-
step={0.1}
|
|
49
|
-
value={[progressValue]}
|
|
50
|
-
onValueChange={handleSeek}
|
|
51
|
-
onValueCommit={handleInteractionEnd}
|
|
52
|
-
onPointerDown={handleInteractionStart}
|
|
53
|
-
onPointerUp={handleInteractionEnd}
|
|
54
|
-
onPointerCancel={handleInteractionEnd}
|
|
55
|
-
onBlur={handleInteractionEnd}
|
|
56
|
-
aria-label="Playback position"
|
|
57
|
-
>
|
|
58
|
-
{audio && (
|
|
59
|
-
<div className={styles.waveformPreview} aria-hidden="true">
|
|
60
|
-
<PlayerSliderWaveform
|
|
61
|
-
audio={audio}
|
|
62
|
-
currentTime={currentTime}
|
|
63
|
-
duration={duration}
|
|
64
|
-
progress={progressValue}
|
|
65
|
-
/>
|
|
66
|
-
</div>
|
|
67
|
-
)}
|
|
68
|
-
<Slider.Track className={styles.sliderTrack}>
|
|
69
|
-
<Slider.Range className={styles.sliderRange} />
|
|
70
|
-
</Slider.Track>
|
|
71
|
-
<Slider.Thumb
|
|
72
|
-
className={styles.sliderThumb}
|
|
73
|
-
onPointerUp={(e) => e.currentTarget.blur()}
|
|
74
|
-
/>
|
|
75
|
-
</Slider.Root>
|
|
76
|
-
</div>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
PlayerSlider.displayName = 'PlayerSlider'
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
.playerSlider {
|
|
2
|
-
position: relative;
|
|
3
|
-
width: 100%;
|
|
4
|
-
height: 0;
|
|
5
|
-
overflow: visible;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.playerSlider[data-interacting='true']::before,
|
|
9
|
-
.playerSlider:has(.sliderRoot:focus-within)::before {
|
|
10
|
-
content: '';
|
|
11
|
-
position: absolute;
|
|
12
|
-
bottom: 0;
|
|
13
|
-
left: 0;
|
|
14
|
-
right: 0;
|
|
15
|
-
height: 94px;
|
|
16
|
-
background: linear-gradient(180deg, rgba(15, 15, 17, 0.00) 0%, rgba(15, 15, 17, 0.95) 80.29%);
|
|
17
|
-
pointer-events: none;
|
|
18
|
-
z-index: 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.withoutTime {
|
|
22
|
-
grid-template-columns: minmax(0, 1fr);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.disabled {
|
|
26
|
-
opacity: 0.56;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.time {
|
|
30
|
-
color: var(--neutral-alpha-10);
|
|
31
|
-
font-size: 11px;
|
|
32
|
-
font-variant-numeric: tabular-nums;
|
|
33
|
-
line-height: 16px;
|
|
34
|
-
text-align: center;
|
|
35
|
-
white-space: nowrap;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.sliderRoot {
|
|
39
|
-
position: absolute;
|
|
40
|
-
isolation: isolate;
|
|
41
|
-
display: flex;
|
|
42
|
-
align-items: center;
|
|
43
|
-
width: 100%;
|
|
44
|
-
height: 16px;
|
|
45
|
-
touch-action: none;
|
|
46
|
-
user-select: none;
|
|
47
|
-
bottom: -7px;
|
|
48
|
-
z-index: 12;
|
|
49
|
-
background: transparent;
|
|
50
|
-
|
|
51
|
-
&:hover {
|
|
52
|
-
.sliderTrack {
|
|
53
|
-
height: 4px;
|
|
54
|
-
border-radius: 9999px;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.sliderTrack {
|
|
60
|
-
position: relative;
|
|
61
|
-
flex: 1;
|
|
62
|
-
height: 2px;
|
|
63
|
-
overflow: visible;
|
|
64
|
-
border-radius: 0px;
|
|
65
|
-
background: var(--neutral-5);
|
|
66
|
-
transition: height 0.15s ease, border-radius 0.15s ease, background 0.15s ease;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.sliderRoot:hover .sliderTrack {
|
|
70
|
-
height: 4px;
|
|
71
|
-
border-radius: 9999px;
|
|
72
|
-
background: var(--neutral-6);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.bufferedRange {
|
|
76
|
-
position: absolute;
|
|
77
|
-
inset-block: 0;
|
|
78
|
-
left: 0;
|
|
79
|
-
max-width: 100%;
|
|
80
|
-
border-radius: inherit;
|
|
81
|
-
background: var(--neutral-alpha-6);
|
|
82
|
-
pointer-events: none;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.sliderRange {
|
|
86
|
-
position: absolute;
|
|
87
|
-
height: 100%;
|
|
88
|
-
/* border-radius: inherit; */
|
|
89
|
-
background: var(--aqua-8);
|
|
90
|
-
border-top-right-radius: 9999px;
|
|
91
|
-
border-bottom-right-radius: 9999px;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.sliderThumb {
|
|
95
|
-
position: relative;
|
|
96
|
-
display: block;
|
|
97
|
-
z-index: 10;
|
|
98
|
-
width: 10px;
|
|
99
|
-
height: 10px;
|
|
100
|
-
border: 1px solid var(--neutral-alpha-5);
|
|
101
|
-
border-radius: 9999px;
|
|
102
|
-
background: var(--white);
|
|
103
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.24);
|
|
104
|
-
opacity: 0;
|
|
105
|
-
transition: opacity 0.15s ease;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.waveformPreview {
|
|
109
|
-
position: absolute;
|
|
110
|
-
z-index: -1;
|
|
111
|
-
right: 0;
|
|
112
|
-
bottom: 8px;
|
|
113
|
-
left: 0;
|
|
114
|
-
height: 28px;
|
|
115
|
-
overflow: clip;
|
|
116
|
-
pointer-events: none;
|
|
117
|
-
opacity: 0;
|
|
118
|
-
transform: translateY(4px);
|
|
119
|
-
transition:
|
|
120
|
-
opacity 0.15s ease,
|
|
121
|
-
transform 0.15s ease;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.waveformPreview>div {
|
|
125
|
-
width: 100%;
|
|
126
|
-
height: 56px;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.sliderRoot[data-interacting='true'] .waveformPreview {
|
|
130
|
-
opacity: 1;
|
|
131
|
-
transform: translateY(0);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.sliderRoot:hover .sliderThumb,
|
|
135
|
-
.sliderRoot:focus-within .sliderThumb {
|
|
136
|
-
opacity: 1;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.sliderRoot:not([data-disabled]),
|
|
140
|
-
.sliderRoot:not([data-disabled]) .sliderTrack {
|
|
141
|
-
cursor: pointer;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.sliderThumb:focus-visible {
|
|
145
|
-
outline: 2px solid var(--neutral-alpha-8);
|
|
146
|
-
outline-offset: 2px;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.sliderRoot[data-disabled] {
|
|
150
|
-
cursor: default;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.sliderRoot[data-disabled] .sliderRange {
|
|
154
|
-
background: var(--neutral-alpha-6);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.sliderRoot[data-disabled] .bufferedRange {
|
|
158
|
-
background: var(--neutral-alpha-4);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.sliderRoot[data-disabled] .sliderThumb {
|
|
162
|
-
cursor: default;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.sliderRoot[data-interacting='true'] .waveformPreview,
|
|
166
|
-
.sliderRoot:focus-within .waveformPreview {
|
|
167
|
-
opacity: 1;
|
|
168
|
-
transform: translateY(0);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@media (max-width: 480px) {
|
|
172
|
-
.playerSlider {
|
|
173
|
-
grid-template-columns: 36px minmax(0, 1fr) 36px;
|
|
174
|
-
gap: 8px;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.sliderThumb:focus {
|
|
179
|
-
outline: none;
|
|
180
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { Flex } from '@radix-ui/themes'
|
|
3
|
-
import { PlayerSlider } from './PlayerSlider'
|
|
4
|
-
|
|
5
|
-
const SAMPLE_AUDIO =
|
|
6
|
-
'https://storage.googleapis.com/moises-api-assets/voice/demo_zuri.m4a'
|
|
7
|
-
|
|
8
|
-
function StatefulPlayerSlider(args) {
|
|
9
|
-
const [currentTime, setCurrentTime] = useState(args.currentTime)
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<PlayerSlider
|
|
13
|
-
{...args}
|
|
14
|
-
currentTime={currentTime}
|
|
15
|
-
onSeek={setCurrentTime}
|
|
16
|
-
/>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default {
|
|
21
|
-
title: 'New Mixer/PlayerSlider',
|
|
22
|
-
component: PlayerSlider,
|
|
23
|
-
parameters: {
|
|
24
|
-
layout: 'centered',
|
|
25
|
-
docs: {
|
|
26
|
-
description: {
|
|
27
|
-
component: `
|
|
28
|
-
Seekable playback slider for New Mixer. Pass \`currentTime\` and \`duration\`, then update \`currentTime\` from \`onSeek\`. Progress is derived automatically from those values.
|
|
29
|
-
|
|
30
|
-
When \`audio\` is provided, a waveform appears while the user presses or drags the thumb.
|
|
31
|
-
`,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
decorators: [
|
|
36
|
-
(Story) => (
|
|
37
|
-
<Flex
|
|
38
|
-
align="end"
|
|
39
|
-
justify="center"
|
|
40
|
-
width="640px"
|
|
41
|
-
maxWidth="100%"
|
|
42
|
-
p="6"
|
|
43
|
-
style={{ background: 'var(--neutral-8)', boxSizing: 'border-box' }}
|
|
44
|
-
>
|
|
45
|
-
<Flex width="100%">
|
|
46
|
-
<Story />
|
|
47
|
-
</Flex>
|
|
48
|
-
</Flex>
|
|
49
|
-
),
|
|
50
|
-
],
|
|
51
|
-
tags: ['autodocs'],
|
|
52
|
-
argTypes: {
|
|
53
|
-
currentTime: {
|
|
54
|
-
description: 'Current playback time in seconds.',
|
|
55
|
-
table: { type: { summary: 'number' } },
|
|
56
|
-
control: 'number',
|
|
57
|
-
},
|
|
58
|
-
duration: {
|
|
59
|
-
description: 'Total playback duration in seconds.',
|
|
60
|
-
table: { type: { summary: 'number' } },
|
|
61
|
-
control: 'number',
|
|
62
|
-
},
|
|
63
|
-
audio: {
|
|
64
|
-
description:
|
|
65
|
-
'Audio URL used to render the waveform while interacting with the slider.',
|
|
66
|
-
table: { type: { summary: 'string' } },
|
|
67
|
-
control: 'text',
|
|
68
|
-
},
|
|
69
|
-
className: {
|
|
70
|
-
description: 'Optional class name applied to the root element.',
|
|
71
|
-
table: { type: { summary: 'string' } },
|
|
72
|
-
control: 'text',
|
|
73
|
-
},
|
|
74
|
-
onSeek: {
|
|
75
|
-
description: 'Called with the next playback time in seconds.',
|
|
76
|
-
table: { type: { summary: '(time: number) => void' } },
|
|
77
|
-
action: 'seek changed',
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export const Default = {
|
|
83
|
-
args: {
|
|
84
|
-
currentTime: 82,
|
|
85
|
-
duration: 215,
|
|
86
|
-
audio: SAMPLE_AUDIO,
|
|
87
|
-
},
|
|
88
|
-
render: (args) => <StatefulPlayerSlider {...args} />,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const WithoutWaveform = {
|
|
92
|
-
args: {
|
|
93
|
-
currentTime: 82,
|
|
94
|
-
duration: 215,
|
|
95
|
-
},
|
|
96
|
-
render: (args) => <StatefulPlayerSlider {...args} />,
|
|
97
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import WavesurferPlayer from '@wavesurfer/react'
|
|
3
|
-
|
|
4
|
-
const clamp = (value, min, max) => Math.min(Math.max(value, min), max)
|
|
5
|
-
|
|
6
|
-
export function PlayerSliderWaveform({
|
|
7
|
-
audio,
|
|
8
|
-
progress = 0,
|
|
9
|
-
currentTime = 0,
|
|
10
|
-
duration = 0,
|
|
11
|
-
}) {
|
|
12
|
-
const [isSafari, setIsSafari] = useState(false)
|
|
13
|
-
const waveSurferRef = useRef(null)
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (typeof navigator !== 'undefined') {
|
|
17
|
-
setIsSafari(/^((?!chrome|android).)*safari/i.test(navigator.userAgent))
|
|
18
|
-
}
|
|
19
|
-
}, [])
|
|
20
|
-
|
|
21
|
-
const syncProgress = useCallback(
|
|
22
|
-
(ws = waveSurferRef.current) => {
|
|
23
|
-
if (!ws) return
|
|
24
|
-
|
|
25
|
-
const safeProgress = clamp(progress, 0, 100)
|
|
26
|
-
const waveformDuration = ws.getDuration?.()
|
|
27
|
-
const targetTime =
|
|
28
|
-
Number.isFinite(waveformDuration) && waveformDuration > 0
|
|
29
|
-
? (safeProgress / 100) * waveformDuration
|
|
30
|
-
: currentTime
|
|
31
|
-
|
|
32
|
-
if (!Number.isFinite(targetTime)) return
|
|
33
|
-
|
|
34
|
-
ws.setTime(targetTime)
|
|
35
|
-
},
|
|
36
|
-
[currentTime, progress],
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
const handleReady = useCallback(
|
|
40
|
-
(ws) => {
|
|
41
|
-
waveSurferRef.current = ws
|
|
42
|
-
syncProgress(ws)
|
|
43
|
-
},
|
|
44
|
-
[syncProgress],
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
syncProgress()
|
|
49
|
-
}, [duration, syncProgress])
|
|
50
|
-
|
|
51
|
-
if (!audio) return null
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<WavesurferPlayer
|
|
55
|
-
url={audio}
|
|
56
|
-
waveColor="rgba(255, 255, 255, 0.22)"
|
|
57
|
-
progressColor="rgba(0, 227, 254, 1)"
|
|
58
|
-
normalize
|
|
59
|
-
barWidth={2}
|
|
60
|
-
barHeight={1}
|
|
61
|
-
cursorWidth={0}
|
|
62
|
-
height={56}
|
|
63
|
-
interact={false}
|
|
64
|
-
onReady={handleReady}
|
|
65
|
-
backend={isSafari ? 'WebAudio' : 'MediaElement'}
|
|
66
|
-
/>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
PlayerSliderWaveform.displayName = 'PlayerSliderWaveform'
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import styles from './PlayerSmartMetronome.module.css'
|
|
2
|
-
import { Flex, Text, SliderLibrary, Separator, IconButton, PanControl } from '../../index'
|
|
3
|
-
import { RefreshBackIcon } from '../../icons'
|
|
4
|
-
import { SegmentedField } from './SegmentedField'
|
|
5
|
-
|
|
6
|
-
export const PlayerSmartMetronome = ({
|
|
7
|
-
subDivision = '1x',
|
|
8
|
-
volume = 50,
|
|
9
|
-
countIn = 'off',
|
|
10
|
-
panValue = 0,
|
|
11
|
-
onSubDivisionChange,
|
|
12
|
-
onVolumeChange,
|
|
13
|
-
onCountInChange,
|
|
14
|
-
onRefresh,
|
|
15
|
-
onPanChange,
|
|
16
|
-
}) => {
|
|
17
|
-
return (
|
|
18
|
-
<Flex className={styles.PlayerSmartMetronome} direction="column" gap="4">
|
|
19
|
-
<Flex align="center" gap="2" justify="between">
|
|
20
|
-
<Text size="3" weight="medium" className={styles.white}>
|
|
21
|
-
Smart Metronome
|
|
22
|
-
</Text>
|
|
23
|
-
|
|
24
|
-
<IconButton variant="ghost" size="1" onClick={onRefresh}>
|
|
25
|
-
<RefreshBackIcon width={16} height={16} />
|
|
26
|
-
</IconButton>
|
|
27
|
-
</Flex>
|
|
28
|
-
|
|
29
|
-
<SegmentedField
|
|
30
|
-
label="Sub-division"
|
|
31
|
-
items={[
|
|
32
|
-
{ value: '0.5x', label: '0.5x' },
|
|
33
|
-
{ value: '1x', label: '1x' },
|
|
34
|
-
{ value: '2x', label: '2x' },
|
|
35
|
-
]}
|
|
36
|
-
value={subDivision}
|
|
37
|
-
onChange={onSubDivisionChange}
|
|
38
|
-
/>
|
|
39
|
-
<Flex gap="3" align="end" justify="between">
|
|
40
|
-
<Flex className={styles.flex1} direction="column" gap="1">
|
|
41
|
-
<Flex width="100%" justify="between" align="center">
|
|
42
|
-
<Text size="2" className={styles.neutral12}>
|
|
43
|
-
Metronome volume
|
|
44
|
-
</Text>
|
|
45
|
-
|
|
46
|
-
<Text size="2" className={styles.neutralAlpha9}>
|
|
47
|
-
{volume}%
|
|
48
|
-
</Text>
|
|
49
|
-
</Flex>
|
|
50
|
-
<SliderLibrary
|
|
51
|
-
value={volume}
|
|
52
|
-
onValueChange={onVolumeChange}
|
|
53
|
-
formatValue={(v) => `${v}%`}
|
|
54
|
-
aria-label="Volume"
|
|
55
|
-
/>
|
|
56
|
-
</Flex>
|
|
57
|
-
|
|
58
|
-
<PanControl value={panValue} onChange={onPanChange} />
|
|
59
|
-
</Flex>
|
|
60
|
-
|
|
61
|
-
<Flex justify="center" width="100%">
|
|
62
|
-
<Separator size="1" className={styles.neutralAlpha4} />
|
|
63
|
-
</Flex>
|
|
64
|
-
|
|
65
|
-
<SegmentedField
|
|
66
|
-
label="Count-in"
|
|
67
|
-
items={[
|
|
68
|
-
{ value: 'off', label: 'Off' },
|
|
69
|
-
{ value: '1 bar', label: '1 bar' },
|
|
70
|
-
{ value: '2 bars', label: '2 bars' },
|
|
71
|
-
]}
|
|
72
|
-
value={countIn}
|
|
73
|
-
onChange={onCountInChange}
|
|
74
|
-
/>
|
|
75
|
-
</Flex>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
PlayerSmartMetronome.displayName = 'PlayerSmartMetronome'
|