@moises.ai/design-system 4.16.5 → 4.16.7
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/{ForwardBarFillIcon-CUEgLgyx.js → ChordDiagramGridIcon-CxpGQmg4.js} +1029 -976
- package/dist/colors/custom-styles.css +1 -0
- package/dist/icons.js +604 -602
- package/dist/{index-f-eJMg6F.js → index-Dwaw5zqT.js} +151 -151
- package/dist/index.js +1252 -1233
- package/dist/primitives.js +147 -147
- package/package.json +1 -1
- package/src/colors/custom-styles.css +1 -0
- package/src/components/DropdownButton/DropdownButton.jsx +29 -3
- package/src/components/DropdownButton/DropdownButton.module.css +50 -5
- package/src/components/PlayerBar/PlayerBar.jsx +12 -11
- package/src/components/PlayerBar/PlayerBar.stories.jsx +193 -8
- package/src/components/PlayerMasterVolume/PlayerMasterVolume.jsx +54 -0
- package/src/components/PlayerMasterVolume/PlayerMasterVolume.module.css +31 -0
- package/src/components/PlayerMasterVolume/PlayerMasterVolume.stories.jsx +182 -0
- package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.jsx +79 -0
- package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.module.css +23 -0
- package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.stories.jsx +225 -0
- package/src/components/PlayerSmartMetronome/SegmentedField.jsx +19 -0
- package/src/components/PlayerTempoPitch/PlayerTempoPitch.jsx +123 -0
- package/src/components/PlayerTempoPitch/PlayerTempoPitch.module.css +25 -0
- package/src/components/PlayerTempoPitch/PlayerTempoPitch.stories.jsx +215 -0
- package/src/components/PlayerTempoPitch/StepperField.jsx +57 -0
- package/src/components/PlayerTempoPitch/constants.js +40 -0
- package/src/icons/ChordDiagramGridIcon.jsx +22 -0
- package/src/icons/ChordGrid3Icon.jsx +20 -0
- package/src/icons.jsx +2 -0
- package/src/index.jsx +1 -1
package/package.json
CHANGED
|
@@ -19,6 +19,8 @@ export const DropdownButton = ({
|
|
|
19
19
|
...props
|
|
20
20
|
}) => {
|
|
21
21
|
const [hover, setHover] = useState(false)
|
|
22
|
+
const [isTriggerButtonFocused, setIsTriggerButtonFocused] = useState(false)
|
|
23
|
+
const [isChevronFocused, setIsChevronFocused] = useState(false)
|
|
22
24
|
|
|
23
25
|
const handleTriggerClick = (e) => {
|
|
24
26
|
if (onTriggerClick) {
|
|
@@ -28,20 +30,38 @@ export const DropdownButton = ({
|
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
const handleTriggerButtonFocus = () => {
|
|
34
|
+
setIsTriggerButtonFocused(true)
|
|
35
|
+
}
|
|
36
|
+
const handleTriggerButtonBlur = () => {
|
|
37
|
+
setIsTriggerButtonFocused(false)
|
|
38
|
+
}
|
|
39
|
+
const handleChevronFocus = () => {
|
|
40
|
+
setIsChevronFocused(true)
|
|
41
|
+
}
|
|
42
|
+
const handleChevronBlur = () => {
|
|
43
|
+
setIsChevronFocused(false)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ChevronTrigger = onTriggerClick ? 'button' : 'div'
|
|
47
|
+
|
|
31
48
|
return (
|
|
32
49
|
<Flex className={classNames(styles.container, className)} {...props}>
|
|
33
|
-
<Popover.Root open={open} onOpenChange={onOpenChange} modal={true}>
|
|
50
|
+
<Popover.Root open={open} onOpenChange={onOpenChange} modal={true} >
|
|
34
51
|
{onTriggerClick && (
|
|
35
52
|
<Flex
|
|
36
53
|
as="button"
|
|
37
54
|
className={classNames({
|
|
38
55
|
[styles.triggerWithClick]: onTriggerClick,
|
|
56
|
+
[styles.triggerWithClickFocused]: isTriggerButtonFocused,
|
|
39
57
|
[styles.hasLeftColor]: leftColor,
|
|
40
58
|
[styles.freeWidthTrigger]: !withIconTrigger,
|
|
41
59
|
})}
|
|
42
60
|
onMouseEnter={() => setHover(true)}
|
|
43
61
|
onMouseLeave={() => setHover(false)}
|
|
44
62
|
onClick={handleTriggerClick}
|
|
63
|
+
onFocus={handleTriggerButtonFocus}
|
|
64
|
+
onBlur={handleTriggerButtonBlur}
|
|
45
65
|
>
|
|
46
66
|
<Button
|
|
47
67
|
variant="ghost"
|
|
@@ -55,6 +75,7 @@ export const DropdownButton = ({
|
|
|
55
75
|
width: withIconTrigger ? '32px' : undefined,
|
|
56
76
|
backgroundColor: leftColor,
|
|
57
77
|
borderRadius: leftColor ? '3px' : '0px',
|
|
78
|
+
outline: 'none',
|
|
58
79
|
}}
|
|
59
80
|
>
|
|
60
81
|
{trigger}
|
|
@@ -62,12 +83,14 @@ export const DropdownButton = ({
|
|
|
62
83
|
</Flex>
|
|
63
84
|
)}
|
|
64
85
|
<Popover.Trigger asChild>
|
|
65
|
-
<
|
|
86
|
+
<ChevronTrigger
|
|
87
|
+
type={onTriggerClick ? 'button' : undefined}
|
|
66
88
|
className={classNames({
|
|
67
89
|
[styles.hover]: hover,
|
|
68
90
|
[styles.hasLeftColor]: leftColor,
|
|
69
91
|
[styles.hasTriggerWithClick]: onTriggerClick,
|
|
70
92
|
[styles.hasTriggerWithoutClick]: !onTriggerClick,
|
|
93
|
+
[styles.chevronFocused]: isChevronFocused,
|
|
71
94
|
})}
|
|
72
95
|
onMouseEnter={() => {
|
|
73
96
|
setHover(true)
|
|
@@ -75,6 +98,8 @@ export const DropdownButton = ({
|
|
|
75
98
|
onMouseLeave={() => {
|
|
76
99
|
setHover(false)
|
|
77
100
|
}}
|
|
101
|
+
onFocus={handleChevronFocus}
|
|
102
|
+
onBlur={handleChevronBlur}
|
|
78
103
|
>
|
|
79
104
|
{!onTriggerClick && (
|
|
80
105
|
<Button
|
|
@@ -88,6 +113,7 @@ export const DropdownButton = ({
|
|
|
88
113
|
: '0 6px',
|
|
89
114
|
backgroundColor: leftColor,
|
|
90
115
|
borderRadius: leftColor ? '3px' : '0px',
|
|
116
|
+
outline: 'none',
|
|
91
117
|
}}
|
|
92
118
|
>
|
|
93
119
|
{trigger}
|
|
@@ -109,7 +135,7 @@ export const DropdownButton = ({
|
|
|
109
135
|
[styles.hover]: hover,
|
|
110
136
|
})}
|
|
111
137
|
/>
|
|
112
|
-
</
|
|
138
|
+
</ChevronTrigger>
|
|
113
139
|
</Popover.Trigger>
|
|
114
140
|
<Popover.Content
|
|
115
141
|
className={styles.content}
|
|
@@ -14,10 +14,16 @@
|
|
|
14
14
|
&.triggerWithClick {
|
|
15
15
|
cursor: pointer;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
&:focus-visible {
|
|
19
|
+
outline: 2px solid var(--neutral-alpha-8);
|
|
20
|
+
outline-offset: 2px;
|
|
21
|
+
}
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
.triggerWithClick {
|
|
20
|
-
border-width: 1px 0 1px 1px;
|
|
25
|
+
border-width: 1px 0 1px 1px;
|
|
26
|
+
/* top right bottom left */
|
|
21
27
|
border-style: solid;
|
|
22
28
|
border-color: var(--neutral-alpha-3);
|
|
23
29
|
display: flex;
|
|
@@ -36,18 +42,36 @@
|
|
|
36
42
|
&:hover button {
|
|
37
43
|
color: var(--neutral-alpha-12) !important;
|
|
38
44
|
}
|
|
45
|
+
|
|
46
|
+
&:focus-visible {
|
|
47
|
+
outline: 2px solid var(--neutral-alpha-8);
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.triggerWithClickFocused,
|
|
53
|
+
.chevronFocused {
|
|
54
|
+
outline: 2px solid var(--neutral-alpha-8);
|
|
55
|
+
outline-offset: 2px;
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
.triggerWithClick.freeWidthTrigger {
|
|
42
59
|
width: auto;
|
|
43
60
|
}
|
|
44
61
|
|
|
62
|
+
/* quando tiver active no triggerWithClick */
|
|
63
|
+
.triggerWithClick:focus-visible {
|
|
64
|
+
outline: 2px solid var(--neutral-alpha-8);
|
|
65
|
+
outline-offset: 2px;
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
.hasTriggerWithClick {
|
|
46
|
-
border-width: 1px 1px 1px 0;
|
|
69
|
+
border-width: 1px 1px 1px 0;
|
|
70
|
+
/* top right bottom left */
|
|
47
71
|
border-style: solid;
|
|
48
72
|
border-color: var(--neutral-alpha-3);
|
|
49
73
|
display: flex;
|
|
50
|
-
height:
|
|
74
|
+
height: 32px;
|
|
51
75
|
|
|
52
76
|
border-top-right-radius: 4px;
|
|
53
77
|
border-bottom-right-radius: 4px;
|
|
@@ -55,11 +79,22 @@
|
|
|
55
79
|
justify-content: center !important;
|
|
56
80
|
align-items: center;
|
|
57
81
|
padding: 0 4px 0 0;
|
|
82
|
+
margin: 0;
|
|
83
|
+
background: transparent;
|
|
84
|
+
font: inherit;
|
|
85
|
+
color: inherit;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
|
|
88
|
+
&:focus-visible {
|
|
89
|
+
outline: 2px solid var(--neutral-alpha-8);
|
|
90
|
+
outline-offset: 2px;
|
|
91
|
+
}
|
|
58
92
|
}
|
|
59
93
|
|
|
60
94
|
.hover {
|
|
61
95
|
color: var(--neutral-alpha-12) !important;
|
|
62
96
|
cursor: pointer;
|
|
97
|
+
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
.trigger.hover button {
|
|
@@ -94,7 +129,7 @@
|
|
|
94
129
|
|
|
95
130
|
display: flex;
|
|
96
131
|
width: fit-content;
|
|
97
|
-
padding: 12px 16px;
|
|
132
|
+
padding: 12px 16px 16px 16px;
|
|
98
133
|
flex-direction: column;
|
|
99
134
|
justify-content: center;
|
|
100
135
|
align-items: center;
|
|
@@ -103,6 +138,7 @@
|
|
|
103
138
|
animation-duration: 400ms;
|
|
104
139
|
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
|
105
140
|
will-change: transform, opacity;
|
|
141
|
+
|
|
106
142
|
}
|
|
107
143
|
|
|
108
144
|
.content[data-side='top'] {
|
|
@@ -121,11 +157,17 @@
|
|
|
121
157
|
animation-name: slideRightAndFade;
|
|
122
158
|
}
|
|
123
159
|
|
|
160
|
+
.content:focus-visible {
|
|
161
|
+
outline: 2px solid var(--neutral-alpha-8);
|
|
162
|
+
outline-offset: 2px;
|
|
163
|
+
}
|
|
164
|
+
|
|
124
165
|
@keyframes slideUpAndFade {
|
|
125
166
|
from {
|
|
126
167
|
opacity: 0;
|
|
127
168
|
transform: translateY(2px);
|
|
128
169
|
}
|
|
170
|
+
|
|
129
171
|
to {
|
|
130
172
|
opacity: 1;
|
|
131
173
|
transform: translateY(0);
|
|
@@ -137,6 +179,7 @@
|
|
|
137
179
|
opacity: 0;
|
|
138
180
|
transform: translateX(-2px);
|
|
139
181
|
}
|
|
182
|
+
|
|
140
183
|
to {
|
|
141
184
|
opacity: 1;
|
|
142
185
|
transform: translateX(0);
|
|
@@ -148,6 +191,7 @@
|
|
|
148
191
|
opacity: 0;
|
|
149
192
|
transform: translateY(-2px);
|
|
150
193
|
}
|
|
194
|
+
|
|
151
195
|
to {
|
|
152
196
|
opacity: 1;
|
|
153
197
|
transform: translateY(0);
|
|
@@ -159,6 +203,7 @@
|
|
|
159
203
|
opacity: 0;
|
|
160
204
|
transform: translateX(2px);
|
|
161
205
|
}
|
|
206
|
+
|
|
162
207
|
to {
|
|
163
208
|
opacity: 1;
|
|
164
209
|
transform: translateX(0);
|
|
@@ -167,4 +212,4 @@
|
|
|
167
212
|
|
|
168
213
|
.chevron {
|
|
169
214
|
color: var(--neutral-alpha-11);
|
|
170
|
-
}
|
|
215
|
+
}
|
|
@@ -20,7 +20,6 @@ import { Text, Flex, IconButton, DropdownMenu, DropdownButton, Separator, Popove
|
|
|
20
20
|
import styles from './PlayerBar.module.css'
|
|
21
21
|
import { PlayerSlider } from '../PlayerSlider/PlayerSlider'
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
const formatTime = (time) => {
|
|
25
24
|
const totalSeconds = Math.floor(time)
|
|
26
25
|
const minutes = Math.floor(totalSeconds / 60)
|
|
@@ -140,16 +139,18 @@ export const PlayerBar = ({
|
|
|
140
139
|
|
|
141
140
|
<Flex className={styles.flex1} direction="column" gap="0" align="center">
|
|
142
141
|
<Flex align="center" gap="3">
|
|
143
|
-
|
|
144
|
-
<Popover.
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
{volumeContent && (
|
|
143
|
+
<Popover.Root>
|
|
144
|
+
<Popover.Trigger>
|
|
145
|
+
<IconButton variant="ghost" size="2" onClick={onVolumeClick}>
|
|
146
|
+
<Volume2Icon width={16} height={16} />
|
|
147
|
+
</IconButton>
|
|
148
|
+
</Popover.Trigger>
|
|
149
|
+
<Popover.Content>
|
|
150
|
+
{volumeContent}
|
|
151
|
+
</Popover.Content>
|
|
152
|
+
</Popover.Root>
|
|
153
|
+
)}
|
|
153
154
|
<Flex gap='0' >
|
|
154
155
|
<IconButton variant="ghost" size="4" onClick={onSeekBackward} className={styles.controlsIconButton}>
|
|
155
156
|
<RewindBarFillIcon width={30} height={30} />
|
|
@@ -2,6 +2,9 @@ import { useState } from 'react'
|
|
|
2
2
|
import { Flex } from '@radix-ui/themes'
|
|
3
3
|
import { Text } from '../../index'
|
|
4
4
|
import { PlayerBar } from './PlayerBar'
|
|
5
|
+
import { PlayerMasterVolume } from '../PlayerMasterVolume/PlayerMasterVolume'
|
|
6
|
+
import { PlayerTempoPitch } from '../PlayerTempoPitch/PlayerTempoPitch'
|
|
7
|
+
import { PlayerSmartMetronome } from '../PlayerSmartMetronome/PlayerSmartMetronome'
|
|
5
8
|
|
|
6
9
|
const SAMPLE_AUDIO =
|
|
7
10
|
'https://storage.googleapis.com/moises-api-assets/voice/demo_zuri.m4a'
|
|
@@ -17,6 +20,22 @@ function StatefulPlayerBar(args) {
|
|
|
17
20
|
const [lyrics, setLyrics] = useState(args.lyricsEnabled ?? false)
|
|
18
21
|
const [metronome, setMetronome] = useState(args.metronomeEnabled ?? false)
|
|
19
22
|
const [expanded, setExpanded] = useState(args.expanded ?? false)
|
|
23
|
+
const [volume, setVolume] = useState(args.volume ?? 1)
|
|
24
|
+
const [tempo, setTempo] = useState(args.tempo ?? 120)
|
|
25
|
+
const [pitch, setPitch] = useState(args.pitch ?? 'C#')
|
|
26
|
+
const [tuning, setTuning] = useState(args.tuning ?? 440)
|
|
27
|
+
const [subDivision, setSubDivision] = useState(args.subDivision ?? '1x')
|
|
28
|
+
const [metronomeVolume, setMetronomeVolume] = useState(args.metronomeVolume ?? 50)
|
|
29
|
+
const [countIn, setCountIn] = useState(args.countIn ?? 'off')
|
|
30
|
+
const [panValue, setPanValue] = useState(args.panValue ?? 0)
|
|
31
|
+
const [stems, setStems] = useState(
|
|
32
|
+
args.stems ?? [
|
|
33
|
+
{ name: 'Vocals', volume: 80 },
|
|
34
|
+
{ name: 'Drums', volume: 65 },
|
|
35
|
+
{ name: 'Bass', volume: 70 },
|
|
36
|
+
{ name: 'Others', volume: 55 },
|
|
37
|
+
],
|
|
38
|
+
)
|
|
20
39
|
|
|
21
40
|
const seekBy = (delta) => {
|
|
22
41
|
setCurrentTime((time) =>
|
|
@@ -36,6 +55,82 @@ function StatefulPlayerBar(args) {
|
|
|
36
55
|
lyricsEnabled={lyrics}
|
|
37
56
|
metronomeEnabled={metronome}
|
|
38
57
|
expanded={expanded}
|
|
58
|
+
tempo={tempo}
|
|
59
|
+
pitch={pitch}
|
|
60
|
+
volumeContent={
|
|
61
|
+
<PlayerMasterVolume
|
|
62
|
+
volume={volume}
|
|
63
|
+
stems={stems}
|
|
64
|
+
onVolumeChange={([value]) => {
|
|
65
|
+
setVolume(value)
|
|
66
|
+
args.onVolumeChange?.([value])
|
|
67
|
+
}}
|
|
68
|
+
onStemVolumeChange={(name, value) => {
|
|
69
|
+
setStems((prev) =>
|
|
70
|
+
prev.map((stem) =>
|
|
71
|
+
stem.name === name ? { ...stem, volume: value } : stem,
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
args.onStemVolumeChange?.(name, value)
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
}
|
|
78
|
+
tempoPitchContent={
|
|
79
|
+
<PlayerTempoPitch
|
|
80
|
+
tempo={tempo}
|
|
81
|
+
pitch={pitch}
|
|
82
|
+
tuning={tuning}
|
|
83
|
+
onTempoChange={(value) => {
|
|
84
|
+
setTempo(value)
|
|
85
|
+
args.onTempoChange?.(value)
|
|
86
|
+
}}
|
|
87
|
+
onPitchChange={(value) => {
|
|
88
|
+
setPitch(value)
|
|
89
|
+
args.onPitchChange?.(value)
|
|
90
|
+
}}
|
|
91
|
+
onTuningChange={(value) => {
|
|
92
|
+
setTuning(value)
|
|
93
|
+
args.onTuningChange?.(value)
|
|
94
|
+
}}
|
|
95
|
+
onRefresh={() => {
|
|
96
|
+
setTempo(120)
|
|
97
|
+
setPitch('C#')
|
|
98
|
+
setTuning(440)
|
|
99
|
+
args.onTempoPitchRefresh?.()
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
}
|
|
103
|
+
metronomeContent={
|
|
104
|
+
<PlayerSmartMetronome
|
|
105
|
+
subDivision={subDivision}
|
|
106
|
+
volume={metronomeVolume}
|
|
107
|
+
countIn={countIn}
|
|
108
|
+
panValue={panValue}
|
|
109
|
+
onSubDivisionChange={(value) => {
|
|
110
|
+
setSubDivision(value)
|
|
111
|
+
args.onSubDivisionChange?.(value)
|
|
112
|
+
}}
|
|
113
|
+
onVolumeChange={(value) => {
|
|
114
|
+
setMetronomeVolume(value)
|
|
115
|
+
args.onMetronomeVolumeChange?.(value)
|
|
116
|
+
}}
|
|
117
|
+
onCountInChange={(value) => {
|
|
118
|
+
setCountIn(value)
|
|
119
|
+
args.onCountInChange?.(value)
|
|
120
|
+
}}
|
|
121
|
+
onPanChange={(value) => {
|
|
122
|
+
setPanValue(value)
|
|
123
|
+
args.onPanChange?.(value)
|
|
124
|
+
}}
|
|
125
|
+
onRefresh={() => {
|
|
126
|
+
setSubDivision('1x')
|
|
127
|
+
setMetronomeVolume(50)
|
|
128
|
+
setCountIn('off')
|
|
129
|
+
setPanValue(0)
|
|
130
|
+
args.onMetronomeRefresh?.()
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
}
|
|
39
134
|
onPlayPause={() => setIsPlaying((value) => !value)}
|
|
40
135
|
onSeek={setCurrentTime}
|
|
41
136
|
onSeekBackward={() => seekBy(-10)}
|
|
@@ -145,13 +240,51 @@ Use it as a controlled component: pass playback state through props and update i
|
|
|
145
240
|
tempo: {
|
|
146
241
|
description: 'Tempo value shown in the tempo/pitch dropdown trigger.',
|
|
147
242
|
table: { type: { summary: 'number | string' } },
|
|
148
|
-
control: '
|
|
243
|
+
control: 'number',
|
|
149
244
|
},
|
|
150
245
|
pitch: {
|
|
151
246
|
description: 'Pitch value shown in the tempo/pitch dropdown trigger.',
|
|
152
247
|
table: { type: { summary: 'string' } },
|
|
153
248
|
control: 'text',
|
|
154
249
|
},
|
|
250
|
+
tuning: {
|
|
251
|
+
description: 'Reference tuning in Hz for PlayerTempoPitch.',
|
|
252
|
+
table: { type: { summary: 'number' }, defaultValue: { summary: 440 } },
|
|
253
|
+
control: 'select',
|
|
254
|
+
options: [440, 442, 444, 446],
|
|
255
|
+
},
|
|
256
|
+
volume: {
|
|
257
|
+
description: 'Master gain passed to PlayerMasterVolume.',
|
|
258
|
+
table: { type: { summary: 'number' }, defaultValue: { summary: 1 } },
|
|
259
|
+
control: { type: 'range', min: 0, max: 2, step: 0.01 },
|
|
260
|
+
},
|
|
261
|
+
subDivision: {
|
|
262
|
+
description: 'Metronome sub-division for PlayerSmartMetronome.',
|
|
263
|
+
table: { type: { summary: "'0.5x' | '1x' | '2x'" }, defaultValue: { summary: '1x' } },
|
|
264
|
+
control: 'select',
|
|
265
|
+
options: ['0.5x', '1x', '2x'],
|
|
266
|
+
},
|
|
267
|
+
metronomeVolume: {
|
|
268
|
+
description: 'Metronome volume percentage (0–100).',
|
|
269
|
+
table: { type: { summary: 'number' }, defaultValue: { summary: 50 } },
|
|
270
|
+
control: { type: 'range', min: 0, max: 100, step: 1 },
|
|
271
|
+
},
|
|
272
|
+
countIn: {
|
|
273
|
+
description: 'Metronome count-in setting.',
|
|
274
|
+
table: { type: { summary: "'off' | '1 bar' | '2 bars'" }, defaultValue: { summary: 'off' } },
|
|
275
|
+
control: 'select',
|
|
276
|
+
options: ['off', '1 bar', '2 bars'],
|
|
277
|
+
},
|
|
278
|
+
panValue: {
|
|
279
|
+
description: 'Metronome pan position.',
|
|
280
|
+
table: { type: { summary: 'number' }, defaultValue: { summary: 0 } },
|
|
281
|
+
control: { type: 'range', min: -1, max: 1, step: 0.01 },
|
|
282
|
+
},
|
|
283
|
+
stems: {
|
|
284
|
+
description: 'Stem rows for PlayerMasterVolume.',
|
|
285
|
+
table: { type: { summary: '{ name: string, volume: number }[]' } },
|
|
286
|
+
control: false,
|
|
287
|
+
},
|
|
155
288
|
loopEnabled: {
|
|
156
289
|
description: 'Whether the loop control is active.',
|
|
157
290
|
table: { type: { summary: 'boolean' } },
|
|
@@ -195,6 +328,11 @@ Use it as a controlled component: pass playback state through props and update i
|
|
|
195
328
|
table: { type: { summary: 'ReactNode' } },
|
|
196
329
|
control: false,
|
|
197
330
|
},
|
|
331
|
+
volumeContent: {
|
|
332
|
+
description: 'Content rendered inside the volume popover.',
|
|
333
|
+
table: { type: { summary: 'ReactNode' } },
|
|
334
|
+
control: false,
|
|
335
|
+
},
|
|
198
336
|
tempoPitchContent: {
|
|
199
337
|
description: 'Content rendered inside the tempo/pitch dropdown.',
|
|
200
338
|
table: { type: { summary: 'ReactNode' } },
|
|
@@ -205,10 +343,60 @@ Use it as a controlled component: pass playback state through props and update i
|
|
|
205
343
|
table: { type: { summary: 'ReactNode' } },
|
|
206
344
|
control: false,
|
|
207
345
|
},
|
|
208
|
-
|
|
209
|
-
description: '
|
|
210
|
-
table: { type: { summary: '
|
|
211
|
-
|
|
346
|
+
onVolumeChange: {
|
|
347
|
+
description: 'Called with `[gain]` when the master fader moves.',
|
|
348
|
+
table: { type: { summary: '([gain: number]) => void' } },
|
|
349
|
+
action: 'volume changed',
|
|
350
|
+
},
|
|
351
|
+
onStemVolumeChange: {
|
|
352
|
+
description: 'Called when a stem slider changes.',
|
|
353
|
+
table: { type: { summary: '(name: string, value: number) => void' } },
|
|
354
|
+
action: 'stem volume changed',
|
|
355
|
+
},
|
|
356
|
+
onTempoChange: {
|
|
357
|
+
description: 'Called when tempo changes in PlayerTempoPitch.',
|
|
358
|
+
table: { type: { summary: '(tempo: number | "") => void' } },
|
|
359
|
+
action: 'tempo changed',
|
|
360
|
+
},
|
|
361
|
+
onPitchChange: {
|
|
362
|
+
description: 'Called when pitch changes in PlayerTempoPitch.',
|
|
363
|
+
table: { type: { summary: '(pitch: string) => void' } },
|
|
364
|
+
action: 'pitch changed',
|
|
365
|
+
},
|
|
366
|
+
onTuningChange: {
|
|
367
|
+
description: 'Called when tuning changes in PlayerTempoPitch.',
|
|
368
|
+
table: { type: { summary: '(tuning: number) => void' } },
|
|
369
|
+
action: 'tuning changed',
|
|
370
|
+
},
|
|
371
|
+
onTempoPitchRefresh: {
|
|
372
|
+
description: 'Called when PlayerTempoPitch refresh is clicked.',
|
|
373
|
+
table: { type: { summary: '() => void' } },
|
|
374
|
+
action: 'tempo/pitch refreshed',
|
|
375
|
+
},
|
|
376
|
+
onSubDivisionChange: {
|
|
377
|
+
description: 'Called when metronome sub-division changes.',
|
|
378
|
+
table: { type: { summary: '(value: string) => void' } },
|
|
379
|
+
action: 'sub-division changed',
|
|
380
|
+
},
|
|
381
|
+
onMetronomeVolumeChange: {
|
|
382
|
+
description: 'Called when metronome volume changes.',
|
|
383
|
+
table: { type: { summary: '(value: number) => void' } },
|
|
384
|
+
action: 'metronome volume changed',
|
|
385
|
+
},
|
|
386
|
+
onCountInChange: {
|
|
387
|
+
description: 'Called when metronome count-in changes.',
|
|
388
|
+
table: { type: { summary: '(value: string) => void' } },
|
|
389
|
+
action: 'count-in changed',
|
|
390
|
+
},
|
|
391
|
+
onPanChange: {
|
|
392
|
+
description: 'Called when metronome pan changes.',
|
|
393
|
+
table: { type: { summary: '(value: number) => void' } },
|
|
394
|
+
action: 'pan changed',
|
|
395
|
+
},
|
|
396
|
+
onMetronomeRefresh: {
|
|
397
|
+
description: 'Called when PlayerSmartMetronome refresh is clicked.',
|
|
398
|
+
table: { type: { summary: '() => void' } },
|
|
399
|
+
action: 'metronome refreshed',
|
|
212
400
|
},
|
|
213
401
|
onPlayPause: {
|
|
214
402
|
description: 'Called when the main play/pause button is clicked.',
|
|
@@ -295,9 +483,6 @@ const defaultArgs = {
|
|
|
295
483
|
],
|
|
296
484
|
versionTrigger: <Text>v6</Text>,
|
|
297
485
|
versionContent: <Text>Version 6</Text>,
|
|
298
|
-
tempoPitchContent: <Text>120 bpm / 4/4</Text>,
|
|
299
|
-
metronomeContent: <Text>Metronome</Text>,
|
|
300
|
-
volumeContent: <Text>Volume</Text>,
|
|
301
486
|
onVolumeClick: () => { },
|
|
302
487
|
onVideoClick: () => { },
|
|
303
488
|
onShareClick: () => { },
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import styles from './PlayerMasterVolume.module.css'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { Text, Flex, InputLevelMeter, Button, Separator, SliderLibrary } from '../../index'
|
|
4
|
+
import { ChevronUpIcon, ChevronDownIcon } from '@radix-ui/react-icons'
|
|
5
|
+
import { useState } from 'react'
|
|
6
|
+
|
|
7
|
+
export const PlayerMasterVolume = ({
|
|
8
|
+
onVolumeChange,
|
|
9
|
+
onStemVolumeChange,
|
|
10
|
+
volume = 1,
|
|
11
|
+
stems,
|
|
12
|
+
|
|
13
|
+
}) => {
|
|
14
|
+
const [stemsOpen, setStemsOpen] = useState(false)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Flex className={styles.PlayerMasterVolume} direction="column">
|
|
18
|
+
<Flex direction="column" gap="4">
|
|
19
|
+
<Text size="3" weight="medium" className={styles.white}>
|
|
20
|
+
Master Volume
|
|
21
|
+
</Text>
|
|
22
|
+
<Flex gap="4" align="center">
|
|
23
|
+
<InputLevelMeter
|
|
24
|
+
linear={[0, 0]}
|
|
25
|
+
volume={volume}
|
|
26
|
+
onVolumeChange={onVolumeChange}
|
|
27
|
+
/>
|
|
28
|
+
<Button variant="soft" size="1">−∞dB</Button>
|
|
29
|
+
</Flex>
|
|
30
|
+
{stems?.length > 0 && <Separator size='4' />}
|
|
31
|
+
</Flex>
|
|
32
|
+
{stems?.length > 0 && (
|
|
33
|
+
<>
|
|
34
|
+
<Flex width='100%' justify='between' mt="2" as="button" align="center" onClick={() => setStemsOpen(!stemsOpen)} className={styles.accordionButton}>
|
|
35
|
+
<Text size="2" className={styles.neutralAlpha11}>Stems</Text>
|
|
36
|
+
{stemsOpen ? <ChevronUpIcon className={styles.neutralAlpha9} width={16} height={16} /> : <ChevronDownIcon className={styles.neutralAlpha9} width={16} height={16} />}
|
|
37
|
+
</Flex>
|
|
38
|
+
{stemsOpen && (
|
|
39
|
+
<Flex direction="column" gap="3" className={styles.accordionContent}>
|
|
40
|
+
{stems.map((stem) => (
|
|
41
|
+
<Flex width='100%' gap="3" key={stem.name}>
|
|
42
|
+
<Text size="2" className={classNames(styles.neutralAlpha11, styles.flex1)}>{stem.name}</Text>
|
|
43
|
+
<SliderLibrary value={stem.volume} onValueChange={(value) => onStemVolumeChange?.(stem.name, value)} className={styles.flex2} />
|
|
44
|
+
</Flex>
|
|
45
|
+
))}
|
|
46
|
+
</Flex>
|
|
47
|
+
)}
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
</Flex>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
PlayerMasterVolume.displayName = 'PlayerMasterVolume'
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}
|