@ttoss/react-wizard 0.4.8 → 0.4.9

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.mjs ADDED
@@ -0,0 +1,490 @@
1
+ /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
+ import { defineMessages, useI18n } from "@ttoss/react-i18n";
3
+ import { Box, Button, Flex, keyframes } from "@ttoss/ui";
4
+ import * as React from "react";
5
+ import { Steps } from "@chakra-ui/react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+
8
+ //#region src/Wizard.styles.ts
9
+ const gradientFlow = keyframes({
10
+ "0%": {
11
+ backgroundPosition: "0% 50%"
12
+ },
13
+ "50%": {
14
+ backgroundPosition: "100% 50%"
15
+ },
16
+ "100%": {
17
+ backgroundPosition: "0% 50%"
18
+ }
19
+ });
20
+ const getAccentGradientBackground = t => {
21
+ const theme = t;
22
+ const start = theme.colors?.action?.background?.accent?.default || theme.colors?.input?.background?.accent?.default;
23
+ if (!start) return void 0;
24
+ return `linear-gradient(270deg, ${start}, ${theme.colors?.action?.background?.accent?.active || theme.colors?.input?.background?.accent?.active || start}, ${start})`;
25
+ };
26
+ const getPrimaryGradientBackground = t => {
27
+ const theme = t;
28
+ const start = theme.colors?.action?.background?.primary?.default;
29
+ if (!start) return void 0;
30
+ return `linear-gradient(270deg, ${start}, ${theme.colors?.action?.background?.secondary?.default || start}, ${start})`;
31
+ };
32
+ const getVariantStyles = (variantType = "spotlight-accent") => {
33
+ const variants = {
34
+ "spotlight-accent": {
35
+ accentColor: "action.background.accent.default",
36
+ accentTextColor: "action.text.accent.default",
37
+ borderColor: "display.border.muted.default",
38
+ primaryButtonVariant: "accent",
39
+ gradientBackground: getAccentGradientBackground
40
+ },
41
+ "spotlight-primary": {
42
+ accentColor: "action.background.primary.default",
43
+ accentTextColor: "display.text.accent.default",
44
+ borderColor: "display.border.muted.default",
45
+ primaryButtonVariant: "primary",
46
+ gradientBackground: getPrimaryGradientBackground
47
+ },
48
+ primary: {
49
+ accentColor: "action.background.primary.default",
50
+ accentTextColor: "action.text.primary.default",
51
+ borderColor: "display.border.muted.default",
52
+ primaryButtonVariant: "primary"
53
+ },
54
+ secondary: {
55
+ accentColor: "action.background.secondary.default",
56
+ accentTextColor: "action.text.primary.default",
57
+ borderColor: "display.border.muted.default",
58
+ primaryButtonVariant: "secondary"
59
+ },
60
+ accent: {
61
+ accentColor: "action.background.accent.default",
62
+ accentTextColor: "action.text.accent.default",
63
+ borderColor: "display.border.muted.default",
64
+ primaryButtonVariant: "accent"
65
+ }
66
+ };
67
+ return variants[variantType] ?? variants["spotlight-accent"];
68
+ };
69
+ const getWizardShellSx = (variant = "spotlight-accent") => {
70
+ return {
71
+ width: "100%",
72
+ minHeight: "300px",
73
+ border: "1px solid",
74
+ borderColor: getVariantStyles(variant).borderColor,
75
+ borderRadius: "8px",
76
+ overflow: "hidden"
77
+ };
78
+ };
79
+ const getWizardStepListSx = ({
80
+ layout,
81
+ variant = "spotlight-accent"
82
+ }) => {
83
+ const isHorizontal = layout === "top" || layout === "bottom";
84
+ const isSpotlight = variant.startsWith("spotlight-");
85
+ const variantStyles = getVariantStyles(variant);
86
+ return {
87
+ position: "relative",
88
+ padding: "6",
89
+ backgroundColor: variantStyles.accentColor,
90
+ ...(isSpotlight ? {
91
+ background: variantStyles.gradientBackground,
92
+ backgroundSize: "400% 400%",
93
+ animation: `${gradientFlow} 6s ease infinite`
94
+ } : {}),
95
+ ...(isHorizontal ? {
96
+ width: "100%"
97
+ } : {
98
+ minWidth: "200px"
99
+ })
100
+ };
101
+ };
102
+ const getWizardStepIndicatorSx = ({
103
+ status,
104
+ variant = "spotlight-accent",
105
+ isClickable
106
+ }) => {
107
+ const variantStyles = getVariantStyles(variant);
108
+ const isCompleted = status === "completed";
109
+ const isUpcoming = status === "upcoming";
110
+ return {
111
+ borderColor: variantStyles.accentTextColor,
112
+ backgroundColor: isCompleted ? variantStyles.accentTextColor : "transparent",
113
+ color: isCompleted ? variantStyles.accentColor : variantStyles.accentTextColor,
114
+ opacity: isUpcoming ? .4 : 1,
115
+ transition: "all 0.2s ease",
116
+ ...(isClickable ? {
117
+ _hover: {
118
+ opacity: 1
119
+ }
120
+ } : {})
121
+ };
122
+ };
123
+ const getWizardStepSeparatorSx = ({
124
+ isCompleted,
125
+ variant = "spotlight-accent"
126
+ }) => {
127
+ return {
128
+ backgroundColor: getVariantStyles(variant).accentTextColor,
129
+ opacity: isCompleted ? 1 : .4
130
+ };
131
+ };
132
+ const getWizardStepTitleSx = ({
133
+ status,
134
+ variant = "spotlight-accent"
135
+ }) => {
136
+ return {
137
+ color: getVariantStyles(variant).accentTextColor,
138
+ textAlign: "center",
139
+ fontWeight: status === "active" ? "bold" : "normal",
140
+ opacity: status === "upcoming" ? .4 : 1
141
+ };
142
+ };
143
+ const getWizardStepDescriptionSx = ({
144
+ status,
145
+ variant = "spotlight-accent"
146
+ }) => {
147
+ return {
148
+ color: getVariantStyles(variant).accentTextColor,
149
+ textAlign: "center",
150
+ opacity: status === "upcoming" ? .4 : 1
151
+ };
152
+ };
153
+ const WizardStepDescriptionFlexSx = {
154
+ flexDirection: "row",
155
+ alignItems: "center",
156
+ justifyContent: "center"
157
+ };
158
+ const WizardStepTextWrapperSx = {
159
+ textAlign: "center"
160
+ };
161
+ const getWizardPrimaryButtonVariant = (variant = "spotlight-accent") => {
162
+ return getVariantStyles(variant).primaryButtonVariant;
163
+ };
164
+
165
+ //#endregion
166
+ //#region src/WizardContext.ts
167
+ const WizardContext = React.createContext(null);
168
+ /**
169
+ * Hook to access the wizard context from within step content.
170
+ */
171
+ const useWizard = () => {
172
+ const context = React.useContext(WizardContext);
173
+ if (!context) throw new Error("useWizard must be used within a Wizard component.");
174
+ return context;
175
+ };
176
+
177
+ //#endregion
178
+ //#region src/WizardStepList.tsx
179
+ const WizardStepList = ({
180
+ steps,
181
+ currentStep,
182
+ layout,
183
+ variant,
184
+ allowStepClick,
185
+ getStepStatus,
186
+ onStepClick
187
+ }) => {
188
+ const isHorizontal = layout === "top" || layout === "bottom";
189
+ const orientation = isHorizontal ? "horizontal" : "vertical";
190
+ return /* @__PURE__ */jsx(Flex, {
191
+ role: "navigation",
192
+ "aria-label": "Wizard steps",
193
+ "data-variant": variant,
194
+ sx: getWizardStepListSx({
195
+ layout,
196
+ variant
197
+ }),
198
+ children: /* @__PURE__ */jsx(Steps.Root, {
199
+ step: currentStep,
200
+ count: steps.length,
201
+ orientation,
202
+ children: /* @__PURE__ */jsx(Steps.List, {
203
+ children: steps.map((step, index) => {
204
+ const status = getStepStatus({
205
+ stepIndex: index
206
+ });
207
+ const isClickable = allowStepClick && status === "completed" && index !== currentStep;
208
+ return /* @__PURE__ */jsxs(Steps.Item, {
209
+ index,
210
+ children: [/* @__PURE__ */jsxs(Flex, {
211
+ sx: {
212
+ flexDirection: isHorizontal ? "column" : "row",
213
+ alignItems: "center",
214
+ gap: "2"
215
+ },
216
+ children: [/* @__PURE__ */jsx(Box, {
217
+ role: "button",
218
+ tabIndex: isClickable ? 0 : -1,
219
+ onClick: () => {
220
+ if (isClickable) onStepClick({
221
+ stepIndex: index
222
+ });
223
+ },
224
+ onKeyDown: e => {
225
+ if (isClickable && (e.key === "Enter" || e.key === " ")) {
226
+ e.preventDefault();
227
+ onStepClick({
228
+ stepIndex: index
229
+ });
230
+ }
231
+ },
232
+ "aria-current": status === "active" ? "step" : void 0,
233
+ sx: {
234
+ cursor: isClickable ? "pointer" : "default"
235
+ },
236
+ children: /* @__PURE__ */jsx(Steps.Indicator, {
237
+ css: getWizardStepIndicatorSx({
238
+ status,
239
+ variant,
240
+ isClickable
241
+ }),
242
+ children: /* @__PURE__ */jsx(Steps.Status, {
243
+ complete: "✓",
244
+ incomplete: /* @__PURE__ */jsx(Steps.Number, {})
245
+ })
246
+ })
247
+ }), (step.title || step.description) && /* @__PURE__ */jsxs(Box, {
248
+ sx: WizardStepTextWrapperSx,
249
+ children: [step.title && /* @__PURE__ */jsx(Steps.Title, {
250
+ css: getWizardStepTitleSx({
251
+ status,
252
+ variant
253
+ }),
254
+ children: step.title
255
+ }), step.description && /* @__PURE__ */jsx(Flex, {
256
+ sx: WizardStepDescriptionFlexSx,
257
+ children: /* @__PURE__ */jsx(Steps.Description, {
258
+ css: getWizardStepDescriptionSx({
259
+ status,
260
+ variant
261
+ }),
262
+ children: step.description
263
+ })
264
+ })]
265
+ })]
266
+ }), index < steps.length - 1 && /* @__PURE__ */jsx(Steps.Separator, {
267
+ css: getWizardStepSeparatorSx({
268
+ isCompleted: index < currentStep,
269
+ variant
270
+ })
271
+ })]
272
+ }, index);
273
+ })
274
+ })
275
+ })
276
+ });
277
+ };
278
+
279
+ //#endregion
280
+ //#region src/Wizard.tsx
281
+ const messages = defineMessages({
282
+ previous: {
283
+ id: "60eH0J",
284
+ defaultMessage: [{
285
+ "type": 0,
286
+ "value": "Previous"
287
+ }]
288
+ },
289
+ next: {
290
+ id: "FmaVut",
291
+ defaultMessage: [{
292
+ "type": 0,
293
+ "value": "Next"
294
+ }]
295
+ },
296
+ finish: {
297
+ id: "5lZ8VT",
298
+ defaultMessage: [{
299
+ "type": 0,
300
+ "value": "Finish"
301
+ }]
302
+ },
303
+ cancel: {
304
+ id: "6I9gjk",
305
+ defaultMessage: [{
306
+ "type": 0,
307
+ "value": "Cancel"
308
+ }]
309
+ }
310
+ });
311
+ const getFlexDirection = layout => {
312
+ switch (layout) {
313
+ case "top":
314
+ return "column";
315
+ case "bottom":
316
+ return "column-reverse";
317
+ case "left":
318
+ return "row";
319
+ case "right":
320
+ return "row-reverse";
321
+ }
322
+ };
323
+ /**
324
+ * Renders a multi-step wizard with step navigation, localized action labels,
325
+ * and support for completion, cancellation, and step changes.
326
+ *
327
+ * @param props - Wizard configuration and step content.
328
+ * @param props.steps - The ordered steps displayed in the wizard.
329
+ * @param props.onComplete - Called when the user finishes the last step.
330
+ * @param props.onCancel - Called when the user cancels the wizard.
331
+ * @param props.onStepChange - Called when the current step changes.
332
+ */
333
+ const Wizard = ({
334
+ steps,
335
+ layout = "top",
336
+ variant = "spotlight-accent",
337
+ onComplete,
338
+ onCancel,
339
+ onStepChange,
340
+ initialStep = 0,
341
+ allowStepClick = true,
342
+ labels
343
+ }) => {
344
+ const {
345
+ intl
346
+ } = useI18n();
347
+ const [currentStep, setCurrentStep] = React.useState(initialStep);
348
+ const stepValidationRef = React.useRef(null);
349
+ const totalSteps = steps.length;
350
+ const isFirstStep = currentStep === 0;
351
+ const isLastStep = currentStep === totalSteps - 1;
352
+ const buttonLabels = React.useMemo(() => {
353
+ return {
354
+ previous: labels?.previous ?? intl.formatMessage(messages.previous),
355
+ next: labels?.next ?? intl.formatMessage(messages.next),
356
+ finish: labels?.finish ?? intl.formatMessage(messages.finish),
357
+ cancel: labels?.cancel ?? intl.formatMessage(messages.cancel)
358
+ };
359
+ }, [intl, labels]);
360
+ const getStepStatus = React.useCallback(({
361
+ stepIndex
362
+ }) => {
363
+ if (stepIndex < currentStep) return "completed";
364
+ if (stepIndex === currentStep) return "active";
365
+ return "upcoming";
366
+ }, [currentStep]);
367
+ const goToNext = React.useCallback(async () => {
368
+ const step = steps[currentStep];
369
+ if (stepValidationRef.current) {
370
+ if (!(await stepValidationRef.current())) return;
371
+ }
372
+ if (step.onNext) {
373
+ if (!(await step.onNext())) return;
374
+ }
375
+ if (isLastStep) onComplete?.();else {
376
+ const nextStep = currentStep + 1;
377
+ stepValidationRef.current = null;
378
+ setCurrentStep(nextStep);
379
+ onStepChange?.({
380
+ stepIndex: nextStep
381
+ });
382
+ }
383
+ }, [currentStep, steps, isLastStep, onComplete, onStepChange]);
384
+ const goToPrevious = React.useCallback(() => {
385
+ if (!isFirstStep) {
386
+ const prevStep = currentStep - 1;
387
+ stepValidationRef.current = null;
388
+ setCurrentStep(prevStep);
389
+ onStepChange?.({
390
+ stepIndex: prevStep
391
+ });
392
+ }
393
+ }, [currentStep, isFirstStep, onStepChange]);
394
+ const goToStep = React.useCallback(({
395
+ stepIndex
396
+ }) => {
397
+ if (stepIndex >= 0 && stepIndex < totalSteps && stepIndex <= currentStep) {
398
+ stepValidationRef.current = null;
399
+ setCurrentStep(stepIndex);
400
+ onStepChange?.({
401
+ stepIndex
402
+ });
403
+ }
404
+ }, [currentStep, totalSteps, onStepChange]);
405
+ const setStepValidation = React.useCallback(validate => {
406
+ stepValidationRef.current = validate;
407
+ }, []);
408
+ const contextValue = React.useMemo(() => {
409
+ return {
410
+ currentStep,
411
+ totalSteps,
412
+ goToNext,
413
+ goToPrevious,
414
+ goToStep,
415
+ isFirstStep,
416
+ isLastStep,
417
+ getStepStatus,
418
+ setStepValidation
419
+ };
420
+ }, [currentStep, totalSteps, goToNext, goToPrevious, goToStep, isFirstStep, isLastStep, getStepStatus, setStepValidation]);
421
+ return /* @__PURE__ */jsx(WizardContext.Provider, {
422
+ value: contextValue,
423
+ children: /* @__PURE__ */jsxs(Flex, {
424
+ "data-variant": variant,
425
+ sx: {
426
+ flexDirection: getFlexDirection(layout),
427
+ ...getWizardShellSx(variant)
428
+ },
429
+ children: [/* @__PURE__ */jsx(WizardStepList, {
430
+ steps,
431
+ currentStep,
432
+ layout,
433
+ variant,
434
+ allowStepClick,
435
+ getStepStatus,
436
+ onStepClick: goToStep
437
+ }), /* @__PURE__ */jsxs(Flex, {
438
+ sx: {
439
+ flexDirection: "column",
440
+ flex: 1,
441
+ padding: "6"
442
+ },
443
+ children: [/* @__PURE__ */jsx(Box, {
444
+ sx: {
445
+ flex: 1,
446
+ marginBottom: "4"
447
+ },
448
+ children: steps[currentStep].content
449
+ }), /* @__PURE__ */jsxs(Flex, {
450
+ sx: {
451
+ justifyContent: "space-between",
452
+ alignItems: "center",
453
+ gap: "3"
454
+ },
455
+ children: [/* @__PURE__ */jsx(Flex, {
456
+ sx: {
457
+ gap: "3"
458
+ },
459
+ children: onCancel && /* @__PURE__ */jsx(Button, {
460
+ variant: "secondary",
461
+ onClick: onCancel,
462
+ "aria-label": buttonLabels.cancel,
463
+ children: buttonLabels.cancel
464
+ })
465
+ }), /* @__PURE__ */jsxs(Flex, {
466
+ sx: {
467
+ gap: "3"
468
+ },
469
+ children: [/* @__PURE__ */jsx(Button, {
470
+ variant: "secondary",
471
+ onClick: goToPrevious,
472
+ disabled: isFirstStep,
473
+ "aria-label": buttonLabels.previous,
474
+ children: buttonLabels.previous
475
+ }), /* @__PURE__ */jsx(Button, {
476
+ variant: getWizardPrimaryButtonVariant(variant),
477
+ onClick: goToNext,
478
+ "aria-label": isLastStep ? buttonLabels.finish : buttonLabels.next,
479
+ children: isLastStep ? buttonLabels.finish : buttonLabels.next
480
+ })]
481
+ })]
482
+ })]
483
+ })]
484
+ })
485
+ });
486
+ };
487
+ Wizard.displayName = "Wizard";
488
+
489
+ //#endregion
490
+ export { Wizard, useWizard };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/react-wizard",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "A React wizard component for guiding users through multi-step flows with configurable step list layouts.",
5
5
  "keywords": [
6
6
  "React",
@@ -34,28 +34,28 @@
34
34
  "@types/jest": "^30.0.0",
35
35
  "@types/react": "^19.2.14",
36
36
  "jest": "^30.3.0",
37
- "react": "^19.2.4",
38
- "tsup": "^8.5.1",
39
- "@ttoss/config": "^1.37.12",
40
- "@ttoss/forms": "^0.43.24",
41
- "@ttoss/i18n-cli": "^0.8.12",
42
- "@ttoss/react-i18n": "^2.2.13",
43
- "@ttoss/ui": "^6.9.19",
44
- "@ttoss/test-utils": "^4.2.12"
37
+ "react": "^19.2.6",
38
+ "tsdown": "^0.22.0",
39
+ "@ttoss/i18n-cli": "^0.8.13",
40
+ "@ttoss/forms": "^0.43.25",
41
+ "@ttoss/react-i18n": "^2.2.14",
42
+ "@ttoss/test-utils": "^4.2.13",
43
+ "@ttoss/ui": "^6.9.20",
44
+ "@ttoss/config": "^1.37.13"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@chakra-ui/react": "^3",
48
48
  "react": ">=16.8.0",
49
49
  "react-icons": "^5",
50
- "@ttoss/react-i18n": "^2.2.13",
51
- "@ttoss/ui": "^6.9.19"
50
+ "@ttoss/ui": "^6.9.20",
51
+ "@ttoss/react-i18n": "^2.2.14"
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public",
55
55
  "provenance": true
56
56
  },
57
57
  "scripts": {
58
- "build": "tsup",
58
+ "build": "tsdown",
59
59
  "i18n": "ttoss-i18n",
60
60
  "test": "jest --projects tests/unit"
61
61
  }