@moises.ai/design-system 3.5.7 → 3.6.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.
@@ -151,7 +151,9 @@ export const SetlistList = ({
151
151
  }) => {
152
152
  const [openSetlistMenuId, setOpenSetlistMenuId] = useState(null)
153
153
  const [isHoveredWhenCollapsed, setIsHoveredWhenCollapsed] = useState(false)
154
+ const [isFullyCollapsed, setIsFullyCollapsed] = useState(false)
154
155
  const hoverTimeoutRef = useRef(null)
156
+ const collapseCenterTimeoutRef = useRef(null)
155
157
  const collapsedItemHeight = 44
156
158
  const collapsedVisibleOffset = 8
157
159
  const maxCollapsedItems = 4
@@ -180,6 +182,26 @@ export const SetlistList = ({
180
182
  }, 180)
181
183
  }
182
184
 
185
+ useEffect(() => {
186
+ if (showCollapsedStack) {
187
+ collapseCenterTimeoutRef.current = setTimeout(() => {
188
+ setIsFullyCollapsed(true)
189
+ collapseCenterTimeoutRef.current = null
190
+ }, 200)
191
+ } else {
192
+ setIsFullyCollapsed(false)
193
+ if (collapseCenterTimeoutRef.current) {
194
+ clearTimeout(collapseCenterTimeoutRef.current)
195
+ collapseCenterTimeoutRef.current = null
196
+ }
197
+ }
198
+ return () => {
199
+ if (collapseCenterTimeoutRef.current) {
200
+ clearTimeout(collapseCenterTimeoutRef.current)
201
+ }
202
+ }
203
+ }, [showCollapsedStack])
204
+
183
205
  useEffect(() => {
184
206
  return () => {
185
207
  if (hoverTimeoutRef.current) {
@@ -260,6 +282,7 @@ export const SetlistList = ({
260
282
  direction="column"
261
283
  className={classNames(styles.setlistsContent, {
262
284
  [styles.collapsedStack]: showCollapsedStack,
285
+ [styles.collapsedStackCentered]: isFullyCollapsed,
263
286
  [styles.collapsedMask]: collapsed,
264
287
  })}
265
288
  >
@@ -194,22 +194,27 @@
194
194
  }
195
195
  .collapsedStack {
196
196
  gap: 0;
197
+ }
198
+
199
+ .collapsedStackCentered {
197
200
  align-items: center;
198
201
  }
199
202
  .collapsedStackItem {
200
203
  position: relative;
201
- justify-content: flex-start;
204
+ /* justify-content: flex-start; */
202
205
  }
203
206
 
204
207
  .collapsedMask {
205
208
  position: relative;
206
209
  overflow: hidden;
207
210
  padding-top: 2px;
211
+ display: flex;
212
+
208
213
  }
209
214
 
210
- .collapsedStack.collapsedMask {
215
+ /* .collapsedStack.collapsedMask {
211
216
  padding-top: 12px;
212
- }
217
+ } */
213
218
 
214
219
  .collapsedTransition {
215
220
  transition: margin-top 360ms ease-in-out;
@@ -0,0 +1,204 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react'
2
+ import { Text, Flex } from '../../index'
3
+
4
+ /**
5
+ * @typedef {import('zod').ZodObject} ZodSchema
6
+ */
7
+
8
+ /**
9
+ * @typedef {'checkbox' | 'switch'} ToggleFieldType
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} TextFieldProps
14
+ * @property {string} value - Current field value.
15
+ * @property {(e: Event | string) => void} onChange - Change handler that accepts
16
+ * an input event or a raw value (for Select-like components).
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} ToggleFieldProps
21
+ * @property {boolean} checked - Current checked state.
22
+ * @property {(checked: boolean) => void} onCheckedChange - Callback fired when the checked state changes.
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} FormResult
27
+ * @property {boolean} isValid - Whether the current values pass schema validation.
28
+ * @property {Object} data - Parsed data when valid, raw values when invalid.
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} FieldProps
33
+ * @property {string} name - Field name matching a key in the schema.
34
+ * @property {React.ReactNode} children - The form control to render.
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} UseFormReturn
39
+ * @property {((name: string, type?: undefined) => TextFieldProps) & ((name: string, type: ToggleFieldType) => ToggleFieldProps)} connectField
40
+ * Returns spread-ready props for a form control. Pass `'checkbox'` or `'switch'`
41
+ * as the second argument for toggle controls.
42
+ * @property {React.FC<FieldProps>} Field - Wrapper that renders its children with
43
+ * an inline validation error message below.
44
+ * @property {(name: string) => React.ReactNode | null} error - Returns the error
45
+ * message element for a field, or `null` if the field is valid.
46
+ * @property {Object} values - Current form values object (reactive).
47
+ * @property {(name: string, value: *) => void} set - Imperatively set a single field value.
48
+ * @property {(vals?: Object) => void} reset - Reset all values and errors.
49
+ * Pass an object to reset to specific values, or omit for schema defaults.
50
+ * @property {() => FormResult} getValues - Validate the form and return
51
+ * `{ isValid, data }`. Sets field errors on failure.
52
+ * @property {(cb: (values: Object) => void) => () => void} watchValues
53
+ * Subscribe to value changes. Returns an unsubscribe function.
54
+ */
55
+
56
+ /** @param {{ name: string, errorsRef: React.MutableRefObject<Object>, children: React.ReactNode }} props */
57
+ function FieldView({ name, errorsRef, children }) {
58
+ const msg = errorsRef.current[name]
59
+ return (
60
+ <Flex direction="column" gap="1">
61
+ {children}
62
+ {msg && <Text size="1" color="red">{msg}</Text>}
63
+ </Flex>
64
+ )
65
+ }
66
+
67
+ /**
68
+ * Lightweight form hook driven by a Zod schema.
69
+ *
70
+ * @param {Object} options
71
+ * @param {ZodSchema} options.schema - Zod object schema used for default values and validation.
72
+ * @returns {UseFormReturn}
73
+ *
74
+ * @example
75
+ * const schema = z.object({
76
+ * name: z.string().min(2, 'Too short').default(''),
77
+ * agree: z.boolean().refine(v => v, 'Required').default(false),
78
+ * })
79
+ *
80
+ * function MyForm() {
81
+ * const { connectField, Field, getValues } = useForm({ schema })
82
+ *
83
+ * return (
84
+ * <>
85
+ * <Field name="name">
86
+ * <TextField placeholder="Name" {...connectField('name')} />
87
+ * </Field>
88
+ * <Field name="agree">
89
+ * <Checkbox {...connectField('agree', 'checkbox')} />
90
+ * </Field>
91
+ * <Button onClick={() => {
92
+ * const { isValid, data } = getValues()
93
+ * if (isValid) console.log(data)
94
+ * }}>Save</Button>
95
+ * </>
96
+ * )
97
+ * }
98
+ */
99
+ export function useForm({ schema }) {
100
+ const [values, setValues] = useState(() => schema.parse({}))
101
+ const [errors, setErrors] = useState({})
102
+ const errorsRef = useRef(errors)
103
+ errorsRef.current = errors
104
+ const listenersRef = useRef([])
105
+
106
+ const mountedRef = useRef(false)
107
+ useEffect(() => {
108
+ if (!mountedRef.current) {
109
+ mountedRef.current = true
110
+ return
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) }
138
+ }
139
+
140
+ /**
141
+ * Validate the current values against the schema.
142
+ *
143
+ * @returns {FormResult} `{ isValid: true, data }` on success, or
144
+ * `{ isValid: false, data }` on failure (with field errors set).
145
+ */
146
+ const getValues = () => {
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 }
159
+ }
160
+
161
+ /**
162
+ * Subscribe to value changes. The callback fires after every state
163
+ * update (skipping the initial mount).
164
+ *
165
+ * @param {(values: Object) => void} cb - Listener called with the latest values.
166
+ * @returns {() => void} Unsubscribe function (safe to return from `useEffect`).
167
+ */
168
+ const watchValues = useCallback((cb) => {
169
+ listenersRef.current.push(cb)
170
+ return () => {
171
+ listenersRef.current = listenersRef.current.filter((l) => l !== cb)
172
+ }
173
+ }, [])
174
+
175
+ /**
176
+ * Returns the error message element for a field, or `null` if valid.
177
+ *
178
+ * @param {string} name - Field name.
179
+ * @returns {React.ReactNode | null}
180
+ */
181
+ const error = (name) => {
182
+ const msg = errors[name]
183
+ if (!msg) return null
184
+ return <Text size="1" color="red">{msg}</Text>
185
+ }
186
+
187
+ /**
188
+ * Reset all values and clear errors.
189
+ *
190
+ * @param {Object} [vals] - Optional values to reset to. Defaults to schema defaults.
191
+ */
192
+ const reset = (vals) => {
193
+ setValues(vals ?? schema.parse({}))
194
+ setErrors({})
195
+ }
196
+
197
+ /** @type {React.FC<FieldProps>} */
198
+ const Field = useCallback(
199
+ (props) => <FieldView {...props} errorsRef={errorsRef} />,
200
+ []
201
+ )
202
+
203
+ return { connectField, Field, error, values, set, reset, getValues, watchValues }
204
+ }