@moises.ai/design-system 3.9.14 → 3.9.16
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 +1073 -1054
- package/package.json +1 -1
- package/src/components/BannerAnnouncement/BannerAnnouncement.module.css +6 -1
- package/src/components/BannerAnnouncement/BannerAnnouncement.stories.jsx +32 -28
- package/src/components/ListCards/ListCards.jsx +12 -8
- package/src/components/useForm/useForm.jsx +133 -107
package/package.json
CHANGED
|
@@ -27,23 +27,25 @@ const withFlexContainer = (Story) => (
|
|
|
27
27
|
|
|
28
28
|
export const FeatureAnnouncement = {
|
|
29
29
|
render: (args) => (
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
<Flex justify="center" align="center" style={{ padding: '40px', marginTop: '100px' }}>
|
|
31
|
+
<BannerAnnouncement {...args}>
|
|
32
|
+
<Flex direction="column" gap="2">
|
|
33
|
+
<Heading size="3" weight="bold">
|
|
34
|
+
Moises AI Studio
|
|
35
|
+
</Heading>
|
|
36
|
+
<Text size="2">
|
|
37
|
+
Step into the future of music creation. Record, separate, generate,
|
|
38
|
+
convert voice, mix, and master with our all-in-one essential AI tool
|
|
39
|
+
for musicians.
|
|
40
|
+
</Text>
|
|
41
|
+
</Flex>
|
|
42
|
+
</BannerAnnouncement>
|
|
43
|
+
</Flex>
|
|
42
44
|
),
|
|
43
45
|
args: {
|
|
44
46
|
badge: { label: 'New', color: 'green' },
|
|
45
47
|
image:
|
|
46
|
-
'https://storage.googleapis.com/moises-cms/
|
|
48
|
+
'https://storage.googleapis.com/moises-cms/image_3x_f0971a1e8e/image_3x_f0971a1e8e.png',
|
|
47
49
|
link: 'https://www.google.com',
|
|
48
50
|
buttonText: 'Learn More',
|
|
49
51
|
buttonColor: 'gray',
|
|
@@ -92,21 +94,23 @@ export const DayOne = {
|
|
|
92
94
|
}, [])
|
|
93
95
|
|
|
94
96
|
return (
|
|
95
|
-
<
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
97
|
+
<Flex justify="center" align="center" style={{ padding: '40px', marginTop: '100px' }}>
|
|
98
|
+
<BannerAnnouncement {...args}>
|
|
99
|
+
<Flex direction="column" gap="3">
|
|
100
|
+
<Text size="2">Up to 30% off any individual plan for the next:</Text>
|
|
101
|
+
<Countdown
|
|
102
|
+
show={true}
|
|
103
|
+
days={timeLeft.days}
|
|
104
|
+
hours={timeLeft.hours}
|
|
105
|
+
minutes={timeLeft.minutes}
|
|
106
|
+
seconds={timeLeft.seconds}
|
|
107
|
+
theme="default"
|
|
108
|
+
variant="lg"
|
|
109
|
+
showDays={false}
|
|
110
|
+
/>
|
|
111
|
+
</Flex>
|
|
112
|
+
</BannerAnnouncement>
|
|
113
|
+
</Flex>
|
|
110
114
|
)
|
|
111
115
|
},
|
|
112
116
|
args: {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { StarFilledIcon, StarIcon } from '@radix-ui/react-icons'
|
|
2
2
|
import {
|
|
3
|
-
RadioCards,
|
|
4
|
-
Flex,
|
|
5
3
|
Avatar,
|
|
6
4
|
Box,
|
|
7
|
-
|
|
5
|
+
Flex,
|
|
6
|
+
RadioCards,
|
|
8
7
|
ScrollArea,
|
|
8
|
+
Text,
|
|
9
9
|
} from '@radix-ui/themes'
|
|
10
|
-
import styles from './ListCards.module.css'
|
|
11
10
|
import classNames from 'classnames'
|
|
12
|
-
import
|
|
11
|
+
import React from 'react'
|
|
13
12
|
import { ThumbnailPicker } from '../ThumbnailPicker/ThumbnailPicker'
|
|
13
|
+
import styles from './ListCards.module.css'
|
|
14
14
|
|
|
15
15
|
const Card = ({
|
|
16
16
|
className,
|
|
@@ -25,6 +25,7 @@ const Card = ({
|
|
|
25
25
|
checked,
|
|
26
26
|
onClick,
|
|
27
27
|
size = '1',
|
|
28
|
+
style,
|
|
28
29
|
...props
|
|
29
30
|
}) => {
|
|
30
31
|
// Use item properties if the item object is provided
|
|
@@ -63,7 +64,7 @@ const Card = ({
|
|
|
63
64
|
styles.listCardsItem,
|
|
64
65
|
styles[`listCardsItemSize${size}`],
|
|
65
66
|
)}
|
|
66
|
-
style={{ '--color-surface': 'transparent' }}
|
|
67
|
+
style={{ '--color-surface': 'transparent', ...style }}
|
|
67
68
|
data-state={checked ? 'checked' : 'unchecked'}
|
|
68
69
|
onClick={handleClick}
|
|
69
70
|
{...props}
|
|
@@ -138,6 +139,7 @@ const CardDetails = ({
|
|
|
138
139
|
avatarContent,
|
|
139
140
|
seeDetailsText = 'Details',
|
|
140
141
|
children,
|
|
142
|
+
style,
|
|
141
143
|
...props
|
|
142
144
|
}) => {
|
|
143
145
|
// Use item properties if the item object is provided
|
|
@@ -160,6 +162,7 @@ const CardDetails = ({
|
|
|
160
162
|
'--color-surface': 'transparent',
|
|
161
163
|
width: '100%',
|
|
162
164
|
height: '68px',
|
|
165
|
+
...style,
|
|
163
166
|
}}
|
|
164
167
|
{...props}
|
|
165
168
|
>
|
|
@@ -269,6 +272,7 @@ export const ListCards = ({
|
|
|
269
272
|
className,
|
|
270
273
|
children,
|
|
271
274
|
alwaysShowScrollbar = true,
|
|
275
|
+
style,
|
|
272
276
|
...props
|
|
273
277
|
}) => {
|
|
274
278
|
return (
|
|
@@ -278,7 +282,7 @@ export const ListCards = ({
|
|
|
278
282
|
scrollbars="vertical"
|
|
279
283
|
type={alwaysShowScrollbar ? 'auto' : 'hover'}
|
|
280
284
|
>
|
|
281
|
-
<Box className={styles.gridContainer} pr="3">
|
|
285
|
+
<Box className={styles.gridContainer} pr="3" style={style}>
|
|
282
286
|
<RadioCards.Root
|
|
283
287
|
defaultValue={undefined}
|
|
284
288
|
className={className}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { Flex, Text } from '../../index'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import('zod').ZodObject} ZodSchema
|
|
@@ -55,13 +55,17 @@ import { Text, Flex } from '../../index'
|
|
|
55
55
|
|
|
56
56
|
/** @param {{ name: string, errorsRef: React.MutableRefObject<Object>, children: React.ReactNode }} props */
|
|
57
57
|
function FieldView({ name, errorsRef, children }) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
const msg = errorsRef.current[name]
|
|
59
|
+
return (
|
|
60
|
+
<Flex direction="column" gap="1">
|
|
61
|
+
{children}
|
|
62
|
+
{msg && (
|
|
63
|
+
<Text size="1" color="red">
|
|
64
|
+
{msg}
|
|
65
|
+
</Text>
|
|
66
|
+
)}
|
|
67
|
+
</Flex>
|
|
68
|
+
)
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
/**
|
|
@@ -97,108 +101,130 @@ function FieldView({ name, errorsRef, children }) {
|
|
|
97
101
|
* }
|
|
98
102
|
*/
|
|
99
103
|
export function useForm({ schema }) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
for (const cb of listenersRef.current) cb(values)
|
|
113
|
-
}, [values])
|
|
114
|
-
|
|
115
|
-
/** @type {(name: string, value: *) => void} */
|
|
116
|
-
const set = useCallback((name, value) => {
|
|
117
|
-
setValues((prev) => ({ ...prev, [name]: value }))
|
|
118
|
-
setErrors((prev) => ({ ...prev, [name]: null }))
|
|
119
|
-
}, [])
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Returns spread-ready props for a form control.
|
|
123
|
-
*
|
|
124
|
-
* - Default (no type): returns `{ value, onChange }` for text inputs and selects.
|
|
125
|
-
* - `'checkbox'` / `'switch'`: returns `{ checked, onCheckedChange }`.
|
|
126
|
-
*
|
|
127
|
-
* @param {string} name - Field name matching a key in the schema.
|
|
128
|
-
* @param {ToggleFieldType} [type] - Control type hint.
|
|
129
|
-
* @returns {TextFieldProps | ToggleFieldProps}
|
|
130
|
-
*/
|
|
131
|
-
const connectField = (name, type) => {
|
|
132
|
-
const val = values[name]
|
|
133
|
-
const update = (v) => set(name, v)
|
|
134
|
-
if (type === 'checkbox' || type === 'switch') {
|
|
135
|
-
return { checked: !!val, onCheckedChange: update }
|
|
136
|
-
}
|
|
137
|
-
return { value: val ?? '', onChange: (e) => update(e?.target?.value ?? e) }
|
|
104
|
+
const [values, setValues] = useState(() => schema.parse({}))
|
|
105
|
+
const [errors, setErrors] = useState({})
|
|
106
|
+
const errorsRef = useRef(errors)
|
|
107
|
+
errorsRef.current = errors
|
|
108
|
+
const listenersRef = useRef([])
|
|
109
|
+
|
|
110
|
+
const mountedRef = useRef(false)
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!mountedRef.current) {
|
|
113
|
+
mountedRef.current = true
|
|
114
|
+
return
|
|
138
115
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const result = schema.safeParse(values)
|
|
148
|
-
if (result.success) {
|
|
149
|
-
setErrors({})
|
|
150
|
-
return { isValid: true, data: result.data }
|
|
151
|
-
}
|
|
152
|
-
const fieldErrors = {}
|
|
153
|
-
for (const issue of result.error.issues) {
|
|
154
|
-
const name = issue.path.join('.')
|
|
155
|
-
if (!fieldErrors[name]) fieldErrors[name] = issue.message
|
|
156
|
-
}
|
|
157
|
-
setErrors(fieldErrors)
|
|
158
|
-
return { isValid: false, data: values }
|
|
116
|
+
for (const cb of listenersRef.current) cb(values)
|
|
117
|
+
}, [values])
|
|
118
|
+
|
|
119
|
+
/** @type {((name: string, value: *) => void) | ((values: Object) => void)} */
|
|
120
|
+
const set = useCallback((name, value) => {
|
|
121
|
+
if (typeof name === 'string') {
|
|
122
|
+
setValues((prev) => ({ ...prev, [name]: value }))
|
|
123
|
+
setErrors((prev) => ({ ...prev, [name]: null }))
|
|
159
124
|
}
|
|
160
125
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return <Text size="1" color="red">{msg}</Text>
|
|
126
|
+
const values = name
|
|
127
|
+
const errors = Object.fromEntries(
|
|
128
|
+
Object.entries(values).map(([key]) => ({ [key]: null })),
|
|
129
|
+
)
|
|
130
|
+
setValues((prev) => ({ ...prev, ...values }))
|
|
131
|
+
setErrors((prev) => ({ ...prev, ...errors }))
|
|
132
|
+
}, [])
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Returns spread-ready props for a form control.
|
|
136
|
+
*
|
|
137
|
+
* - Default (no type): returns `{ value, onChange }` for text inputs and selects.
|
|
138
|
+
* - `'checkbox'` / `'switch'`: returns `{ checked, onCheckedChange }`.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} name - Field name matching a key in the schema.
|
|
141
|
+
* @param {ToggleFieldType} [type] - Control type hint.
|
|
142
|
+
* @returns {TextFieldProps | ToggleFieldProps}
|
|
143
|
+
*/
|
|
144
|
+
const connectField = (name, type) => {
|
|
145
|
+
const val = values[name]
|
|
146
|
+
const update = (v) => set(name, v)
|
|
147
|
+
if (type === 'checkbox' || type === 'switch') {
|
|
148
|
+
return { checked: !!val, onCheckedChange: update }
|
|
185
149
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
150
|
+
return { value: val ?? '', onChange: (e) => update(e?.target?.value ?? e) }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validate the current values against the schema.
|
|
155
|
+
*
|
|
156
|
+
* @returns {FormResult} `{ isValid: true, data }` on success, or
|
|
157
|
+
* `{ isValid: false, data }` on failure (with field errors set).
|
|
158
|
+
*/
|
|
159
|
+
const getValues = () => {
|
|
160
|
+
const result = schema.safeParse(values)
|
|
161
|
+
if (result.success) {
|
|
162
|
+
setErrors({})
|
|
163
|
+
return { isValid: true, data: result.data }
|
|
195
164
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
165
|
+
const fieldErrors = {}
|
|
166
|
+
for (const issue of result.error.issues) {
|
|
167
|
+
const name = issue.path.join('.')
|
|
168
|
+
if (!fieldErrors[name]) fieldErrors[name] = issue.message
|
|
169
|
+
}
|
|
170
|
+
setErrors(fieldErrors)
|
|
171
|
+
return { isValid: false, data: values }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Subscribe to value changes. The callback fires after every state
|
|
176
|
+
* update (skipping the initial mount).
|
|
177
|
+
*
|
|
178
|
+
* @param {(values: Object) => void} cb - Listener called with the latest values.
|
|
179
|
+
* @returns {() => void} Unsubscribe function (safe to return from `useEffect`).
|
|
180
|
+
*/
|
|
181
|
+
const watchValues = useCallback((cb) => {
|
|
182
|
+
listenersRef.current.push(cb)
|
|
183
|
+
return () => {
|
|
184
|
+
listenersRef.current = listenersRef.current.filter((l) => l !== cb)
|
|
185
|
+
}
|
|
186
|
+
}, [])
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Returns the error message element for a field, or `null` if valid.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} name - Field name.
|
|
192
|
+
* @returns {React.ReactNode | null}
|
|
193
|
+
*/
|
|
194
|
+
const error = (name) => {
|
|
195
|
+
const msg = errors[name]
|
|
196
|
+
if (!msg) return null
|
|
197
|
+
return (
|
|
198
|
+
<Text size="1" color="red">
|
|
199
|
+
{msg}
|
|
200
|
+
</Text>
|
|
201
201
|
)
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Reset all values and clear errors.
|
|
206
|
+
*
|
|
207
|
+
* @param {Object} [vals] - Optional values to reset to. Defaults to schema defaults.
|
|
208
|
+
*/
|
|
209
|
+
const reset = (vals) => {
|
|
210
|
+
setValues(vals ?? schema.parse({}))
|
|
211
|
+
setErrors({})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** @type {React.FC<FieldProps>} */
|
|
215
|
+
const Field = useCallback(
|
|
216
|
+
(props) => <FieldView {...props} errorsRef={errorsRef} />,
|
|
217
|
+
[],
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
connectField,
|
|
222
|
+
Field,
|
|
223
|
+
error,
|
|
224
|
+
values,
|
|
225
|
+
set,
|
|
226
|
+
reset,
|
|
227
|
+
getValues,
|
|
228
|
+
watchValues,
|
|
229
|
+
}
|
|
204
230
|
}
|