@fayz-ai/ui 0.14.0 → 0.15.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.
Files changed (84) hide show
  1. package/dist/{ChartWidgetInner-GDE5PBTR.js → ChartWidgetInner-IOLIUYTB.js} +21 -8
  2. package/dist/ChartWidgetInner-IOLIUYTB.js.map +1 -0
  3. package/dist/analytics/AnalyticsCard.d.ts.map +1 -1
  4. package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
  5. package/dist/chunk-3OVZ5JJQ.js +540 -0
  6. package/dist/chunk-3OVZ5JJQ.js.map +1 -0
  7. package/dist/{chunk-UZYXFEQS.js → chunk-4GK6IHOI.js} +16 -3
  8. package/dist/chunk-4GK6IHOI.js.map +1 -0
  9. package/dist/{chunk-46UZAKFJ.js → chunk-5KMAEQA3.js} +7 -6
  10. package/dist/chunk-5KMAEQA3.js.map +1 -0
  11. package/dist/chunk-AASANYXN.js +601 -0
  12. package/dist/chunk-AASANYXN.js.map +1 -0
  13. package/dist/{chunk-LFZFBN3P.js → chunk-FFJ2C5YH.js} +11 -61
  14. package/dist/chunk-FFJ2C5YH.js.map +1 -0
  15. package/dist/{chunk-5KAGNW45.js → chunk-J7F3Z52K.js} +4 -4
  16. package/dist/{chunk-5KAGNW45.js.map → chunk-J7F3Z52K.js.map} +1 -1
  17. package/dist/{chunk-2EFRFYGF.js → chunk-KD2V3TAX.js} +180 -33
  18. package/dist/chunk-KD2V3TAX.js.map +1 -0
  19. package/dist/{chunk-MVH64JFG.js → chunk-NA3GE4H3.js} +488 -537
  20. package/dist/chunk-NA3GE4H3.js.map +1 -0
  21. package/dist/chunk-OKZ6WQB3.js +57 -0
  22. package/dist/chunk-OKZ6WQB3.js.map +1 -0
  23. package/dist/{chunk-77CUIMOI.js → chunk-SLWTKHNY.js} +103 -158
  24. package/dist/chunk-SLWTKHNY.js.map +1 -0
  25. package/dist/dashboard/ChartWidgetInner.d.ts.map +1 -1
  26. package/dist/dashboard/index.js +5 -4
  27. package/dist/icons.d.ts.map +1 -1
  28. package/dist/icons.js +1 -1
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +30 -16
  32. package/dist/index.js.map +1 -1
  33. package/dist/layout/AppShell.d.ts +7 -2
  34. package/dist/layout/AppShell.d.ts.map +1 -1
  35. package/dist/layout/Sidebar.d.ts +27 -4
  36. package/dist/layout/Sidebar.d.ts.map +1 -1
  37. package/dist/layout/Topbar.d.ts +10 -3
  38. package/dist/layout/Topbar.d.ts.map +1 -1
  39. package/dist/layout/index.js +5 -4
  40. package/dist/primitives/SearchCombobox.d.ts +9 -1
  41. package/dist/primitives/SearchCombobox.d.ts.map +1 -1
  42. package/dist/primitives/color-palette.d.ts +24 -0
  43. package/dist/primitives/color-palette.d.ts.map +1 -0
  44. package/dist/primitives/header-tabs.d.ts +11 -1
  45. package/dist/primitives/header-tabs.d.ts.map +1 -1
  46. package/dist/primitives/image-drop.d.ts +28 -0
  47. package/dist/primitives/image-drop.d.ts.map +1 -0
  48. package/dist/primitives/index.d.ts +8 -3
  49. package/dist/primitives/index.d.ts.map +1 -1
  50. package/dist/primitives/index.js +7 -5
  51. package/dist/primitives/list-view.d.ts +7 -1
  52. package/dist/primitives/list-view.d.ts.map +1 -1
  53. package/dist/primitives/option-select.d.ts +46 -0
  54. package/dist/primitives/option-select.d.ts.map +1 -0
  55. package/dist/primitives/page-toolbar.d.ts +20 -1
  56. package/dist/primitives/page-toolbar.d.ts.map +1 -1
  57. package/dist/primitives/sheet.d.ts.map +1 -1
  58. package/dist/primitives/step-progress.d.ts +20 -0
  59. package/dist/primitives/step-progress.d.ts.map +1 -0
  60. package/dist/primitives/use-row-selection.d.ts +50 -0
  61. package/dist/primitives/use-row-selection.d.ts.map +1 -0
  62. package/dist/styles.css +20 -0
  63. package/dist/wizard/Wizard.d.ts +38 -0
  64. package/dist/wizard/Wizard.d.ts.map +1 -0
  65. package/dist/wizard/WizardField.d.ts +22 -0
  66. package/dist/wizard/WizardField.d.ts.map +1 -0
  67. package/dist/wizard/index.d.ts +6 -0
  68. package/dist/wizard/index.d.ts.map +1 -0
  69. package/dist/wizard/index.js +5 -0
  70. package/dist/wizard/index.js.map +1 -0
  71. package/dist/wizard/logic.d.ts +39 -0
  72. package/dist/wizard/logic.d.ts.map +1 -0
  73. package/dist/wizard/types.d.ts +95 -0
  74. package/dist/wizard/types.d.ts.map +1 -0
  75. package/dist/wizard/useWizard.d.ts +47 -0
  76. package/dist/wizard/useWizard.d.ts.map +1 -0
  77. package/package.json +12 -6
  78. package/dist/ChartWidgetInner-GDE5PBTR.js.map +0 -1
  79. package/dist/chunk-2EFRFYGF.js.map +0 -1
  80. package/dist/chunk-46UZAKFJ.js.map +0 -1
  81. package/dist/chunk-77CUIMOI.js.map +0 -1
  82. package/dist/chunk-LFZFBN3P.js.map +0 -1
  83. package/dist/chunk-MVH64JFG.js.map +0 -1
  84. package/dist/chunk-UZYXFEQS.js.map +0 -1
@@ -0,0 +1,601 @@
1
+ import { ImageDrop, Switch, OptionChips, OptionCards, ColorPalette, StepProgress, ConfirmDialog } from './chunk-3OVZ5JJQ.js';
2
+ import { cn, Button } from './chunk-OKZ6WQB3.js';
3
+ import * as React from 'react';
4
+ import { Loader2, Sparkles, X, ArrowLeft, ArrowRight } from 'lucide-react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ // src/wizard/logic.ts
8
+ function isEmpty(value) {
9
+ if (value === void 0 || value === null) return true;
10
+ if (typeof value === "string") return value.trim() === "";
11
+ if (Array.isArray(value)) return value.length === 0;
12
+ return false;
13
+ }
14
+ function visibleSteps(steps, values) {
15
+ return steps.filter((s) => s.visibleWhen ? s.visibleWhen(values) : true);
16
+ }
17
+ function visibleFields(step, values) {
18
+ return (step?.fields ?? []).filter((f) => f.visibleWhen ? f.visibleWhen(values) : true);
19
+ }
20
+ function fieldErrors(fields, values, asyncErrors = {}) {
21
+ const out = {};
22
+ for (const field of fields) {
23
+ const value = values[field.id];
24
+ out[field.id] = (isEmpty(value) ? null : field.validate?.(value, values)) ?? asyncErrors[field.id] ?? null;
25
+ }
26
+ return out;
27
+ }
28
+ function canAdvance(step, fields, values, errors) {
29
+ if (!step) return false;
30
+ if (fields.some((f) => f.required && isEmpty(values[f.id]))) return false;
31
+ if (Object.values(errors).some(Boolean)) return false;
32
+ return !step.validate?.(values);
33
+ }
34
+ function acceptableSuggestion(patch, knownFieldIds, answered) {
35
+ const accepted = {};
36
+ for (const [id, value] of Object.entries(patch)) {
37
+ if (!knownFieldIds.has(id)) continue;
38
+ if (answered.has(id)) continue;
39
+ if (isEmpty(value)) continue;
40
+ accepted[id] = value;
41
+ }
42
+ return accepted;
43
+ }
44
+ function allFieldIds(steps) {
45
+ return new Set(steps.flatMap((s) => (s.fields ?? []).map((f) => f.id)));
46
+ }
47
+ function parseDraft(raw) {
48
+ if (!raw) return null;
49
+ try {
50
+ const parsed = JSON.parse(raw);
51
+ const values = parsed.values && typeof parsed.values === "object" ? parsed.values : {};
52
+ return { index: typeof parsed.index === "number" ? parsed.index : 0, values };
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ function readDraft(key) {
58
+ if (!key || typeof localStorage === "undefined") return null;
59
+ try {
60
+ return parseDraft(localStorage.getItem(key));
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+ function useWizard({ steps, initialValues, draftKey, onSubmit }) {
66
+ const [draft] = React.useState(() => readDraft(draftKey));
67
+ const [values, setValues] = React.useState(() => ({ ...initialValues, ...draft?.values }));
68
+ const [index, setIndex] = React.useState(() => draft?.index ?? 0);
69
+ const [submitting, setSubmitting] = React.useState(false);
70
+ const [touched, setTouched] = React.useState(() => Object.keys(draft?.values ?? {}).length > 0);
71
+ const [error, setError] = React.useState(null);
72
+ const [asyncErrors, setAsyncErrors] = React.useState({});
73
+ const [suggesting, setSuggesting] = React.useState(null);
74
+ const [suggested, setSuggested] = React.useState(() => /* @__PURE__ */ new Set());
75
+ const visibleSteps2 = React.useMemo(() => visibleSteps(steps, values), [steps, values]);
76
+ const safeIndex = Math.min(index, Math.max(visibleSteps2.length - 1, 0));
77
+ const step = visibleSteps2[safeIndex];
78
+ const visibleFields2 = React.useMemo(() => visibleFields(step, values), [step, values]);
79
+ const derivedRef = React.useRef(/* @__PURE__ */ new Set());
80
+ React.useEffect(() => {
81
+ const seeds = visibleFields2.filter((f) => f.derive && !derivedRef.current.has(f.id) && isEmpty(values[f.id])).map((f) => [f, f.derive(values)]).filter(([, derived]) => !isEmpty(derived));
82
+ if (seeds.length === 0) return;
83
+ setValues((prev) => {
84
+ const next2 = { ...prev };
85
+ for (const [field, derived] of seeds) {
86
+ derivedRef.current.add(field.id);
87
+ next2[field.id] = derived;
88
+ }
89
+ return next2;
90
+ });
91
+ }, [visibleFields2, values]);
92
+ React.useEffect(() => {
93
+ if (!draftKey || !touched || typeof localStorage === "undefined") return;
94
+ try {
95
+ localStorage.setItem(draftKey, JSON.stringify({ index: safeIndex, values }));
96
+ } catch {
97
+ }
98
+ }, [draftKey, touched, safeIndex, values]);
99
+ const answeredRef = React.useRef(new Set(Object.keys(draft?.values ?? {})));
100
+ const setValue = React.useCallback((id, value) => {
101
+ setTouched(true);
102
+ answeredRef.current.add(id);
103
+ setValues((prev) => ({ ...prev, [id]: value }));
104
+ setAsyncErrors((prev) => prev[id] ? { ...prev, [id]: null } : prev);
105
+ setSuggested((prev) => {
106
+ if (!prev.has(id)) return prev;
107
+ const next2 = new Set(prev);
108
+ next2.delete(id);
109
+ return next2;
110
+ });
111
+ }, []);
112
+ const clearSuggestion = React.useCallback((id) => {
113
+ answeredRef.current.add(id);
114
+ setValues((prev) => {
115
+ if (!(id in prev)) return prev;
116
+ const next2 = { ...prev };
117
+ delete next2[id];
118
+ return next2;
119
+ });
120
+ setSuggested((prev) => {
121
+ if (!prev.has(id)) return prev;
122
+ const next2 = new Set(prev);
123
+ next2.delete(id);
124
+ return next2;
125
+ });
126
+ }, []);
127
+ const checkable = visibleFields2.filter((f) => f.checkAsync);
128
+ const checkSignature = checkable.map((f) => `${f.id}:${String(values[f.id] ?? "")}`).join("|");
129
+ React.useEffect(() => {
130
+ if (checkable.length === 0) return;
131
+ let cancelled = false;
132
+ const timer = setTimeout(() => {
133
+ for (const field of checkable) {
134
+ const raw = values[field.id];
135
+ if (typeof raw !== "string" || raw.trim() === "") continue;
136
+ void field.checkAsync(raw, values).then((message) => {
137
+ if (!cancelled) setAsyncErrors((prev) => ({ ...prev, [field.id]: message }));
138
+ }).catch(() => {
139
+ if (!cancelled) setAsyncErrors((prev) => ({ ...prev, [field.id]: null }));
140
+ });
141
+ }
142
+ }, 400);
143
+ return () => {
144
+ cancelled = true;
145
+ clearTimeout(timer);
146
+ };
147
+ }, [checkSignature]);
148
+ const knownFieldIds = React.useMemo(() => allFieldIds(steps), [steps]);
149
+ const applySuggestion = React.useCallback(
150
+ (patch) => {
151
+ const accepted = acceptableSuggestion(patch, knownFieldIds, answeredRef.current);
152
+ const ids = Object.keys(accepted);
153
+ if (ids.length === 0) return;
154
+ setValues((prev) => ({ ...prev, ...accepted }));
155
+ setSuggested((prev) => {
156
+ const next2 = new Set(prev);
157
+ for (const id of ids) next2.add(id);
158
+ return next2;
159
+ });
160
+ },
161
+ [knownFieldIds]
162
+ );
163
+ const suggestable = visibleFields2.filter((f) => f.suggest);
164
+ const suggestSignature = suggestable.map((f) => `${f.id}:${String(values[f.id] ?? "")}`).join("|");
165
+ const suggestedForRef = React.useRef(/* @__PURE__ */ new Map());
166
+ const suggestRunRef = React.useRef(0);
167
+ React.useEffect(() => {
168
+ if (suggestable.length === 0) return;
169
+ const pending = suggestable.filter((field) => {
170
+ const raw = values[field.id];
171
+ if (typeof raw !== "string" || raw.trim() === "") return false;
172
+ return suggestedForRef.current.get(field.id) !== raw.trim();
173
+ });
174
+ if (pending.length === 0) return;
175
+ let cancelled = false;
176
+ const timer = setTimeout(() => {
177
+ const run = ++suggestRunRef.current;
178
+ for (const field of pending) {
179
+ const raw = String(values[field.id]).trim();
180
+ suggestedForRef.current.set(field.id, raw);
181
+ setSuggesting(field.id);
182
+ void field.suggest(raw, values).then((patch) => {
183
+ if (cancelled || run !== suggestRunRef.current) return;
184
+ if (patch) applySuggestion(patch);
185
+ }).catch(() => {
186
+ }).finally(() => {
187
+ if (!cancelled && run === suggestRunRef.current) setSuggesting(null);
188
+ });
189
+ }
190
+ }, 900);
191
+ return () => {
192
+ cancelled = true;
193
+ clearTimeout(timer);
194
+ };
195
+ }, [suggestSignature]);
196
+ const fieldErrors2 = React.useMemo(
197
+ () => fieldErrors(visibleFields2, values, asyncErrors),
198
+ [visibleFields2, values, asyncErrors]
199
+ );
200
+ const blocker = React.useMemo(() => step?.validate?.(values) ?? null, [step, values]);
201
+ const canAdvance2 = !submitting && canAdvance(step, visibleFields2, values, fieldErrors2);
202
+ const isLast = safeIndex >= visibleSteps2.length - 1;
203
+ const clearDraft = React.useCallback(() => {
204
+ if (!draftKey || typeof localStorage === "undefined") return;
205
+ try {
206
+ localStorage.removeItem(draftKey);
207
+ } catch {
208
+ }
209
+ }, [draftKey]);
210
+ const submit = React.useCallback(async () => {
211
+ if (!onSubmit) return;
212
+ setSubmitting(true);
213
+ setError(null);
214
+ try {
215
+ await onSubmit(values);
216
+ clearDraft();
217
+ } catch (err) {
218
+ setError(err instanceof Error ? err.message : String(err));
219
+ } finally {
220
+ setSubmitting(false);
221
+ }
222
+ }, [onSubmit, values, clearDraft]);
223
+ const next = React.useCallback(() => {
224
+ if (!canAdvance2) return;
225
+ if (isLast) {
226
+ void submit();
227
+ return;
228
+ }
229
+ setIndex((i) => Math.min(i + 1, visibleSteps2.length - 1));
230
+ }, [canAdvance2, isLast, submit, visibleSteps2.length]);
231
+ const back = React.useCallback(() => setIndex((i) => Math.max(i - 1, 0)), []);
232
+ const goTo = React.useCallback(
233
+ (target) => setIndex(Math.max(0, Math.min(target, visibleSteps2.length - 1))),
234
+ [visibleSteps2.length]
235
+ );
236
+ return {
237
+ steps: visibleSteps2,
238
+ step,
239
+ index: safeIndex,
240
+ total: visibleSteps2.length,
241
+ values,
242
+ setValue,
243
+ fieldErrors: fieldErrors2,
244
+ blocker,
245
+ canAdvance: canAdvance2,
246
+ isFirst: safeIndex === 0,
247
+ isLast,
248
+ submitting,
249
+ error,
250
+ next,
251
+ back,
252
+ goTo,
253
+ submit,
254
+ touched,
255
+ clearDraft,
256
+ visibleFields: visibleFields2,
257
+ suggesting,
258
+ suggested,
259
+ clearSuggestion
260
+ };
261
+ }
262
+ var FIELD_CLASS = "h-12 w-full rounded-lg border border-border bg-card px-4 text-base text-foreground placeholder:text-muted-foreground transition-colors focus:border-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50";
263
+ var INPUT_TYPE = {
264
+ email: "email",
265
+ phone: "tel"
266
+ };
267
+ function WizardField({
268
+ field,
269
+ values,
270
+ onChange,
271
+ error,
272
+ autoFocus,
273
+ shortcuts,
274
+ busy,
275
+ suggested,
276
+ onClearSuggestion,
277
+ suggestedLabel
278
+ }) {
279
+ const value = values[field.id];
280
+ const set = (next) => onChange(field.id, next);
281
+ const body = (() => {
282
+ switch (field.kind) {
283
+ case "palette":
284
+ return /* @__PURE__ */ jsx(
285
+ ColorPalette,
286
+ {
287
+ value: Array.isArray(value) ? value : void 0,
288
+ onChange: set,
289
+ slots: field.slots
290
+ }
291
+ );
292
+ case "cards":
293
+ return /* @__PURE__ */ jsx(
294
+ OptionCards,
295
+ {
296
+ options: field.options ?? [],
297
+ value,
298
+ onChange: set,
299
+ multi: field.multi,
300
+ columns: field.columns,
301
+ shortcuts,
302
+ "aria-label": field.label
303
+ }
304
+ );
305
+ case "chips":
306
+ return /* @__PURE__ */ jsx(
307
+ OptionChips,
308
+ {
309
+ options: field.options ?? [],
310
+ value,
311
+ onChange: set,
312
+ multi: field.multi,
313
+ shortcuts,
314
+ "aria-label": field.label
315
+ }
316
+ );
317
+ case "toggle":
318
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 rounded-lg border border-border bg-card px-4 py-3", children: [
319
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: field.label }),
320
+ /* @__PURE__ */ jsx(Switch, { checked: Boolean(value), onCheckedChange: (checked) => set(checked) })
321
+ ] });
322
+ case "image":
323
+ return /* @__PURE__ */ jsx(
324
+ ImageDrop,
325
+ {
326
+ value: typeof value === "string" ? value : void 0,
327
+ onUpload: field.onUpload,
328
+ onChange: (url) => set(url),
329
+ label: field.label,
330
+ hint: field.hint
331
+ }
332
+ );
333
+ case "textarea":
334
+ return /* @__PURE__ */ jsx(
335
+ "textarea",
336
+ {
337
+ rows: 3,
338
+ className: cn(FIELD_CLASS, "h-auto py-3 leading-relaxed"),
339
+ placeholder: field.placeholder,
340
+ value: typeof value === "string" ? value : "",
341
+ autoFocus,
342
+ onChange: (e) => set(e.target.value)
343
+ }
344
+ );
345
+ default:
346
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
347
+ /* @__PURE__ */ jsx(
348
+ "input",
349
+ {
350
+ type: INPUT_TYPE[field.kind] ?? "text",
351
+ className: cn(FIELD_CLASS, busy && "pr-11", error && "border-destructive"),
352
+ placeholder: field.placeholder,
353
+ value: typeof value === "string" ? value : "",
354
+ autoFocus,
355
+ autoComplete: field.kind === "email" ? "email" : "off",
356
+ onChange: (e) => set(field.normalize ? field.normalize(e.target.value) : e.target.value)
357
+ }
358
+ ),
359
+ busy && // The wrapper does the positioning and the icon does nothing but
360
+ // spin. Both on one element fights: `animate-spin`'s keyframe sets
361
+ // a bare `transform: rotate(...)`, which replaces the composed
362
+ // transform that `-translate-y-1/2` lives in — so the icon drops
363
+ // half its height on every frame and the rotation reads as a
364
+ // stutter. `inset-y-0` + `items-center` centres with no transform
365
+ // at all, leaving the animation the only one.
366
+ /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute inset-y-0 right-4 flex items-center", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) })
367
+ ] });
368
+ }
369
+ })();
370
+ const labelAbove = field.label && field.kind !== "toggle" && field.kind !== "image";
371
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
372
+ (labelAbove || suggested) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
373
+ labelAbove ? /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-foreground", children: field.label }) : /* @__PURE__ */ jsx("span", {}),
374
+ suggested && suggestedLabel && /* @__PURE__ */ jsx(SuggestedBadge, { label: suggestedLabel, onClear: onClearSuggestion })
375
+ ] }),
376
+ body,
377
+ field.hint && field.kind !== "image" && !error && /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: field.hint }),
378
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
379
+ ] });
380
+ }
381
+ function SuggestedBadge({ label, onClear }) {
382
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground", children: [
383
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
384
+ label,
385
+ onClear && /* @__PURE__ */ jsx(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ onClick: onClear,
390
+ "aria-label": `${label} \u2014 descartar`,
391
+ className: "ml-0.5 rounded-full text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
392
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
393
+ }
394
+ )
395
+ ] });
396
+ }
397
+ var DEFAULT_LABELS = {
398
+ back: "Back",
399
+ next: "Continue",
400
+ finish: "Finish",
401
+ exit: "Exit",
402
+ // `{current}` and `{total}` are substituted.
403
+ stepOf: "Step {current} of {total}",
404
+ exitTitle: "Leave setup?",
405
+ exitDescription: "You have already started \u2014 leaving now loses what you filled in.",
406
+ exitConfirm: "Leave",
407
+ exitCancel: "Go back",
408
+ suggested: "from your site"
409
+ };
410
+ function MediaFallback() {
411
+ return /* @__PURE__ */ jsxs("div", { className: "relative h-full w-full overflow-hidden bg-sidebar", children: [
412
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-br from-primary/25 via-transparent to-sidebar" }),
413
+ /* @__PURE__ */ jsx("div", { className: "absolute -right-24 -top-24 h-96 w-96 rounded-full bg-primary/20 blur-3xl" }),
414
+ /* @__PURE__ */ jsx("div", { className: "absolute -bottom-32 -left-16 h-96 w-96 rounded-full bg-primary/10 blur-3xl" })
415
+ ] });
416
+ }
417
+ function MediaPanel({ media }) {
418
+ if (!media) return /* @__PURE__ */ jsx(MediaFallback, {});
419
+ if (media.node) return /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-auto bg-sidebar", children: media.node });
420
+ if (!media.src) return /* @__PURE__ */ jsx(MediaFallback, {});
421
+ return /* @__PURE__ */ jsxs("picture", { className: "block h-full w-full", children: [
422
+ media.srcDark && /* @__PURE__ */ jsx("source", { srcSet: media.srcDark, media: "(prefers-color-scheme: dark)" }),
423
+ /* @__PURE__ */ jsx("img", { src: media.src, alt: media.alt ?? "", className: "h-full w-full object-cover" })
424
+ ] });
425
+ }
426
+ function Wizard({
427
+ steps,
428
+ initialValues,
429
+ draftKey,
430
+ onSubmit,
431
+ onExit,
432
+ logo,
433
+ fallbackMedia,
434
+ labels,
435
+ className
436
+ }) {
437
+ const text = { ...DEFAULT_LABELS, ...labels };
438
+ const w = useWizard({ steps, initialValues, draftKey, onSubmit });
439
+ const [confirmExit, setConfirmExit] = React.useState(false);
440
+ const [direction, setDirection] = React.useState("forward");
441
+ const goNext = React.useCallback(() => {
442
+ setDirection("forward");
443
+ w.next();
444
+ }, [w]);
445
+ const goBack = React.useCallback(() => {
446
+ setDirection("back");
447
+ w.back();
448
+ }, [w]);
449
+ React.useEffect(() => {
450
+ function onKey(e) {
451
+ if (confirmExit) return;
452
+ const target = e.target;
453
+ const typing = target?.tagName === "TEXTAREA";
454
+ if (e.key === "Enter" && !typing && !e.shiftKey) {
455
+ e.preventDefault();
456
+ goNext();
457
+ }
458
+ if (e.key === "ArrowLeft" && target?.tagName !== "INPUT" && !typing && !w.isFirst) goBack();
459
+ }
460
+ document.addEventListener("keydown", onKey);
461
+ return () => document.removeEventListener("keydown", onKey);
462
+ }, [goNext, goBack, w.isFirst, confirmExit]);
463
+ React.useEffect(() => {
464
+ if (typeof document === "undefined") return;
465
+ const previous = document.body.style.overflow;
466
+ document.body.style.overflow = "hidden";
467
+ return () => {
468
+ document.body.style.overflow = previous;
469
+ };
470
+ }, []);
471
+ const requestExit = () => {
472
+ if (!onExit) return;
473
+ if (!w.touched) {
474
+ onExit();
475
+ return;
476
+ }
477
+ setConfirmExit(true);
478
+ };
479
+ const [pendingAdvance, setPendingAdvance] = React.useState(null);
480
+ const setValue = React.useCallback(
481
+ (id, value) => {
482
+ w.setValue(id, value);
483
+ if (w.isLast || !w.step || w.step.render) return;
484
+ const field = w.visibleFields.find((f) => f.id === id);
485
+ if (!field || field.multi || field.kind !== "cards" && field.kind !== "chips") return;
486
+ if (w.visibleFields.length !== 1) return;
487
+ if (visibleFields(w.step, { ...w.values, [id]: value }).length !== 1) return;
488
+ setPendingAdvance(w.step.id);
489
+ },
490
+ [w]
491
+ );
492
+ React.useEffect(() => {
493
+ if (!pendingAdvance) return;
494
+ if (pendingAdvance !== w.step?.id) {
495
+ setPendingAdvance(null);
496
+ return;
497
+ }
498
+ if (!w.canAdvance) return;
499
+ const timer = setTimeout(() => {
500
+ setPendingAdvance(null);
501
+ setDirection("forward");
502
+ w.next();
503
+ }, 320);
504
+ return () => clearTimeout(timer);
505
+ }, [pendingAdvance, w.step?.id, w.canAdvance, w.next]);
506
+ const choiceFields = w.visibleFields.filter((f) => f.kind === "cards" || f.kind === "chips");
507
+ const shortcutFieldId = (choiceFields.find((f) => isEmpty(w.values[f.id])) ?? choiceFields[0])?.id;
508
+ const step = w.step;
509
+ if (!step) return null;
510
+ const nextLabel = step.nextLabel ?? (w.isLast ? text.finish : text.next);
511
+ return /* @__PURE__ */ jsxs("div", { className: cn("fixed inset-0 z-50 flex bg-background", className), children: [
512
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col lg:w-[58%] lg:flex-none lg:basis-[58%]", children: [
513
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-6 pt-10 sm:px-12 lg:px-16", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-xl", children: [
514
+ logo && /* @__PURE__ */ jsx("div", { className: "mb-8", children: logo }),
515
+ /* @__PURE__ */ jsxs(
516
+ "div",
517
+ {
518
+ className: direction === "forward" ? "saas-nav-forward" : "saas-nav-back",
519
+ children: [
520
+ step.eyebrow && /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.14em] text-primary", children: step.eyebrow }),
521
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold leading-tight tracking-tight text-foreground sm:text-4xl", children: step.title }),
522
+ step.subtitle && /* @__PURE__ */ jsx("p", { className: "mt-3 text-base leading-relaxed text-muted-foreground", children: step.subtitle }),
523
+ /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-6 pb-10", children: [
524
+ step.render ? step.render({ values: w.values, setValue, steps: w.steps, index: w.index, submitting: w.submitting }) : w.visibleFields.map((field, i) => /* @__PURE__ */ jsx(
525
+ WizardField,
526
+ {
527
+ field,
528
+ values: w.values,
529
+ onChange: setValue,
530
+ error: w.fieldErrors[field.id],
531
+ autoFocus: i === 0 && (field.kind === "text" || field.kind === "email" || field.kind === "slug"),
532
+ shortcuts: field.id === shortcutFieldId,
533
+ busy: w.suggesting === field.id,
534
+ suggested: w.suggested.has(field.id),
535
+ suggestedLabel: text.suggested,
536
+ onClearSuggestion: () => w.clearSuggestion(field.id)
537
+ },
538
+ field.id
539
+ )),
540
+ w.blocker && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: w.blocker }),
541
+ w.error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: w.error })
542
+ ] })
543
+ ]
544
+ },
545
+ step.id
546
+ )
547
+ ] }) }),
548
+ /* @__PURE__ */ jsx("div", { className: "border-t border-border/60 px-6 pb-6 pt-4 sm:px-12 lg:px-16", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-xl", children: [
549
+ /* @__PURE__ */ jsx(
550
+ StepProgress,
551
+ {
552
+ total: w.total,
553
+ current: w.index,
554
+ onSelect: (i) => {
555
+ setDirection("back");
556
+ w.goTo(i);
557
+ },
558
+ "aria-label": text.stepOf.replace("{current}", String(w.index + 1)).replace("{total}", String(w.total)),
559
+ className: "mb-4"
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
563
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
564
+ !w.isFirst && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "lg", onClick: goBack, disabled: w.submitting, children: [
565
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }),
566
+ text.back
567
+ ] }),
568
+ onExit && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "lg", onClick: requestExit, disabled: w.submitting, children: text.exit })
569
+ ] }),
570
+ /* @__PURE__ */ jsxs(Button, { size: "lg", onClick: goNext, disabled: !w.canAdvance, children: [
571
+ w.submitting ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
572
+ nextLabel,
573
+ !w.submitting && !w.isLast && /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" })
574
+ ] })
575
+ ] })
576
+ ] }) })
577
+ ] }),
578
+ /* @__PURE__ */ jsx("div", { className: "hidden lg:block lg:flex-1", children: /* @__PURE__ */ jsx(MediaPanel, { media: step.media ?? fallbackMedia }) }),
579
+ /* @__PURE__ */ jsx(
580
+ ConfirmDialog,
581
+ {
582
+ open: confirmExit,
583
+ title: text.exitTitle,
584
+ description: text.exitDescription,
585
+ confirmLabel: text.exitConfirm,
586
+ cancelLabel: text.exitCancel,
587
+ variant: "warning",
588
+ onConfirm: () => {
589
+ setConfirmExit(false);
590
+ w.clearDraft();
591
+ onExit?.();
592
+ },
593
+ onCancel: () => setConfirmExit(false)
594
+ }
595
+ )
596
+ ] });
597
+ }
598
+
599
+ export { Wizard, WizardField, canAdvance, fieldErrors, isEmpty, parseDraft, useWizard, visibleFields, visibleSteps };
600
+ //# sourceMappingURL=chunk-AASANYXN.js.map
601
+ //# sourceMappingURL=chunk-AASANYXN.js.map