@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.
Files changed (28) hide show
  1. package/dist/{ForwardBarFillIcon-CUEgLgyx.js → ChordDiagramGridIcon-CxpGQmg4.js} +1029 -976
  2. package/dist/colors/custom-styles.css +1 -0
  3. package/dist/icons.js +604 -602
  4. package/dist/{index-f-eJMg6F.js → index-Dwaw5zqT.js} +151 -151
  5. package/dist/index.js +1252 -1233
  6. package/dist/primitives.js +147 -147
  7. package/package.json +1 -1
  8. package/src/colors/custom-styles.css +1 -0
  9. package/src/components/DropdownButton/DropdownButton.jsx +29 -3
  10. package/src/components/DropdownButton/DropdownButton.module.css +50 -5
  11. package/src/components/PlayerBar/PlayerBar.jsx +12 -11
  12. package/src/components/PlayerBar/PlayerBar.stories.jsx +193 -8
  13. package/src/components/PlayerMasterVolume/PlayerMasterVolume.jsx +54 -0
  14. package/src/components/PlayerMasterVolume/PlayerMasterVolume.module.css +31 -0
  15. package/src/components/PlayerMasterVolume/PlayerMasterVolume.stories.jsx +182 -0
  16. package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.jsx +79 -0
  17. package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.module.css +23 -0
  18. package/src/components/PlayerSmartMetronome/PlayerSmartMetronome.stories.jsx +225 -0
  19. package/src/components/PlayerSmartMetronome/SegmentedField.jsx +19 -0
  20. package/src/components/PlayerTempoPitch/PlayerTempoPitch.jsx +123 -0
  21. package/src/components/PlayerTempoPitch/PlayerTempoPitch.module.css +25 -0
  22. package/src/components/PlayerTempoPitch/PlayerTempoPitch.stories.jsx +215 -0
  23. package/src/components/PlayerTempoPitch/StepperField.jsx +57 -0
  24. package/src/components/PlayerTempoPitch/constants.js +40 -0
  25. package/src/icons/ChordDiagramGridIcon.jsx +22 -0
  26. package/src/icons/ChordGrid3Icon.jsx +20 -0
  27. package/src/icons.jsx +2 -0
  28. package/src/index.jsx +1 -1
@@ -0,0 +1,182 @@
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
+ }
@@ -0,0 +1,79 @@
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'
@@ -0,0 +1,23 @@
1
+ .PlayerSmartMetronome {
2
+ min-width: 258px;
3
+ }
4
+
5
+ .neutral12 {
6
+ color: var(--neutral-12);
7
+ }
8
+
9
+ .neutralAlpha4 {
10
+ color: var(--neutral-alpha-4);
11
+ }
12
+
13
+ .flex1 {
14
+ flex: 1;
15
+ }
16
+
17
+ .neutralAlpha9 {
18
+ color: var(--neutral-alpha-9);
19
+ }
20
+
21
+ .white {
22
+ color: var(--white);
23
+ }
@@ -0,0 +1,225 @@
1
+ import { useCallback, useEffect, useState } from 'react'
2
+ import { PlayerSmartMetronome } from './PlayerSmartMetronome'
3
+
4
+ const DEFAULTS = {
5
+ subDivision: '1x',
6
+ volume: 50,
7
+ countIn: 'off',
8
+ panValue: 0,
9
+ }
10
+
11
+ function StatefulPlayerSmartMetronome({
12
+ initialSubDivision = DEFAULTS.subDivision,
13
+ initialVolume = DEFAULTS.volume,
14
+ initialCountIn = DEFAULTS.countIn,
15
+ initialPanValue = DEFAULTS.panValue,
16
+ onSubDivisionChange,
17
+ onVolumeChange,
18
+ onCountInChange,
19
+ onPanChange,
20
+ onRefresh,
21
+ }) {
22
+ const [subDivision, setSubDivision] = useState(initialSubDivision)
23
+ const [volume, setVolume] = useState(initialVolume)
24
+ const [countIn, setCountIn] = useState(initialCountIn)
25
+ const [panValue, setPanValue] = useState(initialPanValue)
26
+
27
+ useEffect(() => {
28
+ setSubDivision(initialSubDivision)
29
+ }, [initialSubDivision])
30
+
31
+ useEffect(() => {
32
+ setVolume(initialVolume)
33
+ }, [initialVolume])
34
+
35
+ useEffect(() => {
36
+ setCountIn(initialCountIn)
37
+ }, [initialCountIn])
38
+
39
+ useEffect(() => {
40
+ setPanValue(initialPanValue)
41
+ }, [initialPanValue])
42
+
43
+ const handleRefresh = useCallback(() => {
44
+ setSubDivision(DEFAULTS.subDivision)
45
+ setVolume(DEFAULTS.volume)
46
+ setCountIn(DEFAULTS.countIn)
47
+ setPanValue(DEFAULTS.panValue)
48
+ onRefresh?.()
49
+ }, [onRefresh])
50
+
51
+ return (
52
+ <PlayerSmartMetronome
53
+ subDivision={subDivision}
54
+ volume={volume}
55
+ countIn={countIn}
56
+ panValue={panValue}
57
+ onSubDivisionChange={(value) => {
58
+ setSubDivision(value)
59
+ onSubDivisionChange?.(value)
60
+ }}
61
+ onVolumeChange={(value) => {
62
+ setVolume(value)
63
+ onVolumeChange?.(value)
64
+ }}
65
+ onCountInChange={(value) => {
66
+ setCountIn(value)
67
+ onCountInChange?.(value)
68
+ }}
69
+ onPanChange={(value) => {
70
+ setPanValue(value)
71
+ onPanChange?.(value)
72
+ }}
73
+ onRefresh={handleRefresh}
74
+ />
75
+ )
76
+ }
77
+
78
+ export default {
79
+ title: 'New Mixer/PlayerSmartMetronome',
80
+ component: PlayerSmartMetronome,
81
+ parameters: {
82
+ layout: 'centered',
83
+ docs: {
84
+ description: {
85
+ component: `
86
+ Smart metronome panel for New Mixer. Control sub-division, volume, pan and count-in via props and their change handlers.
87
+ `,
88
+ },
89
+ },
90
+ },
91
+ decorators: [
92
+ (Story) => (
93
+ <div
94
+ style={{
95
+ width: 258,
96
+ padding: 16,
97
+ background: '#1d1d1d',
98
+ borderRadius: 8,
99
+ boxSizing: 'border-box',
100
+ }}
101
+ >
102
+ <Story />
103
+ </div>
104
+ ),
105
+ ],
106
+ tags: ['autodocs'],
107
+ argTypes: {
108
+ subDivision: {
109
+ description: 'Selected sub-division multiplier.',
110
+ table: { type: { summary: "'0.5x' | '1x' | '2x'" }, defaultValue: { summary: '1x' } },
111
+ control: 'select',
112
+ options: ['0.5x', '1x', '2x'],
113
+ },
114
+ volume: {
115
+ description: 'Metronome volume percentage (0–100).',
116
+ table: { type: { summary: 'number' }, defaultValue: { summary: 50 } },
117
+ control: { type: 'range', min: 0, max: 100, step: 1 },
118
+ },
119
+ countIn: {
120
+ description: 'Count-in bars before playback.',
121
+ table: { type: { summary: "'off' | '1 bar' | '2 bars'" }, defaultValue: { summary: 'off' } },
122
+ control: 'select',
123
+ options: ['off', '1 bar', '2 bars'],
124
+ },
125
+ panValue: {
126
+ description: 'Pan position passed to PanControl.',
127
+ table: { type: { summary: 'number' }, defaultValue: { summary: 0 } },
128
+ control: { type: 'range', min: -1, max: 1, step: 0.01 },
129
+ },
130
+ onSubDivisionChange: {
131
+ table: { type: { summary: '(value: string) => void' } },
132
+ action: 'sub-division changed',
133
+ },
134
+ onVolumeChange: {
135
+ table: { type: { summary: '(value: number) => void' } },
136
+ action: 'volume changed',
137
+ },
138
+ onCountInChange: {
139
+ table: { type: { summary: '(value: string) => void' } },
140
+ action: 'count-in changed',
141
+ },
142
+ onPanChange: {
143
+ table: { type: { summary: '(value: number) => void' } },
144
+ action: 'pan changed',
145
+ },
146
+ onRefresh: {
147
+ table: { type: { summary: '() => void' } },
148
+ action: 'refreshed',
149
+ },
150
+ },
151
+ }
152
+
153
+ export const Default = {
154
+ render: (args) => (
155
+ <StatefulPlayerSmartMetronome
156
+ initialSubDivision={args.subDivision}
157
+ initialVolume={args.volume}
158
+ initialCountIn={args.countIn}
159
+ initialPanValue={args.panValue}
160
+ onSubDivisionChange={args.onSubDivisionChange}
161
+ onVolumeChange={args.onVolumeChange}
162
+ onCountInChange={args.onCountInChange}
163
+ onPanChange={args.onPanChange}
164
+ onRefresh={args.onRefresh}
165
+ />
166
+ ),
167
+ }
168
+
169
+ export const HalfSubDivision = {
170
+ args: {
171
+ subDivision: '0.5x',
172
+ volume: 75,
173
+ },
174
+ render: (args) => (
175
+ <StatefulPlayerSmartMetronome
176
+ initialSubDivision={args.subDivision}
177
+ initialVolume={args.volume}
178
+ onSubDivisionChange={args.onSubDivisionChange}
179
+ onVolumeChange={args.onVolumeChange}
180
+ onCountInChange={args.onCountInChange}
181
+ onPanChange={args.onPanChange}
182
+ onRefresh={args.onRefresh}
183
+ />
184
+ ),
185
+ }
186
+
187
+ export const WithCountIn = {
188
+ args: {
189
+ countIn: '2 bars',
190
+ volume: 80,
191
+ },
192
+ render: (args) => (
193
+ <StatefulPlayerSmartMetronome
194
+ initialCountIn={args.countIn}
195
+ initialVolume={args.volume}
196
+ onSubDivisionChange={args.onSubDivisionChange}
197
+ onVolumeChange={args.onVolumeChange}
198
+ onCountInChange={args.onCountInChange}
199
+ onPanChange={args.onPanChange}
200
+ onRefresh={args.onRefresh}
201
+ />
202
+ ),
203
+ }
204
+
205
+ export const Playground = {
206
+ args: {
207
+ subDivision: '1x',
208
+ volume: 50,
209
+ countIn: 'off',
210
+ panValue: 0,
211
+ },
212
+ render: (args) => (
213
+ <StatefulPlayerSmartMetronome
214
+ initialSubDivision={args.subDivision}
215
+ initialVolume={args.volume}
216
+ initialCountIn={args.countIn}
217
+ initialPanValue={args.panValue}
218
+ onSubDivisionChange={args.onSubDivisionChange}
219
+ onVolumeChange={args.onVolumeChange}
220
+ onCountInChange={args.onCountInChange}
221
+ onPanChange={args.onPanChange}
222
+ onRefresh={args.onRefresh}
223
+ />
224
+ ),
225
+ }
@@ -0,0 +1,19 @@
1
+ import { Flex, Text, SegmentedControl } from '../../index'
2
+ import styles from './PlayerSmartMetronome.module.css'
3
+
4
+ export function SegmentedField({ label, items, value, onChange }) {
5
+ return (
6
+ <Flex direction="column" gap="1">
7
+ <Text size="2" className={styles.neutral12}>
8
+ {label}
9
+ </Text>
10
+
11
+ <SegmentedControl
12
+ items={items}
13
+ selectedType={value}
14
+ handleTypeChange={onChange}
15
+ size="2"
16
+ />
17
+ </Flex>
18
+ )
19
+ }
@@ -0,0 +1,123 @@
1
+ import { Flex, Separator, Text, IconButton, Select } from '../../index'
2
+ import styles from './PlayerTempoPitch.module.css'
3
+ import { RefreshBackIcon } from '../../icons'
4
+ import { MIN_VALUE_BPM, MAX_VALUE_BPM, TEMPO_RANGES, PITCH_ITEMS, TUNING_ITEMS } from './constants'
5
+ import { StepperField } from './StepperField'
6
+
7
+ export const PlayerTempoPitch = ({
8
+ tempo = 120,
9
+ pitch = 'C#',
10
+ tuning = 440,
11
+ onTempoChange,
12
+ onPitchChange,
13
+ onTuningChange,
14
+ onRefresh,
15
+ }) => {
16
+ const handleTempoChange = (e) => {
17
+ const raw = e.target.value
18
+
19
+ if (raw === '') {
20
+ onTempoChange?.('')
21
+ return
22
+ }
23
+
24
+ const parsed = Number(raw)
25
+ if (Number.isNaN(parsed)) return
26
+
27
+ onTempoChange?.(parsed)
28
+ }
29
+
30
+ const handleTempoBlur = () => {
31
+ const clamped = Math.min(
32
+ MAX_VALUE_BPM,
33
+ Math.max(MIN_VALUE_BPM, Number(tempo) || MIN_VALUE_BPM),
34
+ )
35
+ onTempoChange?.(clamped)
36
+ }
37
+
38
+ const handleTempoDecrease = () => {
39
+ onTempoChange?.(Math.max(MIN_VALUE_BPM, tempo - 1))
40
+ }
41
+
42
+ const handleTempoIncrease = () => {
43
+ onTempoChange?.(Math.min(MAX_VALUE_BPM, tempo + 1))
44
+ }
45
+
46
+ const handlePitchChange = (e) => {
47
+ onPitchChange?.(e.target.value)
48
+ }
49
+
50
+ const handlePitchBlur = () => {
51
+ const exists = PITCH_ITEMS.find((item) => item.value === pitch)
52
+ if (!exists) onPitchChange?.(PITCH_ITEMS[0].value)
53
+ }
54
+
55
+ const handlePitchDecrease = () => {
56
+ const currentIndex = PITCH_ITEMS.findIndex((item) => item.value === pitch)
57
+ if (currentIndex > 0) onPitchChange?.(PITCH_ITEMS[currentIndex - 1].value)
58
+ }
59
+
60
+ const handlePitchIncrease = () => {
61
+ const currentIndex = PITCH_ITEMS.findIndex((item) => item.value === pitch)
62
+ if (currentIndex < PITCH_ITEMS.length - 1) {
63
+ onPitchChange?.(PITCH_ITEMS[currentIndex + 1].value)
64
+ }
65
+ }
66
+
67
+ return (
68
+ <Flex direction="column" gap="4">
69
+ <Flex align="center" gap="2" justify="between">
70
+ <Text size="3" weight="medium" className={styles.white}>
71
+ Tempo & Pitch
72
+ </Text>
73
+
74
+ <IconButton variant="ghost" size="1" onClick={onRefresh}>
75
+ <RefreshBackIcon width={16} height={16} />
76
+ </IconButton>
77
+ </Flex>
78
+
79
+ <StepperField
80
+ label="Tempo change"
81
+ hint={TEMPO_RANGES.find((item) => item.maxBpm >= tempo)?.marking}
82
+ value={tempo}
83
+ onChange={handleTempoChange}
84
+ onBlur={handleTempoBlur}
85
+ onDecrease={handleTempoDecrease}
86
+ onIncrease={handleTempoIncrease}
87
+ disableDecrease={tempo <= MIN_VALUE_BPM}
88
+ disableIncrease={tempo >= MAX_VALUE_BPM}
89
+ />
90
+
91
+ <Separator size="4" className={styles.neutralAlpha4} />
92
+
93
+ <StepperField
94
+ label="Pitch change"
95
+ value={pitch}
96
+ onChange={handlePitchChange}
97
+ onBlur={handlePitchBlur}
98
+ onDecrease={handlePitchDecrease}
99
+ onIncrease={handlePitchIncrease}
100
+ disableDecrease={pitch === PITCH_ITEMS[0].value}
101
+ disableIncrease={pitch === PITCH_ITEMS[PITCH_ITEMS.length - 1].value}
102
+ />
103
+
104
+ <Flex direction="column" gap="1">
105
+ <Text size="2" className={styles.neutral12}>
106
+ Tuning (Hz)
107
+ </Text>
108
+
109
+ <Select
110
+ value={tuning}
111
+ onChange={onTuningChange}
112
+ items={TUNING_ITEMS}
113
+ placeholder="Select tuning"
114
+ size="2"
115
+ variant="soft"
116
+ maxHeight="300px"
117
+ />
118
+ </Flex>
119
+ </Flex>
120
+ )
121
+ }
122
+
123
+ PlayerTempoPitch.displayName = 'PlayerTempoPitch'
@@ -0,0 +1,25 @@
1
+ .PlayerTempoPitch {
2
+ background-color: var(--neutral-alpha-2);
3
+ }
4
+
5
+ .neutral12 {
6
+ color: var(--neutral-12);
7
+ }
8
+
9
+ .neutralAlpha9 {
10
+ color: var(--neutral-alpha-9);
11
+ }
12
+
13
+ .neutralAlpha4 {
14
+ color: var(--neutral-alpha-4);
15
+ }
16
+
17
+ .white {
18
+ color: var(--white);
19
+ }
20
+
21
+ .customTextField {
22
+ input {
23
+ text-align: center;
24
+ }
25
+ }