@moises.ai/design-system 3.7.3 → 3.9.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/index.js +3748 -3520
- package/package.json +1 -1
- package/src/components/EmptyState/EmptyState.jsx +88 -0
- package/src/components/EmptyState/EmptyState.module.css +91 -0
- package/src/components/EmptyState/EmptyState.stories.jsx +192 -0
- package/src/components/NumberPicker/NumberPicker.jsx +71 -0
- package/src/components/NumberPicker/NumberPicker.stories.jsx +63 -0
- package/src/components/ProjectsList/ProjectItem/ProjectItem.jsx +15 -5
- package/src/components/ProjectsList/ProjectItem/ProjectItem.module.css +12 -0
- package/src/components/ProjectsList/ProjectTrack/ProjectTrack.jsx +11 -3
- package/src/components/ProjectsList/ProjectTrack/ProjectTrack.module.css +7 -0
- package/src/components/ProjectsList/ProjectTrackVoice/ProjectTrackVoice.jsx +88 -0
- package/src/components/ProjectsList/ProjectTrackVoice/ProjectTrackVoice.module.css +60 -0
- package/src/components/ProjectsList/ProjectsList.jsx +3 -1
- package/src/components/ProjectsList/ProjectsList.stories.jsx +99 -49
- package/src/components/ProjectsList/utils-stories.js +93 -2
- package/src/components/SetlistList/SetlistList.jsx +2 -0
- package/src/index.jsx +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Flex } from '@radix-ui/themes'
|
|
2
|
+
import { Button } from '../Button/Button'
|
|
3
|
+
import styles from './EmptyState.module.css'
|
|
4
|
+
import classNames from 'classnames'
|
|
5
|
+
import { Text } from '../Text/Text'
|
|
6
|
+
|
|
7
|
+
// Emphasis: Low High
|
|
8
|
+
// Size: Medium Large
|
|
9
|
+
export const EmptyState = ({
|
|
10
|
+
title = 'Title',
|
|
11
|
+
secondaryText = 'Secondary text',
|
|
12
|
+
buttonLabel = 'Button',
|
|
13
|
+
onButtonClick,
|
|
14
|
+
icon,
|
|
15
|
+
showIcon = true,
|
|
16
|
+
showSecondaryText = true,
|
|
17
|
+
showButton = true,
|
|
18
|
+
className,
|
|
19
|
+
titleMultiline = false,
|
|
20
|
+
emphasis = 'low',
|
|
21
|
+
size = 'medium',
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
}) => {
|
|
25
|
+
const hasButtonContent = children != null || buttonLabel != null
|
|
26
|
+
return (
|
|
27
|
+
<Flex
|
|
28
|
+
className={classNames(className)}
|
|
29
|
+
direction="column"
|
|
30
|
+
align="center"
|
|
31
|
+
justify="center"
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
<Flex direction="column" align="center" justify="center" className={styles.content} gap="5" py="6">
|
|
35
|
+
{showIcon && (
|
|
36
|
+
<div className={classNames(styles.iconWrapper, {
|
|
37
|
+
[styles.iconWrapperLarge]: size === 'large',
|
|
38
|
+
[styles.iconWrapperMedium]: size === 'medium',
|
|
39
|
+
[styles.iconWrapperLow]: emphasis === 'low',
|
|
40
|
+
[styles.iconWrapperHigh]: emphasis === 'high',
|
|
41
|
+
})} aria-hidden>
|
|
42
|
+
{icon}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
<Flex direction="column" align="center" justify="center" className={styles.textGroup} gap="2">
|
|
46
|
+
<Text
|
|
47
|
+
size={size === 'large' ? '7' : '4'}
|
|
48
|
+
weight="medium"
|
|
49
|
+
className={classNames({
|
|
50
|
+
[styles.titleMultiline]: titleMultiline,
|
|
51
|
+
[styles.titleLow]: emphasis === 'low',
|
|
52
|
+
[styles.titleHigh]: emphasis === 'high',
|
|
53
|
+
})}
|
|
54
|
+
>
|
|
55
|
+
{title}
|
|
56
|
+
</Text>
|
|
57
|
+
{showSecondaryText && secondaryText != null && (
|
|
58
|
+
<Text
|
|
59
|
+
size={size === 'large' ? '3' : '2'}
|
|
60
|
+
weight="regular"
|
|
61
|
+
className={classNames(styles.secondaryText, {
|
|
62
|
+
[styles.secondaryTextLow]: emphasis === 'low',
|
|
63
|
+
[styles.secondaryTextHigh]: emphasis === 'high',
|
|
64
|
+
})}
|
|
65
|
+
>{secondaryText}</Text>
|
|
66
|
+
)}
|
|
67
|
+
</Flex>
|
|
68
|
+
{showButton && hasButtonContent && (
|
|
69
|
+
<div>
|
|
70
|
+
{children ?? (
|
|
71
|
+
<Button
|
|
72
|
+
size={size === 'large' ? '3' : '2'}
|
|
73
|
+
variant={emphasis === 'low' ? 'soft' : 'solid'}
|
|
74
|
+
onClick={onButtonClick}
|
|
75
|
+
aria-label={buttonLabel}
|
|
76
|
+
>
|
|
77
|
+
{buttonLabel}
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
</Flex>
|
|
84
|
+
</Flex>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
EmptyState.displayName = 'EmptyState'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
.content {
|
|
3
|
+
|
|
4
|
+
max-width: 480px;
|
|
5
|
+
min-width: 320px;
|
|
6
|
+
width: 100%;
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.iconWrapper {
|
|
11
|
+
flex-shrink: 0;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
color: var(--neutral-alpha-9);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.iconWrapperLarge {
|
|
19
|
+
width: 32px;
|
|
20
|
+
height: 32px;
|
|
21
|
+
|
|
22
|
+
svg {
|
|
23
|
+
width: 32px;
|
|
24
|
+
height: 32px;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.iconWrapperMedium {
|
|
29
|
+
width: 24px;
|
|
30
|
+
height: 24px;
|
|
31
|
+
svg {
|
|
32
|
+
width: 24px;
|
|
33
|
+
height: 24px;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.iconWrapperLow {
|
|
38
|
+
color: var(--neutral-alpha-10);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.iconWrapperHigh {
|
|
42
|
+
color: var(--neutral-alpha-12);
|
|
43
|
+
}
|
|
44
|
+
.textGroup {
|
|
45
|
+
width: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.title {
|
|
49
|
+
font-family: inherit;
|
|
50
|
+
font-weight: 400;
|
|
51
|
+
font-size: 18px;
|
|
52
|
+
line-height: 26px;
|
|
53
|
+
letter-spacing: -0.04px;
|
|
54
|
+
color: var(--neutral-alpha-10);
|
|
55
|
+
text-align: center;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
text-overflow: ellipsis;
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.titleMultiline {
|
|
63
|
+
white-space: normal;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.titleLow {
|
|
67
|
+
color: var(--neutral-alpha-10);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.titleHigh {
|
|
71
|
+
color: var(--neutral-alpha-12);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.secondaryText {
|
|
75
|
+
font-family: inherit;
|
|
76
|
+
font-weight: 400;
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
line-height: 20px;
|
|
79
|
+
letter-spacing: 0;
|
|
80
|
+
text-align: center;
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
.secondaryTextLow {
|
|
86
|
+
color: var(--neutral-alpha-9);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.secondaryTextHigh {
|
|
90
|
+
color: var(--neutral-alpha-11);
|
|
91
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { EmptyState } from './EmptyState'
|
|
2
|
+
import { InfoCircledIcon } from '@radix-ui/react-icons'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/EmptyState',
|
|
6
|
+
component: EmptyState,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component: `
|
|
12
|
+
## EmptyState
|
|
13
|
+
|
|
14
|
+
Displays an empty state with icon, title, secondary text, and optional action. Aligned with the design system (Figma) with **emphasis** and **size** variants.
|
|
15
|
+
|
|
16
|
+
### Usage
|
|
17
|
+
|
|
18
|
+
\`\`\`jsx
|
|
19
|
+
import { EmptyState } from '@moises.ai/design-system'
|
|
20
|
+
import { InfoCircledIcon } from '@radix-ui/react-icons'
|
|
21
|
+
|
|
22
|
+
// Default: low emphasis, medium size
|
|
23
|
+
<EmptyState
|
|
24
|
+
title="No items in your setlist"
|
|
25
|
+
secondaryText="Add tracks from your library or search for songs."
|
|
26
|
+
buttonLabel="Add track"
|
|
27
|
+
onButtonClick={() => openLibrary()}
|
|
28
|
+
icon={<InfoCircledIcon />}
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
// High emphasis, large size
|
|
32
|
+
<EmptyState
|
|
33
|
+
title="No projects"
|
|
34
|
+
secondaryText="Create your first project to get started."
|
|
35
|
+
buttonLabel="Create project"
|
|
36
|
+
emphasis="high"
|
|
37
|
+
size="large"
|
|
38
|
+
icon={<PlusIcon />}
|
|
39
|
+
onButtonClick={() => createProject()}
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
// No button, message only
|
|
43
|
+
<EmptyState
|
|
44
|
+
title="No results"
|
|
45
|
+
secondaryText="Try different search terms."
|
|
46
|
+
showButton={false}
|
|
47
|
+
icon={<MagnifyingGlassIcon />}
|
|
48
|
+
/>
|
|
49
|
+
\`\`\`
|
|
50
|
+
`,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
tags: ['autodocs'],
|
|
55
|
+
argTypes: {
|
|
56
|
+
title: {
|
|
57
|
+
control: 'text',
|
|
58
|
+
description: 'Main title displayed in the empty state.',
|
|
59
|
+
},
|
|
60
|
+
secondaryText: {
|
|
61
|
+
control: 'text',
|
|
62
|
+
description: 'Supporting secondary text.',
|
|
63
|
+
},
|
|
64
|
+
buttonLabel: {
|
|
65
|
+
control: 'text',
|
|
66
|
+
description: 'Action button label.',
|
|
67
|
+
},
|
|
68
|
+
onButtonClick: {
|
|
69
|
+
action: 'clicked',
|
|
70
|
+
description: 'Called when the button is clicked.',
|
|
71
|
+
},
|
|
72
|
+
icon: {
|
|
73
|
+
description: 'Icon displayed above the title (e.g. InfoCircledIcon).',
|
|
74
|
+
},
|
|
75
|
+
showIcon: {
|
|
76
|
+
control: 'boolean',
|
|
77
|
+
description: 'Show or hide the icon.',
|
|
78
|
+
},
|
|
79
|
+
showSecondaryText: {
|
|
80
|
+
control: 'boolean',
|
|
81
|
+
description: 'Show or hide the secondary text.',
|
|
82
|
+
},
|
|
83
|
+
showButton: {
|
|
84
|
+
control: 'boolean',
|
|
85
|
+
description: 'Show or hide the button (or action area).',
|
|
86
|
+
},
|
|
87
|
+
emphasis: {
|
|
88
|
+
control: 'select',
|
|
89
|
+
options: ['low', 'high'],
|
|
90
|
+
description: "Visual emphasis: 'low' (subtle, soft button) or 'high' (prominent, solid button).",
|
|
91
|
+
},
|
|
92
|
+
size: {
|
|
93
|
+
control: 'select',
|
|
94
|
+
options: ['medium', 'large'],
|
|
95
|
+
description: "Size: 'medium' (24px icon, smaller text) or 'large' (32px icon, larger text).",
|
|
96
|
+
},
|
|
97
|
+
titleMultiline: {
|
|
98
|
+
control: 'boolean',
|
|
99
|
+
description: 'Allow the title to wrap to multiple lines.',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const Default = {
|
|
105
|
+
args: {
|
|
106
|
+
title: 'Title',
|
|
107
|
+
secondaryText: 'Secondary text',
|
|
108
|
+
buttonLabel: 'Button',
|
|
109
|
+
showIcon: true,
|
|
110
|
+
showSecondaryText: true,
|
|
111
|
+
showButton: true,
|
|
112
|
+
emphasis: 'low',
|
|
113
|
+
size: 'medium',
|
|
114
|
+
icon: <InfoCircledIcon />,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const EmphasisHigh = {
|
|
119
|
+
args: {
|
|
120
|
+
...Default.args,
|
|
121
|
+
title: 'No items found',
|
|
122
|
+
secondaryText: 'Try adjusting filters or searching for a different term.',
|
|
123
|
+
buttonLabel: 'Clear filters',
|
|
124
|
+
emphasis: 'high',
|
|
125
|
+
icon: <InfoCircledIcon />,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const SizeLarge = {
|
|
130
|
+
args: {
|
|
131
|
+
...Default.args,
|
|
132
|
+
title: 'Your setlist is empty',
|
|
133
|
+
secondaryText: 'Add tracks from your library or search for songs to get started.',
|
|
134
|
+
buttonLabel: 'Add track',
|
|
135
|
+
size: 'large',
|
|
136
|
+
icon: <InfoCircledIcon />,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const LargeAndHighEmphasis = {
|
|
141
|
+
args: {
|
|
142
|
+
...Default.args,
|
|
143
|
+
title: 'No projects yet',
|
|
144
|
+
secondaryText: 'Create your first project to get started.',
|
|
145
|
+
buttonLabel: 'Create project',
|
|
146
|
+
size: 'large',
|
|
147
|
+
emphasis: 'high',
|
|
148
|
+
icon: <InfoCircledIcon />,
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const WithCustomIcon = {
|
|
153
|
+
args: {
|
|
154
|
+
...Default.args,
|
|
155
|
+
icon: <InfoCircledIcon />,
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const NoIcon = {
|
|
160
|
+
args: {
|
|
161
|
+
...Default.args,
|
|
162
|
+
showIcon: false,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const NoSecondaryText = {
|
|
167
|
+
args: {
|
|
168
|
+
...Default.args,
|
|
169
|
+
showSecondaryText: false,
|
|
170
|
+
icon: <InfoCircledIcon />,
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const NoButton = {
|
|
175
|
+
args: {
|
|
176
|
+
...Default.args,
|
|
177
|
+
showButton: false,
|
|
178
|
+
icon: <InfoCircledIcon />,
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const LongTitle = {
|
|
183
|
+
args: {
|
|
184
|
+
...Default.args,
|
|
185
|
+
title: 'No items in your setlist yet',
|
|
186
|
+
secondaryText:
|
|
187
|
+
'Add tracks from your library or search for songs to get started.',
|
|
188
|
+
buttonLabel: 'Add track',
|
|
189
|
+
titleMultiline: true,
|
|
190
|
+
icon: <InfoCircledIcon />,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import { Flex } from '@radix-ui/themes'
|
|
3
|
+
import { MinusIcon, PlusIcon } from '../../icons'
|
|
4
|
+
import { IconButton } from '../IconButton/IconButton'
|
|
5
|
+
import { Text } from '../Text/Text'
|
|
6
|
+
|
|
7
|
+
export const NumberPicker = ({
|
|
8
|
+
label,
|
|
9
|
+
value = 0,
|
|
10
|
+
min = 0,
|
|
11
|
+
max = 10,
|
|
12
|
+
onChange,
|
|
13
|
+
}) => {
|
|
14
|
+
const onIncrement = useCallback((e) => {
|
|
15
|
+
e.stopPropagation()
|
|
16
|
+
if (max != null && value >= max) return
|
|
17
|
+
onChange(value + 1)
|
|
18
|
+
}, [max, onChange, value])
|
|
19
|
+
|
|
20
|
+
const onDecrement = useCallback((e) => {
|
|
21
|
+
e.stopPropagation()
|
|
22
|
+
if (min != null && value <= min) return
|
|
23
|
+
onChange(value - 1)
|
|
24
|
+
}, [min, onChange, value])
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Flex direction="row" gap="1" align="center" justify="center">
|
|
28
|
+
{label && (
|
|
29
|
+
<Text size="2" weight="medium">
|
|
30
|
+
{label}
|
|
31
|
+
</Text>
|
|
32
|
+
)}
|
|
33
|
+
<Flex direction="row" gap="1" align="center" justify="center">
|
|
34
|
+
<IconButton
|
|
35
|
+
type="button"
|
|
36
|
+
aria-label="increment"
|
|
37
|
+
onClick={onDecrement}
|
|
38
|
+
style={{
|
|
39
|
+
backgroundColor: 'var(--neutral-alpha-3 )',
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<MinusIcon width={16} height={16} />
|
|
43
|
+
</IconButton>
|
|
44
|
+
|
|
45
|
+
<Flex
|
|
46
|
+
align="center"
|
|
47
|
+
justify="center"
|
|
48
|
+
style={{
|
|
49
|
+
height: '24px',
|
|
50
|
+
minWidth: '42px',
|
|
51
|
+
borderRadius: 'var(--radius-2)',
|
|
52
|
+
backgroundColor: 'var(--neutral-alpha-3 )',
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<Text size="2">{value > 0 ? `+${value}` : value}</Text>
|
|
56
|
+
</Flex>
|
|
57
|
+
|
|
58
|
+
<IconButton
|
|
59
|
+
type="button"
|
|
60
|
+
aria-label="decrement"
|
|
61
|
+
onClick={onIncrement}
|
|
62
|
+
style={{
|
|
63
|
+
backgroundColor: 'var(--neutral-alpha-3 )',
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<PlusIcon width={16} height={16} />
|
|
67
|
+
</IconButton>
|
|
68
|
+
</Flex>
|
|
69
|
+
</Flex>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { NumberPicker } from './NumberPicker'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/NumberPicker',
|
|
6
|
+
component: NumberPicker,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
label: { control: 'text' },
|
|
10
|
+
value: { control: 'number' },
|
|
11
|
+
min: { control: 'number' },
|
|
12
|
+
max: { control: 'number' },
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Default = {
|
|
17
|
+
args: {
|
|
18
|
+
label: 'Number',
|
|
19
|
+
value: 0,
|
|
20
|
+
min: -10,
|
|
21
|
+
max: 10,
|
|
22
|
+
},
|
|
23
|
+
render: (args) => {
|
|
24
|
+
const [value, setValue] = useState(args.value)
|
|
25
|
+
const handleChange = (newValue) => {
|
|
26
|
+
console.log('handleChange. new value:', newValue)
|
|
27
|
+
setValue(parseInt(newValue))
|
|
28
|
+
}
|
|
29
|
+
return (
|
|
30
|
+
<NumberPicker
|
|
31
|
+
label={args.label}
|
|
32
|
+
value={value}
|
|
33
|
+
min={args.min}
|
|
34
|
+
max={args.max}
|
|
35
|
+
onChange={handleChange}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const WithoutLabel = {
|
|
42
|
+
args: {
|
|
43
|
+
value: 0,
|
|
44
|
+
min: -10,
|
|
45
|
+
max: 10,
|
|
46
|
+
},
|
|
47
|
+
render: (args) => {
|
|
48
|
+
const [value, setValue] = useState(args.value)
|
|
49
|
+
const handleChange = (newValue) => {
|
|
50
|
+
console.log('handleChange. new value:', newValue)
|
|
51
|
+
setValue(parseInt(newValue))
|
|
52
|
+
}
|
|
53
|
+
return (
|
|
54
|
+
<NumberPicker
|
|
55
|
+
label={args.label}
|
|
56
|
+
value={value}
|
|
57
|
+
min={args.min}
|
|
58
|
+
max={args.max}
|
|
59
|
+
onChange={handleChange}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
}
|
|
@@ -69,9 +69,17 @@ const ProjectItem = ({
|
|
|
69
69
|
<ProductsBrandPattern type={type} cover={cover} size="48px" />
|
|
70
70
|
</Flex>
|
|
71
71
|
|
|
72
|
-
<Flex
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
<Flex
|
|
73
|
+
direction="column"
|
|
74
|
+
gap="1"
|
|
75
|
+
align="start"
|
|
76
|
+
justify="center"
|
|
77
|
+
style={{ flex: 1, minWidth: 0 }}
|
|
78
|
+
>
|
|
79
|
+
<Text size="2" asChild>
|
|
80
|
+
<span className={styles.projectTitle}>
|
|
81
|
+
{title}
|
|
82
|
+
</span>
|
|
75
83
|
</Text>
|
|
76
84
|
|
|
77
85
|
{failed ? (
|
|
@@ -80,8 +88,10 @@ const ProjectItem = ({
|
|
|
80
88
|
<Text size="1">Failed</Text>
|
|
81
89
|
</Flex>
|
|
82
90
|
) : artist && (
|
|
83
|
-
<Text size="1"
|
|
84
|
-
{
|
|
91
|
+
<Text size="1" asChild>
|
|
92
|
+
<span className={styles.projectArtist}>
|
|
93
|
+
{artist}
|
|
94
|
+
</span>
|
|
85
95
|
</Text>
|
|
86
96
|
)}
|
|
87
97
|
</Flex>
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
.projectTitle {
|
|
2
2
|
color: var(--neutral-12);
|
|
3
3
|
padding-right: var(--space-2);
|
|
4
|
+
display: block;
|
|
5
|
+
width: 100%;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
text-overflow: ellipsis;
|
|
9
|
+
white-space: nowrap;
|
|
4
10
|
}
|
|
5
11
|
|
|
6
12
|
.projectArtist {
|
|
7
13
|
color: var(--neutral-alpha-9);
|
|
14
|
+
display: block;
|
|
15
|
+
width: 100%;
|
|
16
|
+
min-width: 0;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
text-overflow: ellipsis;
|
|
19
|
+
white-space: nowrap;
|
|
8
20
|
}
|
|
9
21
|
|
|
10
22
|
.projectFailed {
|
|
@@ -31,9 +31,17 @@ const ProjectTrack = ({
|
|
|
31
31
|
<ArrowLeftIcon width={16} height={16} className={styles.iconArrowLeft} />
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
|
-
<Flex
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
<Flex
|
|
35
|
+
direction="column"
|
|
36
|
+
gap="1"
|
|
37
|
+
align="start"
|
|
38
|
+
justify="center"
|
|
39
|
+
style={{ flex: 1, minWidth: 0 }}
|
|
40
|
+
>
|
|
41
|
+
<Text size="2" asChild>
|
|
42
|
+
<span className={styles.trackTitle}>
|
|
43
|
+
{title}
|
|
44
|
+
</span>
|
|
37
45
|
</Text>
|
|
38
46
|
</Flex>
|
|
39
47
|
</Flex>
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
.trackTitle {
|
|
24
24
|
color: var(--neutral-alpha-12);
|
|
25
|
+
display: block;
|
|
26
|
+
width: 100%;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
text-overflow: ellipsis;
|
|
30
|
+
white-space: nowrap;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
.icon {
|
|
@@ -32,4 +38,5 @@
|
|
|
32
38
|
justify-content: center;
|
|
33
39
|
background: var(--neutral-alpha-2);
|
|
34
40
|
border-radius: var(--radius-3);
|
|
41
|
+
color: var(--neutral-alpha-10);
|
|
35
42
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Flex, Text } from '@radix-ui/themes'
|
|
2
|
+
import { useCallback, useContext, useMemo, useState } from 'react'
|
|
3
|
+
import { ArrowLeftIcon } from '../../../icons'
|
|
4
|
+
import { ProjectsListContext } from '../context'
|
|
5
|
+
import { NumberPicker } from '../../NumberPicker/NumberPicker'
|
|
6
|
+
import { UserIcon } from '../../../icons'
|
|
7
|
+
import styles from './ProjectTrackVoice.module.css'
|
|
8
|
+
|
|
9
|
+
const ProjectTrackVoice = ({
|
|
10
|
+
...props
|
|
11
|
+
}) => {
|
|
12
|
+
const { onClickTrack } = useContext(ProjectsListContext)
|
|
13
|
+
const { id, title, cover, transfers, audioUrl, onChangeTransfer } = props
|
|
14
|
+
|
|
15
|
+
const hasTransfers = useMemo(() => transfers && transfers.length > 0, [transfers])
|
|
16
|
+
|
|
17
|
+
const [pitchShiftSelected, setpitchShiftSelected] = useState(hasTransfers ? parseInt(transfers?.[0]?.pitchShift ?? 0) : 0)
|
|
18
|
+
const min = useMemo(() => hasTransfers ? Math.min(...transfers?.map((transfer) => parseInt(transfer.pitchShift))) : 0, [transfers])
|
|
19
|
+
const max = useMemo(() => hasTransfers ? Math.max(...transfers?.map((transfer) => parseInt(transfer.pitchShift))) : 0, [transfers])
|
|
20
|
+
|
|
21
|
+
const getAudioUrl = useCallback((pitchShift) => {
|
|
22
|
+
return hasTransfers ? transfers?.find((transfer) => parseInt(transfer.pitchShift) === pitchShift)?.audioUrl : audioUrl
|
|
23
|
+
}, [hasTransfers, transfers, audioUrl])
|
|
24
|
+
|
|
25
|
+
const handleChangeTransfer = useCallback((value) => {
|
|
26
|
+
setpitchShiftSelected(value)
|
|
27
|
+
onChangeTransfer?.({
|
|
28
|
+
id,
|
|
29
|
+
title,
|
|
30
|
+
audioUrl: getAudioUrl(value),
|
|
31
|
+
})
|
|
32
|
+
}, [onChangeTransfer, id, title, transfers])
|
|
33
|
+
|
|
34
|
+
const handleClickTrack = useCallback((e) => {
|
|
35
|
+
e.stopPropagation()
|
|
36
|
+
onClickTrack?.({
|
|
37
|
+
id,
|
|
38
|
+
title,
|
|
39
|
+
audioUrl: getAudioUrl(pitchShiftSelected),
|
|
40
|
+
})
|
|
41
|
+
}, [onClickTrack, id, title, getAudioUrl, pitchShiftSelected])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Flex direction="column" gap="1">
|
|
45
|
+
<Flex
|
|
46
|
+
direction="row"
|
|
47
|
+
gap="3"
|
|
48
|
+
className={styles.itemTrack}
|
|
49
|
+
onClick={handleClickTrack}
|
|
50
|
+
>
|
|
51
|
+
<div className={styles.cover}>
|
|
52
|
+
<Flex className={styles.coverImage}>
|
|
53
|
+
{cover
|
|
54
|
+
? <img src={cover} alt={title} width={48} height={48} />
|
|
55
|
+
: <UserIcon width={16} height={16} />
|
|
56
|
+
}
|
|
57
|
+
</Flex>
|
|
58
|
+
<ArrowLeftIcon width={16} height={16} className={styles.iconArrowLeft} />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<Flex
|
|
62
|
+
direction="column"
|
|
63
|
+
gap="1"
|
|
64
|
+
align="start"
|
|
65
|
+
justify="center"
|
|
66
|
+
style={{ flex: 1, minWidth: 0 }}
|
|
67
|
+
>
|
|
68
|
+
<Text size="2" asChild>
|
|
69
|
+
<span className={styles.trackTitle}>
|
|
70
|
+
{title}
|
|
71
|
+
</span>
|
|
72
|
+
</Text>
|
|
73
|
+
</Flex>
|
|
74
|
+
|
|
75
|
+
{hasTransfers && (
|
|
76
|
+
<NumberPicker
|
|
77
|
+
min={min}
|
|
78
|
+
max={max}
|
|
79
|
+
value={pitchShiftSelected}
|
|
80
|
+
onChange={handleChangeTransfer}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
</Flex>
|
|
84
|
+
</Flex>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default ProjectTrackVoice
|