@ttoss/react-wizard 0.2.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.
- package/LICENSE +21 -0
- package/README.md +399 -0
- package/dist/esm/index.js +295 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +329 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Terezinha Tech Operations (ttoss)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# @ttoss/react-wizard
|
|
2
|
+
|
|
3
|
+
A React wizard component for guiding users through multi-step flows with configurable step list layouts.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @ttoss/react-wizard
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Wizard } from '@ttoss/react-wizard';
|
|
15
|
+
|
|
16
|
+
const steps = [
|
|
17
|
+
{
|
|
18
|
+
title: 'Personal Info',
|
|
19
|
+
description: 'Enter your details',
|
|
20
|
+
content: <PersonalInfoForm />,
|
|
21
|
+
onNext: () => validatePersonalInfo(),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: 'Address',
|
|
25
|
+
content: <AddressForm />,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'Review',
|
|
29
|
+
content: <ReviewStep />,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const MyWizard = () => {
|
|
34
|
+
return (
|
|
35
|
+
<Wizard
|
|
36
|
+
steps={steps}
|
|
37
|
+
layout="top"
|
|
38
|
+
onComplete={() => console.log('Done!')}
|
|
39
|
+
onCancel={() => console.log('Cancelled')}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Props
|
|
46
|
+
|
|
47
|
+
| Prop | Type | Default | Description |
|
|
48
|
+
| ---------------- | ----------------------------------------- | ------- | ------------------------------------------------------ |
|
|
49
|
+
| `steps` | `WizardStep[]` | — | Array of step definitions. |
|
|
50
|
+
| `layout` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Position of the step list relative to content. |
|
|
51
|
+
| `onComplete` | `() => void` | — | Called when the user finishes the last step. |
|
|
52
|
+
| `onCancel` | `() => void` | — | Called on cancel. If omitted, cancel button is hidden. |
|
|
53
|
+
| `onStepChange` | `(params: { stepIndex: number }) => void` | — | Called when the active step changes. |
|
|
54
|
+
| `initialStep` | `number` | `0` | The initially active step index. |
|
|
55
|
+
| `allowStepClick` | `boolean` | `true` | Allow clicking completed steps to navigate back. |
|
|
56
|
+
|
|
57
|
+
## WizardStep
|
|
58
|
+
|
|
59
|
+
| Property | Type | Description |
|
|
60
|
+
| ------------- | ----------------------------------- | --------------------------------------------------------- |
|
|
61
|
+
| `title` | `string` | Title displayed in the step list. |
|
|
62
|
+
| `description` | `string` | Optional description below the title. |
|
|
63
|
+
| `content` | `ReactNode` | Content rendered when the step is active. |
|
|
64
|
+
| `onNext` | `() => boolean \| Promise<boolean>` | Validation callback. Return `false` to prevent advancing. |
|
|
65
|
+
|
|
66
|
+
## Layouts
|
|
67
|
+
|
|
68
|
+
The `layout` prop controls where the step list appears:
|
|
69
|
+
|
|
70
|
+
- **`top`** — Horizontal step list above the content (default).
|
|
71
|
+
- **`bottom`** — Horizontal step list below the content.
|
|
72
|
+
- **`left`** — Vertical step list on the left side.
|
|
73
|
+
- **`right`** — Vertical step list on the right side.
|
|
74
|
+
|
|
75
|
+
## useWizard Hook
|
|
76
|
+
|
|
77
|
+
Access the wizard context from within step content:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useWizard } from '@ttoss/react-wizard';
|
|
81
|
+
|
|
82
|
+
const StepContent = () => {
|
|
83
|
+
const {
|
|
84
|
+
currentStep,
|
|
85
|
+
totalSteps,
|
|
86
|
+
goToNext,
|
|
87
|
+
goToPrevious,
|
|
88
|
+
isLastStep,
|
|
89
|
+
setStepValidation,
|
|
90
|
+
} = useWizard();
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
Step {currentStep + 1} of {totalSteps}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### useWizard Return Value
|
|
101
|
+
|
|
102
|
+
| Property | Type | Description |
|
|
103
|
+
| ------------------- | ------------------------------------------------------- | ------------------------------------------------------------ |
|
|
104
|
+
| `currentStep` | `number` | Current active step index. |
|
|
105
|
+
| `totalSteps` | `number` | Total number of steps. |
|
|
106
|
+
| `goToNext` | `() => Promise<void>` | Navigate to the next step. |
|
|
107
|
+
| `goToPrevious` | `() => void` | Navigate to the previous step. |
|
|
108
|
+
| `goToStep` | `(params: { stepIndex: number }) => void` | Navigate to a specific step. |
|
|
109
|
+
| `isFirstStep` | `boolean` | Whether the wizard is on the first step. |
|
|
110
|
+
| `isLastStep` | `boolean` | Whether the wizard is on the last step. |
|
|
111
|
+
| `getStepStatus` | `(params: { stepIndex: number }) => WizardStepStatus` | Get the status of a step by index. |
|
|
112
|
+
| `setStepValidation` | `(validate: () => boolean \| Promise<boolean>) => void` | Register a validation function for the current step content. |
|
|
113
|
+
|
|
114
|
+
## Example: Form with Step-by-Step Validation
|
|
115
|
+
|
|
116
|
+
This example demonstrates using a single form across all wizard steps, with each step validating only its relevant fields using Zod:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { Wizard } from '@ttoss/react-wizard';
|
|
120
|
+
import {
|
|
121
|
+
Form,
|
|
122
|
+
FormFieldInput,
|
|
123
|
+
FormFieldSelect,
|
|
124
|
+
FormFieldTextarea,
|
|
125
|
+
useForm,
|
|
126
|
+
z,
|
|
127
|
+
zodResolver,
|
|
128
|
+
} from '@ttoss/forms';
|
|
129
|
+
import { Button, Box, Text } from '@ttoss/ui';
|
|
130
|
+
|
|
131
|
+
// Define the complete Zod schema for all steps
|
|
132
|
+
const schema = z.object({
|
|
133
|
+
// Step 1: Personal Information
|
|
134
|
+
firstName: z.string().min(1, 'First name is required'),
|
|
135
|
+
lastName: z.string().min(1, 'Last name is required'),
|
|
136
|
+
email: z.string().email('Invalid email address'),
|
|
137
|
+
|
|
138
|
+
// Step 2: Address
|
|
139
|
+
street: z.string().min(1, 'Street is required'),
|
|
140
|
+
city: z.string().min(1, 'City is required'),
|
|
141
|
+
country: z.string().min(1, 'Country is required'),
|
|
142
|
+
|
|
143
|
+
// Step 3: Additional Info
|
|
144
|
+
phone: z.string().min(10, 'Phone must be at least 10 digits'),
|
|
145
|
+
comments: z.string().optional(),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
type FormData = z.infer<typeof schema>;
|
|
149
|
+
|
|
150
|
+
const RegistrationWizard = () => {
|
|
151
|
+
const formMethods = useForm<FormData>({
|
|
152
|
+
mode: 'onChange',
|
|
153
|
+
resolver: zodResolver(schema),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const { trigger, handleSubmit } = formMethods;
|
|
157
|
+
|
|
158
|
+
const handleComplete = handleSubmit((data) => {
|
|
159
|
+
console.log('Form submitted:', data);
|
|
160
|
+
// Handle final submission
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const steps = [
|
|
164
|
+
{
|
|
165
|
+
title: 'Personal Info',
|
|
166
|
+
description: 'Basic information',
|
|
167
|
+
content: (
|
|
168
|
+
<Box>
|
|
169
|
+
<FormFieldInput
|
|
170
|
+
name="firstName"
|
|
171
|
+
label="First Name"
|
|
172
|
+
placeholder="Enter your first name"
|
|
173
|
+
/>
|
|
174
|
+
<FormFieldInput
|
|
175
|
+
name="lastName"
|
|
176
|
+
label="Last Name"
|
|
177
|
+
placeholder="Enter your last name"
|
|
178
|
+
/>
|
|
179
|
+
<FormFieldInput
|
|
180
|
+
name="email"
|
|
181
|
+
label="Email"
|
|
182
|
+
type="email"
|
|
183
|
+
placeholder="Enter your email"
|
|
184
|
+
/>
|
|
185
|
+
</Box>
|
|
186
|
+
),
|
|
187
|
+
onNext: async () => {
|
|
188
|
+
// Validate only Step 1 fields
|
|
189
|
+
return await trigger(['firstName', 'lastName', 'email']);
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: 'Address',
|
|
194
|
+
description: 'Location details',
|
|
195
|
+
content: (
|
|
196
|
+
<Box>
|
|
197
|
+
<FormFieldInput
|
|
198
|
+
name="street"
|
|
199
|
+
label="Street"
|
|
200
|
+
placeholder="Enter your street"
|
|
201
|
+
/>
|
|
202
|
+
<FormFieldInput
|
|
203
|
+
name="city"
|
|
204
|
+
label="City"
|
|
205
|
+
placeholder="Enter your city"
|
|
206
|
+
/>
|
|
207
|
+
<FormFieldSelect
|
|
208
|
+
name="country"
|
|
209
|
+
label="Country"
|
|
210
|
+
placeholder="Select your country"
|
|
211
|
+
options={[
|
|
212
|
+
{ value: 'us', label: 'United States' },
|
|
213
|
+
{ value: 'br', label: 'Brazil' },
|
|
214
|
+
{ value: 'uk', label: 'United Kingdom' },
|
|
215
|
+
]}
|
|
216
|
+
/>
|
|
217
|
+
</Box>
|
|
218
|
+
),
|
|
219
|
+
onNext: async () => {
|
|
220
|
+
// Validate only Step 2 fields
|
|
221
|
+
return await trigger(['street', 'city', 'country']);
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
title: 'Additional Info',
|
|
226
|
+
description: 'Final details',
|
|
227
|
+
content: (
|
|
228
|
+
<Box>
|
|
229
|
+
<FormFieldInput
|
|
230
|
+
name="phone"
|
|
231
|
+
label="Phone"
|
|
232
|
+
placeholder="Enter your phone number"
|
|
233
|
+
/>
|
|
234
|
+
<FormFieldTextarea
|
|
235
|
+
name="comments"
|
|
236
|
+
label="Comments (Optional)"
|
|
237
|
+
placeholder="Any additional comments"
|
|
238
|
+
/>
|
|
239
|
+
</Box>
|
|
240
|
+
),
|
|
241
|
+
onNext: async () => {
|
|
242
|
+
// Validate only Step 3 fields
|
|
243
|
+
return await trigger(['phone', 'comments']);
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
title: 'Review',
|
|
248
|
+
description: 'Confirm details',
|
|
249
|
+
content: (
|
|
250
|
+
<Box>
|
|
251
|
+
<Text variant="heading">Review Your Information</Text>
|
|
252
|
+
{/* Display summary of all form data */}
|
|
253
|
+
</Box>
|
|
254
|
+
),
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<Form {...formMethods} onSubmit={handleComplete}>
|
|
260
|
+
<Wizard
|
|
261
|
+
steps={steps}
|
|
262
|
+
layout="top"
|
|
263
|
+
onComplete={handleComplete}
|
|
264
|
+
onCancel={() => console.log('Wizard cancelled')}
|
|
265
|
+
/>
|
|
266
|
+
</Form>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Key Points:**
|
|
272
|
+
|
|
273
|
+
- **Single Form**: The `<Form>` component wraps the entire wizard, maintaining form state across all steps
|
|
274
|
+
- **Step-by-Step Validation**: Each step's `onNext` callback uses `trigger()` to validate only the fields relevant to that step
|
|
275
|
+
- **Zod Schema**: Define the complete schema upfront with all fields from all steps
|
|
276
|
+
- **Form Methods**: Access `trigger` from `useForm` to programmatically validate specific fields
|
|
277
|
+
- **Progressive Flow**: Users can only advance to the next step after passing validation for current step fields
|
|
278
|
+
|
|
279
|
+
## Example: Complex Component with Internal Validation
|
|
280
|
+
|
|
281
|
+
This example shows how a complex component can use `useWizard` to handle its own validation directly:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { Wizard, useWizard } from '@ttoss/react-wizard';
|
|
285
|
+
import { Box, Text } from '@ttoss/ui';
|
|
286
|
+
import { useState, useEffect } from 'react';
|
|
287
|
+
|
|
288
|
+
// Complex component with internal validation using useWizard
|
|
289
|
+
const ComplexForm = () => {
|
|
290
|
+
const { setStepValidation } = useWizard();
|
|
291
|
+
|
|
292
|
+
const [formData, setFormData] = useState({
|
|
293
|
+
username: '',
|
|
294
|
+
password: '',
|
|
295
|
+
confirmPassword: '',
|
|
296
|
+
});
|
|
297
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
298
|
+
|
|
299
|
+
// Register validation function with the wizard
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
const validate = async () => {
|
|
302
|
+
const newErrors: Record<string, string> = {};
|
|
303
|
+
|
|
304
|
+
// Complex validation logic
|
|
305
|
+
if (!formData.username || formData.username.length < 3) {
|
|
306
|
+
newErrors.username = 'Username must be at least 3 characters';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!formData.password || formData.password.length < 8) {
|
|
310
|
+
newErrors.password = 'Password must be at least 8 characters';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (formData.password !== formData.confirmPassword) {
|
|
314
|
+
newErrors.confirmPassword = 'Passwords do not match';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
setErrors(newErrors);
|
|
318
|
+
return Object.keys(newErrors).length === 0;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
setStepValidation(validate);
|
|
322
|
+
}, [formData, setStepValidation]);
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<Box>
|
|
326
|
+
<Box>
|
|
327
|
+
<Text>Username</Text>
|
|
328
|
+
<input
|
|
329
|
+
value={formData.username}
|
|
330
|
+
onChange={(e) =>
|
|
331
|
+
setFormData({ ...formData, username: e.target.value })
|
|
332
|
+
}
|
|
333
|
+
/>
|
|
334
|
+
{errors.username && <Text color="red">{errors.username}</Text>}
|
|
335
|
+
</Box>
|
|
336
|
+
|
|
337
|
+
<Box>
|
|
338
|
+
<Text>Password</Text>
|
|
339
|
+
<input
|
|
340
|
+
type="password"
|
|
341
|
+
value={formData.password}
|
|
342
|
+
onChange={(e) =>
|
|
343
|
+
setFormData({ ...formData, password: e.target.value })
|
|
344
|
+
}
|
|
345
|
+
/>
|
|
346
|
+
{errors.password && <Text color="red">{errors.password}</Text>}
|
|
347
|
+
</Box>
|
|
348
|
+
|
|
349
|
+
<Box>
|
|
350
|
+
<Text>Confirm Password</Text>
|
|
351
|
+
<input
|
|
352
|
+
type="password"
|
|
353
|
+
value={formData.confirmPassword}
|
|
354
|
+
onChange={(e) =>
|
|
355
|
+
setFormData({ ...formData, confirmPassword: e.target.value })
|
|
356
|
+
}
|
|
357
|
+
/>
|
|
358
|
+
{errors.confirmPassword && (
|
|
359
|
+
<Text color="red">{errors.confirmPassword}</Text>
|
|
360
|
+
)}
|
|
361
|
+
</Box>
|
|
362
|
+
</Box>
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const WizardWithComplexComponent = () => {
|
|
367
|
+
const steps = [
|
|
368
|
+
{
|
|
369
|
+
title: 'Account Setup',
|
|
370
|
+
description: 'Create your account',
|
|
371
|
+
content: <ComplexForm />,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
title: 'Profile',
|
|
375
|
+
content: <Box>Profile information...</Box>,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
title: 'Complete',
|
|
379
|
+
content: <Box>Setup complete!</Box>,
|
|
380
|
+
},
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<Wizard
|
|
385
|
+
steps={steps}
|
|
386
|
+
layout="top"
|
|
387
|
+
onComplete={() => console.log('Wizard complete!')}
|
|
388
|
+
/>
|
|
389
|
+
);
|
|
390
|
+
};
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Key Points:**
|
|
394
|
+
|
|
395
|
+
- **useWizard Hook**: The complex component uses `setStepValidation` from `useWizard` to register its validation function
|
|
396
|
+
- **Automatic Integration**: The wizard automatically calls the registered validation when the user tries to advance
|
|
397
|
+
- **No Refs Needed**: Simpler than using refs and forwardRef patterns
|
|
398
|
+
- **Self-Contained**: The component manages its own state and validation logic
|
|
399
|
+
- **Dynamic Updates**: The validation function updates when formData changes
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
5
|
+
value,
|
|
6
|
+
configurable: true
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/Wizard.tsx
|
|
10
|
+
import { defineMessages, useI18n } from "@ttoss/react-i18n";
|
|
11
|
+
import { Box as Box2, Button, Flex as Flex2 } from "@ttoss/ui";
|
|
12
|
+
import * as React3 from "react";
|
|
13
|
+
|
|
14
|
+
// src/WizardContext.ts
|
|
15
|
+
import * as React2 from "react";
|
|
16
|
+
var WizardContext = React2.createContext(null);
|
|
17
|
+
var useWizard = /* @__PURE__ */__name(() => {
|
|
18
|
+
const context = React2.useContext(WizardContext);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error("useWizard must be used within a Wizard component.");
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
}, "useWizard");
|
|
24
|
+
|
|
25
|
+
// src/WizardStepList.tsx
|
|
26
|
+
import { Steps } from "@chakra-ui/react";
|
|
27
|
+
import { Box, Flex } from "@ttoss/ui";
|
|
28
|
+
var WizardStepList = /* @__PURE__ */__name(({
|
|
29
|
+
steps,
|
|
30
|
+
currentStep,
|
|
31
|
+
layout,
|
|
32
|
+
allowStepClick,
|
|
33
|
+
getStepStatus,
|
|
34
|
+
onStepClick
|
|
35
|
+
}) => {
|
|
36
|
+
const isHorizontal = layout === "top" || layout === "bottom";
|
|
37
|
+
const orientation = isHorizontal ? "horizontal" : "vertical";
|
|
38
|
+
return /* @__PURE__ */React.createElement(Flex, {
|
|
39
|
+
role: "navigation",
|
|
40
|
+
"aria-label": "Wizard steps",
|
|
41
|
+
sx: {
|
|
42
|
+
padding: "6",
|
|
43
|
+
backgroundColor: "navigation.background.muted.default",
|
|
44
|
+
...(isHorizontal ? {
|
|
45
|
+
width: "100%",
|
|
46
|
+
borderBottom: layout === "top" ? "1px solid" : void 0,
|
|
47
|
+
borderTop: layout === "bottom" ? "1px solid" : void 0,
|
|
48
|
+
borderColor: "display.border.muted.default"
|
|
49
|
+
} : {
|
|
50
|
+
minWidth: "200px",
|
|
51
|
+
borderRight: layout === "left" ? "1px solid" : void 0,
|
|
52
|
+
borderLeft: layout === "right" ? "1px solid" : void 0,
|
|
53
|
+
borderColor: "display.border.muted.default"
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}, /* @__PURE__ */React.createElement(Steps.Root, {
|
|
57
|
+
step: currentStep,
|
|
58
|
+
count: steps.length,
|
|
59
|
+
orientation
|
|
60
|
+
}, /* @__PURE__ */React.createElement(Steps.List, null, steps.map((step, index) => {
|
|
61
|
+
const status = getStepStatus({
|
|
62
|
+
stepIndex: index
|
|
63
|
+
});
|
|
64
|
+
const isClickable = allowStepClick && status === "completed" && index !== currentStep;
|
|
65
|
+
return /* @__PURE__ */React.createElement(Steps.Item, {
|
|
66
|
+
key: index,
|
|
67
|
+
index
|
|
68
|
+
}, /* @__PURE__ */React.createElement(Flex, {
|
|
69
|
+
sx: {
|
|
70
|
+
flexDirection: isHorizontal ? "column" : "row",
|
|
71
|
+
alignItems: "center",
|
|
72
|
+
gap: "2"
|
|
73
|
+
}
|
|
74
|
+
}, /* @__PURE__ */React.createElement(Box, {
|
|
75
|
+
role: "button",
|
|
76
|
+
tabIndex: isClickable ? 0 : -1,
|
|
77
|
+
onClick: /* @__PURE__ */__name(() => {
|
|
78
|
+
if (isClickable) {
|
|
79
|
+
onStepClick({
|
|
80
|
+
stepIndex: index
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}, "onClick"),
|
|
84
|
+
onKeyDown: /* @__PURE__ */__name(e => {
|
|
85
|
+
if (isClickable && (e.key === "Enter" || e.key === " ")) {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
onStepClick({
|
|
88
|
+
stepIndex: index
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}, "onKeyDown"),
|
|
92
|
+
"aria-current": status === "active" ? "step" : void 0,
|
|
93
|
+
sx: {
|
|
94
|
+
cursor: isClickable ? "pointer" : "default"
|
|
95
|
+
}
|
|
96
|
+
}, /* @__PURE__ */React.createElement(Steps.Indicator, null, /* @__PURE__ */React.createElement(Steps.Status, {
|
|
97
|
+
complete: "\u2713",
|
|
98
|
+
incomplete: /* @__PURE__ */React.createElement(Steps.Number, null)
|
|
99
|
+
}))), (step.title || step.description) && /* @__PURE__ */React.createElement(Box, null, step.title && /* @__PURE__ */React.createElement(Steps.Title, null, step.title), step.description && /* @__PURE__ */React.createElement(Steps.Description, null, step.description))), index < steps.length - 1 && /* @__PURE__ */React.createElement(Steps.Separator, null));
|
|
100
|
+
}))));
|
|
101
|
+
}, "WizardStepList");
|
|
102
|
+
|
|
103
|
+
// src/Wizard.tsx
|
|
104
|
+
var messages = defineMessages({
|
|
105
|
+
previous: {
|
|
106
|
+
id: "60eH0J",
|
|
107
|
+
defaultMessage: [{
|
|
108
|
+
"type": 0,
|
|
109
|
+
"value": "Previous"
|
|
110
|
+
}]
|
|
111
|
+
},
|
|
112
|
+
next: {
|
|
113
|
+
id: "FmaVut",
|
|
114
|
+
defaultMessage: [{
|
|
115
|
+
"type": 0,
|
|
116
|
+
"value": "Next"
|
|
117
|
+
}]
|
|
118
|
+
},
|
|
119
|
+
finish: {
|
|
120
|
+
id: "5lZ8VT",
|
|
121
|
+
defaultMessage: [{
|
|
122
|
+
"type": 0,
|
|
123
|
+
"value": "Finish"
|
|
124
|
+
}]
|
|
125
|
+
},
|
|
126
|
+
cancel: {
|
|
127
|
+
id: "6I9gjk",
|
|
128
|
+
defaultMessage: [{
|
|
129
|
+
"type": 0,
|
|
130
|
+
"value": "Cancel"
|
|
131
|
+
}]
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
var getFlexDirection = /* @__PURE__ */__name(layout => {
|
|
135
|
+
switch (layout) {
|
|
136
|
+
case "top":
|
|
137
|
+
return "column";
|
|
138
|
+
case "bottom":
|
|
139
|
+
return "column-reverse";
|
|
140
|
+
case "left":
|
|
141
|
+
return "row";
|
|
142
|
+
case "right":
|
|
143
|
+
return "row-reverse";
|
|
144
|
+
}
|
|
145
|
+
}, "getFlexDirection");
|
|
146
|
+
var Wizard = /* @__PURE__ */__name(({
|
|
147
|
+
steps,
|
|
148
|
+
layout = "top",
|
|
149
|
+
onComplete,
|
|
150
|
+
onCancel,
|
|
151
|
+
onStepChange,
|
|
152
|
+
initialStep = 0,
|
|
153
|
+
allowStepClick = true
|
|
154
|
+
}) => {
|
|
155
|
+
const {
|
|
156
|
+
intl
|
|
157
|
+
} = useI18n();
|
|
158
|
+
const [currentStep, setCurrentStep] = React3.useState(initialStep);
|
|
159
|
+
const stepValidationRef = React3.useRef(null);
|
|
160
|
+
const totalSteps = steps.length;
|
|
161
|
+
const isFirstStep = currentStep === 0;
|
|
162
|
+
const isLastStep = currentStep === totalSteps - 1;
|
|
163
|
+
const getStepStatus = React3.useCallback(({
|
|
164
|
+
stepIndex
|
|
165
|
+
}) => {
|
|
166
|
+
if (stepIndex < currentStep) {
|
|
167
|
+
return "completed";
|
|
168
|
+
}
|
|
169
|
+
if (stepIndex === currentStep) {
|
|
170
|
+
return "active";
|
|
171
|
+
}
|
|
172
|
+
return "upcoming";
|
|
173
|
+
}, [currentStep]);
|
|
174
|
+
const goToNext = React3.useCallback(async () => {
|
|
175
|
+
const step = steps[currentStep];
|
|
176
|
+
if (stepValidationRef.current) {
|
|
177
|
+
const canProceed = await stepValidationRef.current();
|
|
178
|
+
if (!canProceed) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (step.onNext) {
|
|
183
|
+
const canProceed = await step.onNext();
|
|
184
|
+
if (!canProceed) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (isLastStep) {
|
|
189
|
+
onComplete?.();
|
|
190
|
+
} else {
|
|
191
|
+
const nextStep = currentStep + 1;
|
|
192
|
+
stepValidationRef.current = null;
|
|
193
|
+
setCurrentStep(nextStep);
|
|
194
|
+
onStepChange?.({
|
|
195
|
+
stepIndex: nextStep
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}, [currentStep, steps, isLastStep, onComplete, onStepChange]);
|
|
199
|
+
const goToPrevious = React3.useCallback(() => {
|
|
200
|
+
if (!isFirstStep) {
|
|
201
|
+
const prevStep = currentStep - 1;
|
|
202
|
+
stepValidationRef.current = null;
|
|
203
|
+
setCurrentStep(prevStep);
|
|
204
|
+
onStepChange?.({
|
|
205
|
+
stepIndex: prevStep
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}, [currentStep, isFirstStep, onStepChange]);
|
|
209
|
+
const goToStep = React3.useCallback(({
|
|
210
|
+
stepIndex
|
|
211
|
+
}) => {
|
|
212
|
+
if (stepIndex >= 0 && stepIndex < totalSteps && stepIndex <= currentStep) {
|
|
213
|
+
stepValidationRef.current = null;
|
|
214
|
+
setCurrentStep(stepIndex);
|
|
215
|
+
onStepChange?.({
|
|
216
|
+
stepIndex
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}, [currentStep, totalSteps, onStepChange]);
|
|
220
|
+
const setStepValidation = React3.useCallback(validate => {
|
|
221
|
+
stepValidationRef.current = validate;
|
|
222
|
+
}, []);
|
|
223
|
+
const contextValue = React3.useMemo(() => {
|
|
224
|
+
return {
|
|
225
|
+
currentStep,
|
|
226
|
+
totalSteps,
|
|
227
|
+
goToNext,
|
|
228
|
+
goToPrevious,
|
|
229
|
+
goToStep,
|
|
230
|
+
isFirstStep,
|
|
231
|
+
isLastStep,
|
|
232
|
+
getStepStatus,
|
|
233
|
+
setStepValidation
|
|
234
|
+
};
|
|
235
|
+
}, [currentStep, totalSteps, goToNext, goToPrevious, goToStep, isFirstStep, isLastStep, getStepStatus, setStepValidation]);
|
|
236
|
+
return /* @__PURE__ */React3.createElement(WizardContext.Provider, {
|
|
237
|
+
value: contextValue
|
|
238
|
+
}, /* @__PURE__ */React3.createElement(Flex2, {
|
|
239
|
+
sx: {
|
|
240
|
+
flexDirection: getFlexDirection(layout),
|
|
241
|
+
width: "100%",
|
|
242
|
+
minHeight: "300px",
|
|
243
|
+
border: "1px solid",
|
|
244
|
+
borderColor: "display.border.muted.default",
|
|
245
|
+
borderRadius: "8px",
|
|
246
|
+
overflow: "hidden"
|
|
247
|
+
}
|
|
248
|
+
}, /* @__PURE__ */React3.createElement(WizardStepList, {
|
|
249
|
+
steps,
|
|
250
|
+
currentStep,
|
|
251
|
+
layout,
|
|
252
|
+
allowStepClick,
|
|
253
|
+
getStepStatus,
|
|
254
|
+
onStepClick: goToStep
|
|
255
|
+
}), /* @__PURE__ */React3.createElement(Flex2, {
|
|
256
|
+
sx: {
|
|
257
|
+
flexDirection: "column",
|
|
258
|
+
flex: 1,
|
|
259
|
+
padding: "6"
|
|
260
|
+
}
|
|
261
|
+
}, /* @__PURE__ */React3.createElement(Box2, {
|
|
262
|
+
sx: {
|
|
263
|
+
flex: 1,
|
|
264
|
+
marginBottom: "4"
|
|
265
|
+
}
|
|
266
|
+
}, steps[currentStep].content), /* @__PURE__ */React3.createElement(Flex2, {
|
|
267
|
+
sx: {
|
|
268
|
+
justifyContent: "space-between",
|
|
269
|
+
alignItems: "center",
|
|
270
|
+
gap: "3"
|
|
271
|
+
}
|
|
272
|
+
}, /* @__PURE__ */React3.createElement(Flex2, {
|
|
273
|
+
sx: {
|
|
274
|
+
gap: "3"
|
|
275
|
+
}
|
|
276
|
+
}, onCancel && /* @__PURE__ */React3.createElement(Button, {
|
|
277
|
+
variant: "secondary",
|
|
278
|
+
onClick: onCancel,
|
|
279
|
+
"aria-label": intl.formatMessage(messages.cancel)
|
|
280
|
+
}, intl.formatMessage(messages.cancel))), /* @__PURE__ */React3.createElement(Flex2, {
|
|
281
|
+
sx: {
|
|
282
|
+
gap: "3"
|
|
283
|
+
}
|
|
284
|
+
}, /* @__PURE__ */React3.createElement(Button, {
|
|
285
|
+
variant: "secondary",
|
|
286
|
+
onClick: goToPrevious,
|
|
287
|
+
disabled: isFirstStep,
|
|
288
|
+
"aria-label": intl.formatMessage(messages.previous)
|
|
289
|
+
}, intl.formatMessage(messages.previous)), /* @__PURE__ */React3.createElement(Button, {
|
|
290
|
+
onClick: goToNext,
|
|
291
|
+
"aria-label": isLastStep ? intl.formatMessage(messages.finish) : intl.formatMessage(messages.next)
|
|
292
|
+
}, isLastStep ? intl.formatMessage(messages.finish) : intl.formatMessage(messages.next)))))));
|
|
293
|
+
}, "Wizard");
|
|
294
|
+
Wizard.displayName = "Wizard";
|
|
295
|
+
export { Wizard, useWizard };
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Position of the step list relative to the wizard content.
|
|
6
|
+
*/
|
|
7
|
+
type WizardLayout = 'top' | 'right' | 'bottom' | 'left';
|
|
8
|
+
/**
|
|
9
|
+
* Status of a wizard step.
|
|
10
|
+
*/
|
|
11
|
+
type WizardStepStatus = 'completed' | 'active' | 'upcoming';
|
|
12
|
+
/**
|
|
13
|
+
* Definition of a single wizard step.
|
|
14
|
+
*/
|
|
15
|
+
interface WizardStep {
|
|
16
|
+
/**
|
|
17
|
+
* Title displayed in the step list.
|
|
18
|
+
*/
|
|
19
|
+
title: string;
|
|
20
|
+
/**
|
|
21
|
+
* Optional description displayed below the title in the step list.
|
|
22
|
+
*/
|
|
23
|
+
description?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The content rendered when this step is active.
|
|
26
|
+
*/
|
|
27
|
+
content: React.ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Called when the user clicks "Next". If it returns false or
|
|
30
|
+
* a Promise that resolves to false, the wizard will not advance.
|
|
31
|
+
*/
|
|
32
|
+
onNext?: () => boolean | Promise<boolean>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Props for the Wizard component.
|
|
36
|
+
*/
|
|
37
|
+
interface WizardProps {
|
|
38
|
+
/**
|
|
39
|
+
* Array of steps to display in the wizard.
|
|
40
|
+
*/
|
|
41
|
+
steps: WizardStep[];
|
|
42
|
+
/**
|
|
43
|
+
* Position of the step list. Defaults to 'top'.
|
|
44
|
+
*/
|
|
45
|
+
layout?: WizardLayout;
|
|
46
|
+
/**
|
|
47
|
+
* Called when the wizard completes (user clicks "Finish" on the last step).
|
|
48
|
+
*/
|
|
49
|
+
onComplete?: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Called when the user clicks "Cancel".
|
|
52
|
+
* If not provided, the Cancel button is not rendered.
|
|
53
|
+
*/
|
|
54
|
+
onCancel?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Called whenever the active step changes.
|
|
57
|
+
*/
|
|
58
|
+
onStepChange?: (params: {
|
|
59
|
+
stepIndex: number;
|
|
60
|
+
}) => void;
|
|
61
|
+
/**
|
|
62
|
+
* The initially active step index. Defaults to 0.
|
|
63
|
+
*/
|
|
64
|
+
initialStep?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Allow clicking on completed steps to navigate back.
|
|
67
|
+
* Defaults to true.
|
|
68
|
+
*/
|
|
69
|
+
allowStepClick?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Context value exposed by WizardProvider.
|
|
73
|
+
*/
|
|
74
|
+
interface WizardContextValue {
|
|
75
|
+
/**
|
|
76
|
+
* Current active step index.
|
|
77
|
+
*/
|
|
78
|
+
currentStep: number;
|
|
79
|
+
/**
|
|
80
|
+
* Total number of steps.
|
|
81
|
+
*/
|
|
82
|
+
totalSteps: number;
|
|
83
|
+
/**
|
|
84
|
+
* Navigate to the next step.
|
|
85
|
+
*/
|
|
86
|
+
goToNext: () => Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Navigate to the previous step.
|
|
89
|
+
*/
|
|
90
|
+
goToPrevious: () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Navigate to a specific step (only if it's completed or active).
|
|
93
|
+
*/
|
|
94
|
+
goToStep: (params: {
|
|
95
|
+
stepIndex: number;
|
|
96
|
+
}) => void;
|
|
97
|
+
/**
|
|
98
|
+
* Whether the wizard is on the first step.
|
|
99
|
+
*/
|
|
100
|
+
isFirstStep: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Whether the wizard is on the last step.
|
|
103
|
+
*/
|
|
104
|
+
isLastStep: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Get the status of a step by index.
|
|
107
|
+
*/
|
|
108
|
+
getStepStatus: (params: {
|
|
109
|
+
stepIndex: number;
|
|
110
|
+
}) => WizardStepStatus;
|
|
111
|
+
/**
|
|
112
|
+
* Register a validation function for the current step.
|
|
113
|
+
* This allows step content components to provide their own validation logic.
|
|
114
|
+
*/
|
|
115
|
+
setStepValidation: (validate: () => boolean | Promise<boolean>) => void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
declare const Wizard: {
|
|
119
|
+
({ steps, layout, onComplete, onCancel, onStepChange, initialStep, allowStepClick, }: WizardProps): react_jsx_runtime.JSX.Element;
|
|
120
|
+
displayName: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Hook to access the wizard context from within step content.
|
|
125
|
+
*/
|
|
126
|
+
declare const useWizard: () => WizardContextValue;
|
|
127
|
+
|
|
128
|
+
export { Wizard, type WizardContextValue, type WizardLayout, type WizardProps, type WizardStep, type WizardStepStatus, useWizard };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Position of the step list relative to the wizard content.
|
|
6
|
+
*/
|
|
7
|
+
type WizardLayout = 'top' | 'right' | 'bottom' | 'left';
|
|
8
|
+
/**
|
|
9
|
+
* Status of a wizard step.
|
|
10
|
+
*/
|
|
11
|
+
type WizardStepStatus = 'completed' | 'active' | 'upcoming';
|
|
12
|
+
/**
|
|
13
|
+
* Definition of a single wizard step.
|
|
14
|
+
*/
|
|
15
|
+
interface WizardStep {
|
|
16
|
+
/**
|
|
17
|
+
* Title displayed in the step list.
|
|
18
|
+
*/
|
|
19
|
+
title: string;
|
|
20
|
+
/**
|
|
21
|
+
* Optional description displayed below the title in the step list.
|
|
22
|
+
*/
|
|
23
|
+
description?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The content rendered when this step is active.
|
|
26
|
+
*/
|
|
27
|
+
content: React.ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Called when the user clicks "Next". If it returns false or
|
|
30
|
+
* a Promise that resolves to false, the wizard will not advance.
|
|
31
|
+
*/
|
|
32
|
+
onNext?: () => boolean | Promise<boolean>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Props for the Wizard component.
|
|
36
|
+
*/
|
|
37
|
+
interface WizardProps {
|
|
38
|
+
/**
|
|
39
|
+
* Array of steps to display in the wizard.
|
|
40
|
+
*/
|
|
41
|
+
steps: WizardStep[];
|
|
42
|
+
/**
|
|
43
|
+
* Position of the step list. Defaults to 'top'.
|
|
44
|
+
*/
|
|
45
|
+
layout?: WizardLayout;
|
|
46
|
+
/**
|
|
47
|
+
* Called when the wizard completes (user clicks "Finish" on the last step).
|
|
48
|
+
*/
|
|
49
|
+
onComplete?: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Called when the user clicks "Cancel".
|
|
52
|
+
* If not provided, the Cancel button is not rendered.
|
|
53
|
+
*/
|
|
54
|
+
onCancel?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Called whenever the active step changes.
|
|
57
|
+
*/
|
|
58
|
+
onStepChange?: (params: {
|
|
59
|
+
stepIndex: number;
|
|
60
|
+
}) => void;
|
|
61
|
+
/**
|
|
62
|
+
* The initially active step index. Defaults to 0.
|
|
63
|
+
*/
|
|
64
|
+
initialStep?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Allow clicking on completed steps to navigate back.
|
|
67
|
+
* Defaults to true.
|
|
68
|
+
*/
|
|
69
|
+
allowStepClick?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Context value exposed by WizardProvider.
|
|
73
|
+
*/
|
|
74
|
+
interface WizardContextValue {
|
|
75
|
+
/**
|
|
76
|
+
* Current active step index.
|
|
77
|
+
*/
|
|
78
|
+
currentStep: number;
|
|
79
|
+
/**
|
|
80
|
+
* Total number of steps.
|
|
81
|
+
*/
|
|
82
|
+
totalSteps: number;
|
|
83
|
+
/**
|
|
84
|
+
* Navigate to the next step.
|
|
85
|
+
*/
|
|
86
|
+
goToNext: () => Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Navigate to the previous step.
|
|
89
|
+
*/
|
|
90
|
+
goToPrevious: () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Navigate to a specific step (only if it's completed or active).
|
|
93
|
+
*/
|
|
94
|
+
goToStep: (params: {
|
|
95
|
+
stepIndex: number;
|
|
96
|
+
}) => void;
|
|
97
|
+
/**
|
|
98
|
+
* Whether the wizard is on the first step.
|
|
99
|
+
*/
|
|
100
|
+
isFirstStep: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Whether the wizard is on the last step.
|
|
103
|
+
*/
|
|
104
|
+
isLastStep: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Get the status of a step by index.
|
|
107
|
+
*/
|
|
108
|
+
getStepStatus: (params: {
|
|
109
|
+
stepIndex: number;
|
|
110
|
+
}) => WizardStepStatus;
|
|
111
|
+
/**
|
|
112
|
+
* Register a validation function for the current step.
|
|
113
|
+
* This allows step content components to provide their own validation logic.
|
|
114
|
+
*/
|
|
115
|
+
setStepValidation: (validate: () => boolean | Promise<boolean>) => void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
declare const Wizard: {
|
|
119
|
+
({ steps, layout, onComplete, onCancel, onStepChange, initialStep, allowStepClick, }: WizardProps): react_jsx_runtime.JSX.Element;
|
|
120
|
+
displayName: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Hook to access the wizard context from within step content.
|
|
125
|
+
*/
|
|
126
|
+
declare const useWizard: () => WizardContextValue;
|
|
127
|
+
|
|
128
|
+
export { Wizard, type WizardContextValue, type WizardLayout, type WizardProps, type WizardStep, type WizardStepStatus, useWizard };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
var __create = Object.create;
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
12
|
+
value,
|
|
13
|
+
configurable: true
|
|
14
|
+
});
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all) __defProp(target, name, {
|
|
17
|
+
get: all[name],
|
|
18
|
+
enumerable: true
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
24
|
+
get: () => from[key],
|
|
25
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return to;
|
|
29
|
+
};
|
|
30
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
31
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
32
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
33
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
34
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
35
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
36
|
+
value: mod,
|
|
37
|
+
enumerable: true
|
|
38
|
+
}) : target, mod));
|
|
39
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
40
|
+
value: true
|
|
41
|
+
}), mod);
|
|
42
|
+
|
|
43
|
+
// src/index.ts
|
|
44
|
+
var index_exports = {};
|
|
45
|
+
__export(index_exports, {
|
|
46
|
+
Wizard: () => Wizard,
|
|
47
|
+
useWizard: () => useWizard
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
|
|
51
|
+
// src/Wizard.tsx
|
|
52
|
+
var import_react_i18n = require("@ttoss/react-i18n");
|
|
53
|
+
var import_ui2 = require("@ttoss/ui");
|
|
54
|
+
var React3 = __toESM(require("react"), 1);
|
|
55
|
+
|
|
56
|
+
// src/WizardContext.ts
|
|
57
|
+
var React2 = __toESM(require("react"), 1);
|
|
58
|
+
var WizardContext = React2.createContext(null);
|
|
59
|
+
var useWizard = /* @__PURE__ */__name(() => {
|
|
60
|
+
const context = React2.useContext(WizardContext);
|
|
61
|
+
if (!context) {
|
|
62
|
+
throw new Error("useWizard must be used within a Wizard component.");
|
|
63
|
+
}
|
|
64
|
+
return context;
|
|
65
|
+
}, "useWizard");
|
|
66
|
+
|
|
67
|
+
// src/WizardStepList.tsx
|
|
68
|
+
var import_react = require("@chakra-ui/react");
|
|
69
|
+
var import_ui = require("@ttoss/ui");
|
|
70
|
+
var WizardStepList = /* @__PURE__ */__name(({
|
|
71
|
+
steps,
|
|
72
|
+
currentStep,
|
|
73
|
+
layout,
|
|
74
|
+
allowStepClick,
|
|
75
|
+
getStepStatus,
|
|
76
|
+
onStepClick
|
|
77
|
+
}) => {
|
|
78
|
+
const isHorizontal = layout === "top" || layout === "bottom";
|
|
79
|
+
const orientation = isHorizontal ? "horizontal" : "vertical";
|
|
80
|
+
return /* @__PURE__ */React.createElement(import_ui.Flex, {
|
|
81
|
+
role: "navigation",
|
|
82
|
+
"aria-label": "Wizard steps",
|
|
83
|
+
sx: {
|
|
84
|
+
padding: "6",
|
|
85
|
+
backgroundColor: "navigation.background.muted.default",
|
|
86
|
+
...(isHorizontal ? {
|
|
87
|
+
width: "100%",
|
|
88
|
+
borderBottom: layout === "top" ? "1px solid" : void 0,
|
|
89
|
+
borderTop: layout === "bottom" ? "1px solid" : void 0,
|
|
90
|
+
borderColor: "display.border.muted.default"
|
|
91
|
+
} : {
|
|
92
|
+
minWidth: "200px",
|
|
93
|
+
borderRight: layout === "left" ? "1px solid" : void 0,
|
|
94
|
+
borderLeft: layout === "right" ? "1px solid" : void 0,
|
|
95
|
+
borderColor: "display.border.muted.default"
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}, /* @__PURE__ */React.createElement(import_react.Steps.Root, {
|
|
99
|
+
step: currentStep,
|
|
100
|
+
count: steps.length,
|
|
101
|
+
orientation
|
|
102
|
+
}, /* @__PURE__ */React.createElement(import_react.Steps.List, null, steps.map((step, index) => {
|
|
103
|
+
const status = getStepStatus({
|
|
104
|
+
stepIndex: index
|
|
105
|
+
});
|
|
106
|
+
const isClickable = allowStepClick && status === "completed" && index !== currentStep;
|
|
107
|
+
return /* @__PURE__ */React.createElement(import_react.Steps.Item, {
|
|
108
|
+
key: index,
|
|
109
|
+
index
|
|
110
|
+
}, /* @__PURE__ */React.createElement(import_ui.Flex, {
|
|
111
|
+
sx: {
|
|
112
|
+
flexDirection: isHorizontal ? "column" : "row",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
gap: "2"
|
|
115
|
+
}
|
|
116
|
+
}, /* @__PURE__ */React.createElement(import_ui.Box, {
|
|
117
|
+
role: "button",
|
|
118
|
+
tabIndex: isClickable ? 0 : -1,
|
|
119
|
+
onClick: /* @__PURE__ */__name(() => {
|
|
120
|
+
if (isClickable) {
|
|
121
|
+
onStepClick({
|
|
122
|
+
stepIndex: index
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}, "onClick"),
|
|
126
|
+
onKeyDown: /* @__PURE__ */__name(e => {
|
|
127
|
+
if (isClickable && (e.key === "Enter" || e.key === " ")) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
onStepClick({
|
|
130
|
+
stepIndex: index
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}, "onKeyDown"),
|
|
134
|
+
"aria-current": status === "active" ? "step" : void 0,
|
|
135
|
+
sx: {
|
|
136
|
+
cursor: isClickable ? "pointer" : "default"
|
|
137
|
+
}
|
|
138
|
+
}, /* @__PURE__ */React.createElement(import_react.Steps.Indicator, null, /* @__PURE__ */React.createElement(import_react.Steps.Status, {
|
|
139
|
+
complete: "\u2713",
|
|
140
|
+
incomplete: /* @__PURE__ */React.createElement(import_react.Steps.Number, null)
|
|
141
|
+
}))), (step.title || step.description) && /* @__PURE__ */React.createElement(import_ui.Box, null, step.title && /* @__PURE__ */React.createElement(import_react.Steps.Title, null, step.title), step.description && /* @__PURE__ */React.createElement(import_react.Steps.Description, null, step.description))), index < steps.length - 1 && /* @__PURE__ */React.createElement(import_react.Steps.Separator, null));
|
|
142
|
+
}))));
|
|
143
|
+
}, "WizardStepList");
|
|
144
|
+
|
|
145
|
+
// src/Wizard.tsx
|
|
146
|
+
var messages = (0, import_react_i18n.defineMessages)({
|
|
147
|
+
previous: {
|
|
148
|
+
defaultMessage: "Previous",
|
|
149
|
+
description: "Label for the previous step button in the wizard."
|
|
150
|
+
},
|
|
151
|
+
next: {
|
|
152
|
+
defaultMessage: "Next",
|
|
153
|
+
description: "Label for the next step button in the wizard."
|
|
154
|
+
},
|
|
155
|
+
finish: {
|
|
156
|
+
defaultMessage: "Finish",
|
|
157
|
+
description: "Label for the finish button on the last wizard step."
|
|
158
|
+
},
|
|
159
|
+
cancel: {
|
|
160
|
+
defaultMessage: "Cancel",
|
|
161
|
+
description: "Label for the cancel button in the wizard."
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
var getFlexDirection = /* @__PURE__ */__name(layout => {
|
|
165
|
+
switch (layout) {
|
|
166
|
+
case "top":
|
|
167
|
+
return "column";
|
|
168
|
+
case "bottom":
|
|
169
|
+
return "column-reverse";
|
|
170
|
+
case "left":
|
|
171
|
+
return "row";
|
|
172
|
+
case "right":
|
|
173
|
+
return "row-reverse";
|
|
174
|
+
}
|
|
175
|
+
}, "getFlexDirection");
|
|
176
|
+
var Wizard = /* @__PURE__ */__name(({
|
|
177
|
+
steps,
|
|
178
|
+
layout = "top",
|
|
179
|
+
onComplete,
|
|
180
|
+
onCancel,
|
|
181
|
+
onStepChange,
|
|
182
|
+
initialStep = 0,
|
|
183
|
+
allowStepClick = true
|
|
184
|
+
}) => {
|
|
185
|
+
const {
|
|
186
|
+
intl
|
|
187
|
+
} = (0, import_react_i18n.useI18n)();
|
|
188
|
+
const [currentStep, setCurrentStep] = React3.useState(initialStep);
|
|
189
|
+
const stepValidationRef = React3.useRef(null);
|
|
190
|
+
const totalSteps = steps.length;
|
|
191
|
+
const isFirstStep = currentStep === 0;
|
|
192
|
+
const isLastStep = currentStep === totalSteps - 1;
|
|
193
|
+
const getStepStatus = React3.useCallback(({
|
|
194
|
+
stepIndex
|
|
195
|
+
}) => {
|
|
196
|
+
if (stepIndex < currentStep) {
|
|
197
|
+
return "completed";
|
|
198
|
+
}
|
|
199
|
+
if (stepIndex === currentStep) {
|
|
200
|
+
return "active";
|
|
201
|
+
}
|
|
202
|
+
return "upcoming";
|
|
203
|
+
}, [currentStep]);
|
|
204
|
+
const goToNext = React3.useCallback(async () => {
|
|
205
|
+
const step = steps[currentStep];
|
|
206
|
+
if (stepValidationRef.current) {
|
|
207
|
+
const canProceed = await stepValidationRef.current();
|
|
208
|
+
if (!canProceed) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (step.onNext) {
|
|
213
|
+
const canProceed = await step.onNext();
|
|
214
|
+
if (!canProceed) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (isLastStep) {
|
|
219
|
+
onComplete?.();
|
|
220
|
+
} else {
|
|
221
|
+
const nextStep = currentStep + 1;
|
|
222
|
+
stepValidationRef.current = null;
|
|
223
|
+
setCurrentStep(nextStep);
|
|
224
|
+
onStepChange?.({
|
|
225
|
+
stepIndex: nextStep
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}, [currentStep, steps, isLastStep, onComplete, onStepChange]);
|
|
229
|
+
const goToPrevious = React3.useCallback(() => {
|
|
230
|
+
if (!isFirstStep) {
|
|
231
|
+
const prevStep = currentStep - 1;
|
|
232
|
+
stepValidationRef.current = null;
|
|
233
|
+
setCurrentStep(prevStep);
|
|
234
|
+
onStepChange?.({
|
|
235
|
+
stepIndex: prevStep
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}, [currentStep, isFirstStep, onStepChange]);
|
|
239
|
+
const goToStep = React3.useCallback(({
|
|
240
|
+
stepIndex
|
|
241
|
+
}) => {
|
|
242
|
+
if (stepIndex >= 0 && stepIndex < totalSteps && stepIndex <= currentStep) {
|
|
243
|
+
stepValidationRef.current = null;
|
|
244
|
+
setCurrentStep(stepIndex);
|
|
245
|
+
onStepChange?.({
|
|
246
|
+
stepIndex
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}, [currentStep, totalSteps, onStepChange]);
|
|
250
|
+
const setStepValidation = React3.useCallback(validate => {
|
|
251
|
+
stepValidationRef.current = validate;
|
|
252
|
+
}, []);
|
|
253
|
+
const contextValue = React3.useMemo(() => {
|
|
254
|
+
return {
|
|
255
|
+
currentStep,
|
|
256
|
+
totalSteps,
|
|
257
|
+
goToNext,
|
|
258
|
+
goToPrevious,
|
|
259
|
+
goToStep,
|
|
260
|
+
isFirstStep,
|
|
261
|
+
isLastStep,
|
|
262
|
+
getStepStatus,
|
|
263
|
+
setStepValidation
|
|
264
|
+
};
|
|
265
|
+
}, [currentStep, totalSteps, goToNext, goToPrevious, goToStep, isFirstStep, isLastStep, getStepStatus, setStepValidation]);
|
|
266
|
+
return /* @__PURE__ */React3.createElement(WizardContext.Provider, {
|
|
267
|
+
value: contextValue
|
|
268
|
+
}, /* @__PURE__ */React3.createElement(import_ui2.Flex, {
|
|
269
|
+
sx: {
|
|
270
|
+
flexDirection: getFlexDirection(layout),
|
|
271
|
+
width: "100%",
|
|
272
|
+
minHeight: "300px",
|
|
273
|
+
border: "1px solid",
|
|
274
|
+
borderColor: "display.border.muted.default",
|
|
275
|
+
borderRadius: "8px",
|
|
276
|
+
overflow: "hidden"
|
|
277
|
+
}
|
|
278
|
+
}, /* @__PURE__ */React3.createElement(WizardStepList, {
|
|
279
|
+
steps,
|
|
280
|
+
currentStep,
|
|
281
|
+
layout,
|
|
282
|
+
allowStepClick,
|
|
283
|
+
getStepStatus,
|
|
284
|
+
onStepClick: goToStep
|
|
285
|
+
}), /* @__PURE__ */React3.createElement(import_ui2.Flex, {
|
|
286
|
+
sx: {
|
|
287
|
+
flexDirection: "column",
|
|
288
|
+
flex: 1,
|
|
289
|
+
padding: "6"
|
|
290
|
+
}
|
|
291
|
+
}, /* @__PURE__ */React3.createElement(import_ui2.Box, {
|
|
292
|
+
sx: {
|
|
293
|
+
flex: 1,
|
|
294
|
+
marginBottom: "4"
|
|
295
|
+
}
|
|
296
|
+
}, steps[currentStep].content), /* @__PURE__ */React3.createElement(import_ui2.Flex, {
|
|
297
|
+
sx: {
|
|
298
|
+
justifyContent: "space-between",
|
|
299
|
+
alignItems: "center",
|
|
300
|
+
gap: "3"
|
|
301
|
+
}
|
|
302
|
+
}, /* @__PURE__ */React3.createElement(import_ui2.Flex, {
|
|
303
|
+
sx: {
|
|
304
|
+
gap: "3"
|
|
305
|
+
}
|
|
306
|
+
}, onCancel && /* @__PURE__ */React3.createElement(import_ui2.Button, {
|
|
307
|
+
variant: "secondary",
|
|
308
|
+
onClick: onCancel,
|
|
309
|
+
"aria-label": intl.formatMessage(messages.cancel)
|
|
310
|
+
}, intl.formatMessage(messages.cancel))), /* @__PURE__ */React3.createElement(import_ui2.Flex, {
|
|
311
|
+
sx: {
|
|
312
|
+
gap: "3"
|
|
313
|
+
}
|
|
314
|
+
}, /* @__PURE__ */React3.createElement(import_ui2.Button, {
|
|
315
|
+
variant: "secondary",
|
|
316
|
+
onClick: goToPrevious,
|
|
317
|
+
disabled: isFirstStep,
|
|
318
|
+
"aria-label": intl.formatMessage(messages.previous)
|
|
319
|
+
}, intl.formatMessage(messages.previous)), /* @__PURE__ */React3.createElement(import_ui2.Button, {
|
|
320
|
+
onClick: goToNext,
|
|
321
|
+
"aria-label": isLastStep ? intl.formatMessage(messages.finish) : intl.formatMessage(messages.next)
|
|
322
|
+
}, isLastStep ? intl.formatMessage(messages.finish) : intl.formatMessage(messages.next)))))));
|
|
323
|
+
}, "Wizard");
|
|
324
|
+
Wizard.displayName = "Wizard";
|
|
325
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
326
|
+
0 && (module.exports = {
|
|
327
|
+
Wizard,
|
|
328
|
+
useWizard
|
|
329
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ttoss/react-wizard",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A React wizard component for guiding users through multi-step flows with configurable step list layouts.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "ttoss",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Pedro Arantes <pedro@arantespp.com> (https://arantespp.com/contact)"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/ttoss/ttoss.git",
|
|
13
|
+
"directory": "packages/react-wizard"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/esm/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@chakra-ui/react": "^3",
|
|
28
|
+
"react": ">=16.8.0",
|
|
29
|
+
"react-icons": "^5",
|
|
30
|
+
"@ttoss/react-i18n": "^2.1.0",
|
|
31
|
+
"@ttoss/ui": "^6.6.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/jest": "^30.0.0",
|
|
35
|
+
"@types/react": "^19.2.14",
|
|
36
|
+
"jest": "^30.2.0",
|
|
37
|
+
"react": "^19.2.4",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"@ttoss/i18n-cli": "^0.7.39",
|
|
40
|
+
"@ttoss/config": "^1.36.0",
|
|
41
|
+
"@ttoss/forms": "^0.41.0",
|
|
42
|
+
"@ttoss/test-utils": "^4.1.0",
|
|
43
|
+
"@ttoss/ui": "^6.6.0",
|
|
44
|
+
"@ttoss/react-i18n": "^2.1.0"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"React",
|
|
48
|
+
"multi-step",
|
|
49
|
+
"stepper",
|
|
50
|
+
"ui",
|
|
51
|
+
"wizard"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"provenance": true
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"i18n": "ttoss-i18n",
|
|
60
|
+
"test": "jest --projects tests/unit"
|
|
61
|
+
}
|
|
62
|
+
}
|