@moises.ai/design-system 3.6.13 → 3.6.18

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.6.13",
3
+ "version": "3.6.18",
4
4
  "description": "Design System package based on @radix-ui/themes with custom defaults",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,11 +1,15 @@
1
- import { useCallback } from 'react'
1
+ import { useCallback, useEffect, useRef } from 'react'
2
2
  import { Flex, Box, Tooltip, Separator } from '@radix-ui/themes'
3
3
  import { TextArea } from '../../TextArea/TextArea'
4
4
  import { Button } from '../../Button/Button'
5
5
  import { IconButton } from '../../IconButton/IconButton'
6
6
  import { Text } from '../../Text/Text'
7
- import { FileIcon, TrashIcon, UploadIcon } from '../../../icons'
7
+ import { FileIcon, TrashIcon, AttachmentIcon, MagicWandIcon, RocketIcon } from '../../../icons'
8
8
  import { formatFileInfo, getInstrumentDescription } from '../utils'
9
+ import styles from './CreateNew.module.css'
10
+
11
+ const DEBOUNCE_MS = 500
12
+ const SKELETON_WIDTHS = [60, 48, 68, 52, 56]
9
13
 
10
14
  export const CreateNew = ({
11
15
  formState,
@@ -13,7 +17,16 @@ export const CreateNew = ({
13
17
  i18n = { _: (text) => text },
14
18
  onAddConditioningAudio,
15
19
  onTextConditioningFocus,
20
+ onInspireMe,
21
+ inspireMeLoading,
22
+ onBoost,
23
+ boostLoading,
24
+ suggestedTags,
25
+ onFetchSuggestedTags,
26
+ suggestedTagsLoading,
16
27
  }) => {
28
+ const debounceRef = useRef(null)
29
+
17
30
  const handleTextConditioningChange = useCallback(
18
31
  (e) => {
19
32
  const newValue = e.target.value
@@ -25,6 +38,30 @@ export const CreateNew = ({
25
38
  [onFormStateChange],
26
39
  )
27
40
 
41
+ useEffect(() => {
42
+ if (!onFetchSuggestedTags) return
43
+ if (!formState.conditioningText) return
44
+
45
+ if (debounceRef.current) clearTimeout(debounceRef.current)
46
+ debounceRef.current = setTimeout(() => {
47
+ onFetchSuggestedTags(formState.conditioningText)
48
+ }, DEBOUNCE_MS)
49
+
50
+ return () => {
51
+ if (debounceRef.current) clearTimeout(debounceRef.current)
52
+ }
53
+ }, [formState.conditioningText, onFetchSuggestedTags])
54
+
55
+ const handleTagClick = useCallback(
56
+ (tag) => {
57
+ onFormStateChange((prevState) => ({
58
+ ...prevState,
59
+ conditioningText: (prevState.conditioningText || '').trimEnd() + ' ' + tag + ' ',
60
+ }))
61
+ },
62
+ [onFormStateChange],
63
+ )
64
+
28
65
  const handleClearConditioningAudio = useCallback(() => {
29
66
  onFormStateChange((prevState) => ({
30
67
  ...prevState,
@@ -73,11 +110,11 @@ export const CreateNew = ({
73
110
  >
74
111
  <Button
75
112
  variant="ghost"
76
- color="cyan"
77
113
  onClick={onAddConditioningAudio}
114
+ style={{ color: '#EDEEF0' }}
78
115
  >
79
- <UploadIcon width={16} height={16} />
80
- <Text size="1">{i18n._('Select Reference Audio')}</Text>
116
+ <AttachmentIcon width={16} height={16} style={{ color: '#B0B4BA' }} />
117
+ <Text size="1" style={{ color: '#EDEEF0' }}>{i18n._('Select Reference Audio')}</Text>
81
118
  </Button>
82
119
  </Tooltip>
83
120
  )}
@@ -92,6 +129,7 @@ export const CreateNew = ({
92
129
  value={formState.conditioningText}
93
130
  onFocus={onTextConditioningFocus}
94
131
  onChange={handleTextConditioningChange}
132
+ disabled={!!inspireMeLoading || !!boostLoading}
95
133
  placeholder={i18n._(
96
134
  `Describe your desired sound characteristics (ex: ${getInstrumentDescription(
97
135
  formState.instrument,
@@ -101,6 +139,73 @@ export const CreateNew = ({
101
139
  rows={6}
102
140
  />
103
141
  </Box>
142
+
143
+ {!formState.conditioningText && onInspireMe && (
144
+ <Box px="3px" pb="2">
145
+ <Button
146
+ variant="soft"
147
+ color="cyan"
148
+ size="1"
149
+ onClick={onInspireMe}
150
+ loading={inspireMeLoading}
151
+ style={{ borderRadius: '9999px' }}
152
+ >
153
+ <MagicWandIcon width={14} height={14} />
154
+ {i18n._('Inspire me')}
155
+ </Button>
156
+ </Box>
157
+ )}
158
+
159
+ {formState.conditioningText && (onBoost || suggestedTagsLoading || suggestedTags?.length > 0) && (
160
+ <div className={styles.promptActions}>
161
+ {onBoost && (
162
+ <IconButton
163
+ className={styles.boostButton}
164
+ variant="soft"
165
+ color="orange"
166
+ size="2"
167
+ onClick={onBoost}
168
+ loading={boostLoading}
169
+ style={{ borderRadius: '9999px', width: 32, height: 32 }}
170
+ >
171
+ <RocketIcon width={14} height={14} />
172
+ </IconButton>
173
+ )}
174
+ {suggestedTagsLoading && (
175
+ <div className={styles.tagsContainer}>
176
+ {SKELETON_WIDTHS.map((w, i) => (
177
+ <div
178
+ key={i}
179
+ className={styles.skeletonChip}
180
+ style={{ width: w }}
181
+ />
182
+ ))}
183
+ </div>
184
+ )}
185
+ {!suggestedTagsLoading && suggestedTags?.length > 0 && (
186
+ <div className={styles.tagsContainer}>
187
+ {suggestedTags.map((tag) => (
188
+ <Button
189
+ key={tag}
190
+ className={styles.tagChip}
191
+ variant="ghost"
192
+ size="2"
193
+ onClick={() => handleTagClick(tag)}
194
+ style={{
195
+ borderRadius: '9999px',
196
+ color: 'rgba(241, 247, 254, 0.71)',
197
+ backgroundColor: 'rgba(221, 234, 248, 0.08)',
198
+ paddingLeft: 14,
199
+ paddingRight: 14,
200
+ }}
201
+ >
202
+ {tag}
203
+ </Button>
204
+ ))}
205
+ </div>
206
+ )}
207
+ </div>
208
+ )}
104
209
  </Flex>
105
210
  </Flex>
106
211
  )
@@ -0,0 +1,51 @@
1
+ .promptActions {
2
+ display: flex;
3
+ flex-direction: row;
4
+ align-items: center;
5
+ gap: 8px;
6
+ padding: 0 3px 8px;
7
+ }
8
+
9
+ .tagsContainer {
10
+ display: flex;
11
+ flex-direction: row;
12
+ align-items: center;
13
+ gap: 8px;
14
+ overflow-x: auto;
15
+ flex-wrap: nowrap;
16
+ flex: 1;
17
+ min-width: 0;
18
+ scrollbar-width: none;
19
+ -ms-overflow-style: none;
20
+ }
21
+
22
+ .tagsContainer::-webkit-scrollbar {
23
+ display: none;
24
+ }
25
+
26
+ .tagChip {
27
+ border-radius: 9999px;
28
+ white-space: nowrap;
29
+ flex-shrink: 0;
30
+ }
31
+
32
+ .boostButton {
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ .skeletonChip {
37
+ border-radius: 9999px;
38
+ flex-shrink: 0;
39
+ height: 32px;
40
+ background: rgba(221, 234, 248, 0.06);
41
+ animation: shimmer 1.4s ease-in-out infinite;
42
+ }
43
+
44
+ @keyframes shimmer {
45
+ 0%, 100% {
46
+ opacity: 0.4;
47
+ }
48
+ 50% {
49
+ opacity: 1;
50
+ }
51
+ }
@@ -39,6 +39,13 @@ export const StemGenerationForm = ({
39
39
  unmutedTracksCount,
40
40
  onMountConcurrency,
41
41
  onClear,
42
+ onInspireMe,
43
+ inspireMeLoading,
44
+ onBoost,
45
+ boostLoading,
46
+ suggestedTags,
47
+ onFetchSuggestedTags,
48
+ suggestedTagsLoading,
42
49
  }) => {
43
50
  const [isAnimating, setIsAnimating] = useState(false)
44
51
  const [presetSelected, setPresetSelected] = useState('2')
@@ -229,6 +236,13 @@ export const StemGenerationForm = ({
229
236
  i18n={i18n}
230
237
  onAddConditioningAudio={onAddConditioningAudio}
231
238
  onTextConditioningFocus={showConditioningTextPaywall}
239
+ onInspireMe={onInspireMe}
240
+ inspireMeLoading={inspireMeLoading}
241
+ onBoost={onBoost}
242
+ boostLoading={boostLoading}
243
+ suggestedTags={suggestedTags}
244
+ onFetchSuggestedTags={onFetchSuggestedTags}
245
+ suggestedTagsLoading={suggestedTagsLoading}
232
246
  />
233
247
  )}
234
248
 
@@ -231,6 +231,37 @@ generateFromTrack = {
231
231
  description: 'Callback to clear generation state',
232
232
  control: false,
233
233
  },
234
+ onInspireMe: {
235
+ description:
236
+ 'Callback when user clicks "Inspire me". Consumer fetches prompt and sets conditioningText.',
237
+ control: false,
238
+ },
239
+ inspireMeLoading: {
240
+ description: 'Loading state for the inspire action',
241
+ control: 'boolean',
242
+ },
243
+ onBoost: {
244
+ description:
245
+ 'Callback when user clicks the boost (rocket) button. Consumer fetches boosted text and replaces conditioningText.',
246
+ control: false,
247
+ },
248
+ boostLoading: {
249
+ description: 'Loading state for the boost action',
250
+ control: 'boolean',
251
+ },
252
+ suggestedTags: {
253
+ description: 'Array of tag strings returned from upstream, shown as clickable chips next to the boost button',
254
+ control: false,
255
+ },
256
+ onFetchSuggestedTags: {
257
+ description:
258
+ 'Callback fired (debounced) when the user types in the textarea. Receives the current text. Consumer fetches tags and passes them back via suggestedTags.',
259
+ control: false,
260
+ },
261
+ suggestedTagsLoading: {
262
+ description: 'Loading state for suggested tags. Shows skeleton chips when true.',
263
+ control: 'boolean',
264
+ },
234
265
  },
235
266
  }
236
267
 
@@ -701,3 +732,128 @@ export const WithCreativeControls = {
701
732
  unmutedTracksCount: 3,
702
733
  },
703
734
  }
735
+
736
+ /**
737
+ * ## Inspire Me (Empty Textarea)
738
+ *
739
+ * Shows the "Inspire me" button below the textarea when no text has been entered.
740
+ * Clicking the button triggers the consumer to fetch and populate a prompt.
741
+ */
742
+ export const InspireMeEmpty = {
743
+ args: {
744
+ formState: {
745
+ ...FORM_DEFAULTS,
746
+ conditioningText: '',
747
+ },
748
+ presets: [],
749
+ i18n: mockI18n,
750
+ loading: false,
751
+ limitReached: false,
752
+ showUpgradeConcurrency: false,
753
+ error: null,
754
+ unmutedTracksCount: 3,
755
+ onInspireMe: () => console.log('Inspire me clicked'),
756
+ inspireMeLoading: false,
757
+ },
758
+ }
759
+
760
+ /**
761
+ * ## Inspire Me (Loading)
762
+ *
763
+ * Shows the "Inspire me" button in its loading state while a prompt is being fetched.
764
+ */
765
+ export const InspireMeLoading = {
766
+ args: {
767
+ formState: {
768
+ ...FORM_DEFAULTS,
769
+ conditioningText: '',
770
+ },
771
+ presets: [],
772
+ i18n: mockI18n,
773
+ loading: false,
774
+ limitReached: false,
775
+ showUpgradeConcurrency: false,
776
+ error: null,
777
+ unmutedTracksCount: 3,
778
+ onInspireMe: () => console.log('Inspire me clicked'),
779
+ inspireMeLoading: true,
780
+ },
781
+ }
782
+
783
+ /**
784
+ * ## Boost with Tags
785
+ *
786
+ * Shows the boost button and suggested tag chips when the textarea has text.
787
+ * Tags scroll horizontally and clicking a tag appends it to the prompt locally.
788
+ */
789
+ export const BoostWithTags = {
790
+ args: {
791
+ formState: {
792
+ ...FORM_DEFAULTS,
793
+ conditioningText: 'Heavy rock drums with double bass pedal',
794
+ },
795
+ presets: [],
796
+ i18n: mockI18n,
797
+ loading: false,
798
+ limitReached: false,
799
+ showUpgradeConcurrency: false,
800
+ error: null,
801
+ unmutedTracksCount: 3,
802
+ onBoost: () => console.log('Boost clicked'),
803
+ boostLoading: false,
804
+ suggestedTags: ['lo-fi', 'dub', 'soul-fi', 'swing', 'lo-rez'],
805
+ onFetchSuggestedTags: (text) => console.log('Fetch tags for:', text),
806
+ suggestedTagsLoading: false,
807
+ },
808
+ }
809
+
810
+ /**
811
+ * ## Boost Loading
812
+ *
813
+ * Shows the boost button in its loading state while boost text is being fetched.
814
+ */
815
+ export const BoostLoading = {
816
+ args: {
817
+ formState: {
818
+ ...FORM_DEFAULTS,
819
+ conditioningText: 'Smooth jazz guitar with warm tone',
820
+ },
821
+ presets: [],
822
+ i18n: mockI18n,
823
+ loading: false,
824
+ limitReached: false,
825
+ showUpgradeConcurrency: false,
826
+ error: null,
827
+ unmutedTracksCount: 3,
828
+ onBoost: () => console.log('Boost clicked'),
829
+ boostLoading: true,
830
+ suggestedTags: ['lo-fi', 'dub', 'soul-fi', 'swing', 'lo-rez'],
831
+ onFetchSuggestedTags: (text) => console.log('Fetch tags for:', text),
832
+ suggestedTagsLoading: false,
833
+ },
834
+ }
835
+
836
+ /**
837
+ * ## Suggested Tags Loading (Skeleton)
838
+ *
839
+ * Shows skeleton placeholders while suggested tags are being fetched from upstream.
840
+ */
841
+ export const SuggestedTagsLoading = {
842
+ args: {
843
+ formState: {
844
+ ...FORM_DEFAULTS,
845
+ conditioningText: 'Funky bass groove with slap technique',
846
+ },
847
+ presets: [],
848
+ i18n: mockI18n,
849
+ loading: false,
850
+ limitReached: false,
851
+ showUpgradeConcurrency: false,
852
+ error: null,
853
+ unmutedTracksCount: 3,
854
+ onBoost: () => console.log('Boost clicked'),
855
+ boostLoading: false,
856
+ onFetchSuggestedTags: (text) => console.log('Fetch tags for:', text),
857
+ suggestedTagsLoading: true,
858
+ },
859
+ }
@@ -26,7 +26,12 @@
26
26
  color: var(--neutral-alpha-9) !important;
27
27
  -webkit-text-fill-color: var(--neutral-alpha-9) !important;
28
28
  background-color: var(--neutral-alpha-3) !important;
29
+ }
29
30
 
31
+ .disabled textarea {
32
+ color: var(--neutral-alpha-9) !important;
33
+ -webkit-text-fill-color: var(--neutral-alpha-9) !important;
34
+ opacity: 1 !important;
30
35
  }
31
36
 
32
37
  .focusRing:where(:focus-within) {
@@ -0,0 +1,70 @@
1
+ export const MagicWandIcon = ({ width, height, className, ...props }) => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ width={width}
5
+ height={height}
6
+ viewBox="0 0 16 16"
7
+ fill="none"
8
+ className={className}
9
+ {...props}
10
+ >
11
+ <path
12
+ d="M1.33301 14.667L10.333 5.66699"
13
+ stroke="currentColor"
14
+ strokeWidth="1.13"
15
+ strokeLinecap="round"
16
+ strokeLinejoin="round"
17
+ />
18
+ <path
19
+ d="M8.33301 7.66699L10.333 5.66699"
20
+ stroke="currentColor"
21
+ strokeWidth="1.13"
22
+ strokeLinecap="round"
23
+ strokeLinejoin="round"
24
+ />
25
+ <path
26
+ d="M11.667 1.33301V4.33301"
27
+ stroke="currentColor"
28
+ strokeWidth="1.13"
29
+ strokeLinecap="round"
30
+ strokeLinejoin="round"
31
+ />
32
+ <path
33
+ d="M13.167 2.83301H10.167"
34
+ stroke="currentColor"
35
+ strokeWidth="1.13"
36
+ strokeLinecap="round"
37
+ strokeLinejoin="round"
38
+ />
39
+ <path
40
+ d="M5.33301 2.33301V4.33301"
41
+ stroke="currentColor"
42
+ strokeWidth="1.13"
43
+ strokeLinecap="round"
44
+ strokeLinejoin="round"
45
+ />
46
+ <path
47
+ d="M6.33301 3.33301H4.33301"
48
+ stroke="currentColor"
49
+ strokeWidth="1.13"
50
+ strokeLinecap="round"
51
+ strokeLinejoin="round"
52
+ />
53
+ <path
54
+ d="M13.333 9.33301V11.333"
55
+ stroke="currentColor"
56
+ strokeWidth="1.13"
57
+ strokeLinecap="round"
58
+ strokeLinejoin="round"
59
+ />
60
+ <path
61
+ d="M14.333 10.333H12.333"
62
+ stroke="currentColor"
63
+ strokeWidth="1.13"
64
+ strokeLinecap="round"
65
+ strokeLinejoin="round"
66
+ />
67
+ </svg>
68
+ )
69
+
70
+ MagicWandIcon.displayName = 'MagicWandIcon'
package/src/icons.jsx CHANGED
@@ -168,6 +168,7 @@ export { LoopSection3Icon } from './icons/LoopSection3Icon'
168
168
  export { NoLoopSection2Icon } from './icons/NoLoopSection2Icon'
169
169
  export { NoLoopSectionIcon } from './icons/NoLoopSectionIcon'
170
170
  export { NoLyricsIcon } from './icons/NoLyricsIcon'
171
+ export { MagicWandIcon } from './icons/MagicWandIcon'
171
172
  export { MasteringIcon } from './icons/MasteringIcon'
172
173
  export { MonoIcon } from './icons/MonoIcon'
173
174
  export { MusicControlIcon } from './icons/MusicControlIcon'