@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Aziz
3
+ Copyright (c) 2026 Aziz Isapov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,354 @@
1
+ # @wizzard-packages/react
2
+
3
+ ![npm](https://img.shields.io/npm/v/@wizzard-packages/react)
4
+ ![downloads](https://img.shields.io/npm/dm/@wizzard-packages/react)
5
+ ![license](https://img.shields.io/npm/l/@wizzard-packages/react)
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/