@moises.ai/design-system 3.8.0 → 3.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moises.ai/design-system",
3
- "version": "3.8.0",
3
+ "version": "3.9.1",
4
4
  "description": "Design System package based on @radix-ui/themes with custom defaults",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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
+ }
@@ -115,7 +115,7 @@ const ProjectItem = ({
115
115
 
116
116
  {allTracks.length > 0 && (
117
117
  <Flex direction="column" gap="1">
118
- {typeof children === 'function' ? children(tracks) : children}
118
+ {typeof children === 'function' ? children({ projectId: id, tracks }) : children}
119
119
 
120
120
  <Flex direction="row" gap="3" p="2" align="center" justify="center">
121
121
  <Button variant="ghost" color="gray" style={{ flex: '1' }}
@@ -10,7 +10,7 @@ const ProjectTrackVoice = ({
10
10
  ...props
11
11
  }) => {
12
12
  const { onClickTrack } = useContext(ProjectsListContext)
13
- const { id, title, cover, transfers, audioUrl, onChangeTransfer } = props
13
+ const { projectId, id, title, cover, transfers, audioUrl, onChangeTransfer } = props
14
14
 
15
15
  const hasTransfers = useMemo(() => transfers && transfers.length > 0, [transfers])
16
16
 
@@ -24,12 +24,12 @@ const ProjectTrackVoice = ({
24
24
 
25
25
  const handleChangeTransfer = useCallback((value) => {
26
26
  setpitchShiftSelected(value)
27
- onChangeTransfer?.({
27
+ onChangeTransfer?.(projectId, {
28
28
  id,
29
29
  title,
30
30
  audioUrl: getAudioUrl(value),
31
31
  })
32
- }, [onChangeTransfer, id, title, transfers])
32
+ }, [onChangeTransfer, projectId, id, title, transfers])
33
33
 
34
34
  const handleClickTrack = useCallback((e) => {
35
35
  e.stopPropagation()
@@ -72,7 +72,7 @@ const ProjectTrackVoice = ({
72
72
  </Text>
73
73
  </Flex>
74
74
 
75
- {hasTransfers && (
75
+ {hasTransfers && transfers.length > 1 && (
76
76
  <NumberPicker
77
77
  min={min}
78
78
  max={max}
@@ -206,8 +206,8 @@ export const WithTrackVoice = {
206
206
  onInsertAllTracks: (tracks) => {
207
207
  console.log('insert-all-tracks', tracks)
208
208
  },
209
- onChangeTransfer: (track) => {
210
- console.log('change-transfer', track)
209
+ onChangeTransfer: (projectId, track) => {
210
+ console.log('change-transfer', projectId, track)
211
211
  },
212
212
  },
213
213
  render: (args) => {
@@ -219,9 +219,10 @@ export const WithTrackVoice = {
219
219
  onInsertAllTracks={args.onInsertAllTracks}
220
220
  onProjectClick={args.onProjectClick}
221
221
  >
222
- {(tracks) =>
222
+ {({ projectId, tracks }) =>
223
223
  tracks.map((track) => (
224
224
  <ProjectsList.TrackVoice
225
+ projectId={projectId}
225
226
  key={track.id}
226
227
  {...track}
227
228
  onChangeTransfer={args.onChangeTransfer}
@@ -211,7 +211,7 @@ export const projects = [
211
211
 
212
212
  export const projectsVoiceStudio = [
213
213
  {
214
- "id": "3842c898-9e0c-41e8-8927-7d9f67c50bc6",
214
+ "id": "3842c898-9e0c-41e8-8927-7d9f1111111167c50bc6",
215
215
  "title": "vocals - Billie Bossa Nova",
216
216
  "artist": "Billie Bossa Nova",
217
217
  "cover": null,
@@ -266,11 +266,6 @@ export const projectsVoiceStudio = [
266
266
  "cover": "https://picsum.photos/103/103",
267
267
  "type": "voice",
268
268
  "transfers": [
269
- {
270
- "id": "b9470aco6y4a9dlef0c035-0",
271
- "pitchShift": "-10",
272
- "audioUrl": "https://d3-stage.moises.ai/v3/download/moises-stage--tasks-us-east1/operations/VOICETRANSFER_A/0b926e1f-52ce-4581-9f76-09545eed1c61/vocals.m4"
273
- },
274
269
  {
275
270
  "id": "b9470aco6y4a9dlef0c035-1",
276
271
  "pitchShift": "-11",
@@ -213,6 +213,8 @@ export const SetlistItem = ({
213
213
  <DropdownMenu
214
214
  open={dropdownMenuOpen}
215
215
  onOpenChange={onDropdownMenuOpenChange}
216
+ side="bottom"
217
+ align="end"
216
218
  trigger={
217
219
  <Flex
218
220
  className={classNames(styles.dropdownTriggerWrapper, {
package/src/index.jsx CHANGED
@@ -140,3 +140,5 @@ export { TrackHeader } from './components/TrackHeader/TrackHeader'
140
140
  export { useForm } from './components/useForm/useForm'
141
141
  export { VoiceConversionForm } from './components/VoiceConversionForm/VoiceConversionForm'
142
142
  export { Waveform } from './components/VoiceConversionForm/Waveform/Waveform'
143
+
144
+ export { EmptyState } from './components/EmptyState/EmptyState'