@wizzard-packages/react 0.1.0 → 0.3.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 +1 -1
- package/README.md +354 -0
- package/dist/index.cjs +573 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -17
- package/dist/index.d.ts +127 -17
- package/dist/index.js +566 -88
- package/dist/index.js.map +1 -1
- package/package.json +14 -4
package/LICENSE
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# @wizzard-packages/react
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
React bindings for Wizzard Stepper: provider, hooks, and typed factory built on top of @wizzard-packages/core.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @wizzard-packages/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Optional add-ons:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @wizzard-packages/persistence @wizzard-packages/middleware
|
|
19
|
+
pnpm add @wizzard-packages/adapter-zod zod
|
|
20
|
+
pnpm add @wizzard-packages/adapter-yup yup
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { createWizardFactory } from '@wizzard-packages/react';
|
|
27
|
+
|
|
28
|
+
type Data = { name: string };
|
|
29
|
+
|
|
30
|
+
type StepId = 'name' | 'review';
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
WizardProvider,
|
|
34
|
+
createStep,
|
|
35
|
+
useWizardActions,
|
|
36
|
+
useWizardState,
|
|
37
|
+
} = createWizardFactory<Data, StepId>();
|
|
38
|
+
|
|
39
|
+
const steps = [
|
|
40
|
+
createStep({ id: 'name', label: 'Name', component: NameStep }),
|
|
41
|
+
createStep({ id: 'review', label: 'Review', component: ReviewStep }),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export function App() {
|
|
45
|
+
return (
|
|
46
|
+
<WizardProvider
|
|
47
|
+
config={{ steps }}
|
|
48
|
+
initialData={{ name: '' }}
|
|
49
|
+
initialStepId="name"
|
|
50
|
+
>
|
|
51
|
+
<WizardUI />
|
|
52
|
+
</WizardProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function WizardUI() {
|
|
57
|
+
const { goToNextStep } = useWizardActions();
|
|
58
|
+
const { currentStepId } = useWizardState();
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<button onClick={goToNextStep}>Next ({currentStepId})</button>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Best practices (DX)
|
|
67
|
+
|
|
68
|
+
- Memoize `steps` and `config` (`useMemo`) to avoid unnecessary recalculations.
|
|
69
|
+
- Prefer granular hooks (`useWizardValue`, `useWizardSelector`, `useWizardMeta`) over `useWizardContext` for performance.
|
|
70
|
+
- For form fields, use `useWizardField(path)` to get `[value, setValue]` without extra boilerplate.
|
|
71
|
+
- SSR is supported via `useSyncExternalStore`. Avoid persistence adapters that touch `window` during module init.
|
|
72
|
+
|
|
73
|
+
## Context-free store (without Provider)
|
|
74
|
+
|
|
75
|
+
If you need store access without React Context (common in large codebases), the API surface is the same.
|
|
76
|
+
You get the same `actions` as `useWizardActions`, and the hooks mirror `useWizardState`,
|
|
77
|
+
`useWizardValue`, and `useWizardSelector`.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { createWizardStore, createWizardHooks } from '@wizzard-packages/react';
|
|
81
|
+
|
|
82
|
+
type Data = { name: string };
|
|
83
|
+
type StepId = 'name' | 'review';
|
|
84
|
+
|
|
85
|
+
const steps = [
|
|
86
|
+
{ id: 'name', label: 'Name', component: NameStep },
|
|
87
|
+
{ id: 'review', label: 'Review', component: ReviewStep },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const { store, actions } = createWizardStore<Data, StepId>({
|
|
91
|
+
config: { steps },
|
|
92
|
+
initialData: { name: '' },
|
|
93
|
+
initialStepId: 'name',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const { useWizardState, useWizardValue, useWizardField } = createWizardHooks(store, actions);
|
|
97
|
+
|
|
98
|
+
function WizardUI() {
|
|
99
|
+
const state = useWizardState();
|
|
100
|
+
const name = useWizardValue('name');
|
|
101
|
+
const [nameField, setNameField] = useWizardField('name');
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<button onClick={actions.goToNextStep}>
|
|
105
|
+
Next ({state.currentStepId}) {name} {nameField}
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Example: selector + derived UI
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
const { useWizardSelector } = createWizardHooks(store, actions);
|
|
115
|
+
|
|
116
|
+
function Progress() {
|
|
117
|
+
const { progress, isBusy } = useWizardSelector((s) => ({
|
|
118
|
+
progress: s.progress,
|
|
119
|
+
isBusy: s.isBusy,
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
return <div>Progress: {progress}% {isBusy ? '...' : ''}</div>;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Example: actions outside React (service layer)
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// services/wizardActions.ts
|
|
130
|
+
export const wizard = createWizardStore<Data, StepId>({
|
|
131
|
+
config: { steps },
|
|
132
|
+
initialData: { name: '' },
|
|
133
|
+
initialStepId: 'name',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const wizardActions = wizard.actions;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { wizardActions } from './services/wizardActions';
|
|
141
|
+
|
|
142
|
+
function Footer() {
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<button onClick={wizardActions.goToPrevStep}>Back</button>
|
|
146
|
+
<button onClick={wizardActions.goToNextStep}>Next</button>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Example: multiple stores (independent wizards)
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
const wizardA = createWizardStore<DataA, StepIdA>({ config: { steps: stepsA } });
|
|
156
|
+
const wizardB = createWizardStore<DataB, StepIdB>({ config: { steps: stepsB } });
|
|
157
|
+
|
|
158
|
+
const hooksA = createWizardHooks(wizardA.store);
|
|
159
|
+
const hooksB = createWizardHooks(wizardB.store);
|
|
160
|
+
|
|
161
|
+
function DualWizard() {
|
|
162
|
+
const stepA = hooksA.useWizardState().currentStepId;
|
|
163
|
+
const stepB = hooksB.useWizardState().currentStepId;
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div>
|
|
167
|
+
<div>Wizard A: {stepA}</div>
|
|
168
|
+
<div>Wizard B: {stepB}</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Example: SSR-friendly initialization
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
// create store outside render to keep it stable across SSR/CSR.
|
|
178
|
+
const wizard = createWizardStore<Data, StepId>({
|
|
179
|
+
config: { steps },
|
|
180
|
+
initialData: { name: '' },
|
|
181
|
+
initialStepId: 'name',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const hooks = createWizardHooks(wizard.store);
|
|
185
|
+
|
|
186
|
+
function WizardApp() {
|
|
187
|
+
const { currentStepId } = hooks.useWizardState();
|
|
188
|
+
return <div>Current step: {currentStepId}</div>;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Example: integration with external state manager
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
// Example using a simple external event bus pattern.
|
|
196
|
+
type Listener = () => void;
|
|
197
|
+
const listeners = new Set<Listener>();
|
|
198
|
+
|
|
199
|
+
wizard.store.subscribe(() => {
|
|
200
|
+
listeners.forEach((listener) => listener());
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export const wizardStateStore = {
|
|
204
|
+
subscribe(listener: Listener) {
|
|
205
|
+
listeners.add(listener);
|
|
206
|
+
return () => listeners.delete(listener);
|
|
207
|
+
},
|
|
208
|
+
getSnapshot() {
|
|
209
|
+
return wizard.store.getSnapshot();
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { useSyncExternalStore } from 'react';
|
|
216
|
+
|
|
217
|
+
function WizardBadge() {
|
|
218
|
+
const snapshot = useSyncExternalStore(
|
|
219
|
+
wizardStateStore.subscribe,
|
|
220
|
+
wizardStateStore.getSnapshot,
|
|
221
|
+
wizardStateStore.getSnapshot
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return <span>{snapshot.currentStepId}</span>;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Example: Zustand integration
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { create } from 'zustand';
|
|
232
|
+
import { shallow } from 'zustand/shallow';
|
|
233
|
+
|
|
234
|
+
type WizardSnapshot = ReturnType<typeof wizard.store.getSnapshot>;
|
|
235
|
+
|
|
236
|
+
export const useWizardSnapshot = create<{ snapshot: WizardSnapshot }>(() => ({
|
|
237
|
+
snapshot: wizard.store.getSnapshot(),
|
|
238
|
+
}));
|
|
239
|
+
|
|
240
|
+
wizard.store.subscribe(() => {
|
|
241
|
+
useWizardSnapshot.setState({ snapshot: wizard.store.getSnapshot() });
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
function WizardStatus() {
|
|
247
|
+
const { currentStepId, progress } = useWizardSnapshot(
|
|
248
|
+
(s) => ({
|
|
249
|
+
currentStepId: s.snapshot.currentStepId,
|
|
250
|
+
progress: s.snapshot.progress,
|
|
251
|
+
}),
|
|
252
|
+
shallow
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<div>
|
|
257
|
+
Step: {currentStepId} ({progress}%)
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Example: Redux integration
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
// Store current snapshot in Redux when the wizard updates.
|
|
267
|
+
import { createSlice, configureStore } from '@reduxjs/toolkit';
|
|
268
|
+
import { createSelector } from '@reduxjs/toolkit';
|
|
269
|
+
|
|
270
|
+
const wizardSlice = createSlice({
|
|
271
|
+
name: 'wizard',
|
|
272
|
+
initialState: { snapshot: wizard.store.getSnapshot() },
|
|
273
|
+
reducers: {
|
|
274
|
+
setSnapshot(state, action) {
|
|
275
|
+
state.snapshot = action.payload;
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
export const { setSnapshot } = wizardSlice.actions;
|
|
281
|
+
export const store = configureStore({ reducer: { wizard: wizardSlice.reducer } });
|
|
282
|
+
|
|
283
|
+
wizard.store.subscribe(() => {
|
|
284
|
+
store.dispatch(setSnapshot(wizard.store.getSnapshot()));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
export const selectWizardSnapshot = (state) => state.wizard.snapshot;
|
|
288
|
+
export const selectWizardProgress = createSelector(
|
|
289
|
+
[selectWizardSnapshot],
|
|
290
|
+
(snapshot) => ({
|
|
291
|
+
currentStepId: snapshot.currentStepId,
|
|
292
|
+
progress: snapshot.progress,
|
|
293
|
+
isBusy: snapshot.isBusy,
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import { useSelector } from 'react-redux';
|
|
300
|
+
|
|
301
|
+
function WizardReduxStatus() {
|
|
302
|
+
const { currentStepId, progress, isBusy } = useSelector(selectWizardProgress);
|
|
303
|
+
return (
|
|
304
|
+
<div>
|
|
305
|
+
Step: {currentStepId} ({progress}%) {isBusy ? '...' : ''}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Example: memoized config + middleware pipeline
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { useMemo } from 'react';
|
|
315
|
+
import { loggerMiddleware } from '@wizzard-packages/middleware';
|
|
316
|
+
|
|
317
|
+
function WizardRoot() {
|
|
318
|
+
const steps = useMemo(
|
|
319
|
+
() => [
|
|
320
|
+
{ id: 'name', label: 'Name', component: NameStep },
|
|
321
|
+
{ id: 'review', label: 'Review', component: ReviewStep },
|
|
322
|
+
],
|
|
323
|
+
[]
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const config = useMemo(
|
|
327
|
+
() => ({
|
|
328
|
+
steps,
|
|
329
|
+
middlewares: [loggerMiddleware],
|
|
330
|
+
validationMode: 'onChange',
|
|
331
|
+
validationDebounceTime: 250,
|
|
332
|
+
}),
|
|
333
|
+
[steps]
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<WizardProvider config={config} initialData={{ name: '' }} initialStepId="name">
|
|
338
|
+
<WizardUI />
|
|
339
|
+
</WizardProvider>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## How it fits
|
|
345
|
+
|
|
346
|
+
- Core engine: @wizzard-packages/core
|
|
347
|
+
- Optional persistence: @wizzard-packages/persistence
|
|
348
|
+
- Optional middleware: @wizzard-packages/middleware
|
|
349
|
+
- Optional validation: @wizzard-packages/adapter-zod or @wizzard-packages/adapter-yup
|
|
350
|
+
|
|
351
|
+
## Links
|
|
352
|
+
|
|
353
|
+
- Repo: https://github.com/ZizzX/wizzard-packages
|
|
354
|
+
- Docs UI: https://zizzx.github.io/wizzard-packages/
|