feat: add Onboarding stepper component#101
Conversation
Adds three Onboarding stories (Horizontal, Vertical, CustomIndicator) following repo conventions (satisfies Meta, fn() from storybook/test, fullscreen layout, autodocs tag) and a `test` npm script for wp-scripts.
📝 WalkthroughWalkthroughThis PR introduces a complete multi-step onboarding wizard component. It includes type-safe step contracts, step navigation utilities, three UI leaf components (step indicator, footer, body), a main orchestrator managing step state and settings persistence, Storybook documentation, public API exports, and a test infrastructure script. ChangesOnboarding Wizard Component
Sequence DiagramsequenceDiagram
participant User
participant Onboarding
participant OnboardingInner
participant SettingsProvider
participant StepIndicator
participant StepFooter
participant StepBody
User->>Onboarding: Mount with steps and activeStepId
Onboarding->>SettingsProvider: Wrap with merged schema
SettingsProvider->>OnboardingInner: Provide settings context
OnboardingInner->>OnboardingInner: Derive active step metadata
OnboardingInner->>StepIndicator: Pass steps, onStepClick
OnboardingInner->>StepBody: Render current step content
OnboardingInner->>StepFooter: Pass navigation handlers, error state
User->>StepIndicator: Click step
StepIndicator->>OnboardingInner: onStepClick
OnboardingInner->>OnboardingInner: Update activeStepId
User->>StepFooter: Click Next
StepFooter->>OnboardingInner: onNext
OnboardingInner->>OnboardingInner: persist() validates no errors
OnboardingInner->>SettingsProvider: save(stepId, values)
SettingsProvider-->>OnboardingInner: Complete
OnboardingInner->>Onboarding: onStepChange callback
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/index.ts (1)
389-396: ⚡ Quick winInconsistent type import pattern breaks module boundaries.
Types are imported directly from the internal
onboarding-types.tsfile (line 391-396), while the component is imported from the module index (line 390). This breaks the module boundary pattern used elsewhere in this file—for example, Settings exports all types through'./components/settings'(lines 374-386).The onboarding module's
index.tsxcurrently re-exports onlyOnboardingPropsandOnboardingStep, but not the render props types. This forces consumers to bypass the module boundary and depend on internal file structure.Recommend:
- Add
StepIndicatorRenderPropsandStepFooterRenderPropsto the re-exports insrc/components/onboarding/index.tsx- Update this import to use
'./components/onboarding'for all typesThis maintains consistency, makes the public API surface clear, and protects against internal reorganization.
♻️ Recommended fix
Step 1: Add missing type re-exports to
src/components/onboarding/index.tsx:-export type { OnboardingProps, OnboardingStep } from './onboarding-types'; +export type { + OnboardingProps, + OnboardingStep, + StepIndicatorRenderProps, + StepFooterRenderProps, +} from './onboarding-types';Step 2: Update this file to import from the module index:
// Onboarding (schema-driven setup wizard) export { Onboarding } from './components/onboarding'; export type { OnboardingProps, OnboardingStep, StepIndicatorRenderProps, StepFooterRenderProps, -} from './components/onboarding/onboarding-types'; +} from './components/onboarding';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/index.ts` around lines 389 - 396, The onboarding export imports types directly from the internal onboarding-types file which breaks module boundaries; update the component module to publicly re-export StepIndicatorRenderProps and StepFooterRenderProps from src/components/onboarding/index.tsx (alongside OnboardingProps and OnboardingStep) and then change the imports in this file to import all types from './components/onboarding' (so the export lines reference Onboarding, OnboardingProps, OnboardingStep, StepIndicatorRenderProps, and StepFooterRenderProps via the module index rather than the internal onboarding-types file).src/components/onboarding/index.tsx (1)
35-47: ⚡ Quick winDocument or validate the schema page-element contract.
The code on line 39 assumes each
step.schemacontains a page element whereel.id === step.id. If a step's schema lacks this matching page element,hide_savewon't be set, potentially showing a duplicate save button in the step body (conflicting with the footer's save logic).Consider adding a DEV-mode validation:
🔍 Example validation
const schema = useMemo<SettingsElement[]>(() => { const out: SettingsElement[] = []; for (const step of steps) { + if (process.env.NODE_ENV !== 'production') { + const hasMatchingPage = step.schema.some( + el => el.type === 'page' && el.id === step.id + ); + if (!hasMatchingPage) { + console.warn( + `Onboarding step "${step.id}" schema should contain a page element with id="${step.id}"` + ); + } + } for (const el of step.schema) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/onboarding/index.tsx` around lines 35 - 47, The code building schema in the useMemo (variable schema) assumes each step.schema contains a page element with el.type === 'page' and el.id === step.id, which may be missing; add a DEV-only validation and safe-fix: iterate each step.schema and if no page element matches step.id, log a clear console.warn/error in development identifying the step (use step.id and step.title) and either set hide_save on the first page element found (el.type === 'page') or inject a minimal page element with hide_save: true so the footer save isn’t duplicated; update the logic around the loop that builds out to perform this check and modification (referencing schema useMemo, steps, step.schema, SettingsElement, hide_save).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/onboarding/index.tsx`:
- Around line 28-29: The code sets internalActive to an empty string when steps
is empty which can produce an invalid initialPage for SettingsProvider; update
the initializer and the value passed to SettingsProvider to prefer a real step
id or undefined instead of ''. For example, initialize internalActive using a
safe lookup: useState<string | undefined>(() => activeStepId ?? steps.find(s =>
s?.id)?.id), keep const active = activeStepId ?? internalActive, and when
providing initialPage to SettingsProvider pass active (which will be undefined
if no valid id) or compute const initialPage = active ?? steps.find(s =>
s?.id)?.id and pass that; ensure you reference internalActive,
setInternalActive, active, activeStepId, steps and SettingsProvider when making
the change.
---
Nitpick comments:
In `@src/components/onboarding/index.tsx`:
- Around line 35-47: The code building schema in the useMemo (variable schema)
assumes each step.schema contains a page element with el.type === 'page' and
el.id === step.id, which may be missing; add a DEV-only validation and safe-fix:
iterate each step.schema and if no page element matches step.id, log a clear
console.warn/error in development identifying the step (use step.id and
step.title) and either set hide_save on the first page element found (el.type
=== 'page') or inject a minimal page element with hide_save: true so the footer
save isn’t duplicated; update the logic around the loop that builds out to
perform this check and modification (referencing schema useMemo, steps,
step.schema, SettingsElement, hide_save).
In `@src/index.ts`:
- Around line 389-396: The onboarding export imports types directly from the
internal onboarding-types file which breaks module boundaries; update the
component module to publicly re-export StepIndicatorRenderProps and
StepFooterRenderProps from src/components/onboarding/index.tsx (alongside
OnboardingProps and OnboardingStep) and then change the imports in this file to
import all types from './components/onboarding' (so the export lines reference
Onboarding, OnboardingProps, OnboardingStep, StepIndicatorRenderProps, and
StepFooterRenderProps via the module index rather than the internal
onboarding-types file).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 11fc3447-b263-4426-9bf2-fd213897cecc
📒 Files selected for processing (10)
package.jsonsrc/components/onboarding/Onboarding.stories.tsxsrc/components/onboarding/index.tsxsrc/components/onboarding/onboarding-navigation.test.tssrc/components/onboarding/onboarding-navigation.tssrc/components/onboarding/onboarding-types.tssrc/components/onboarding/step-body.tsxsrc/components/onboarding/step-footer.tsxsrc/components/onboarding/step-indicator.tsxsrc/index.ts
| const [internalActive, setInternalActive] = useState<string>(activeStepId ?? steps[0]?.id ?? ''); | ||
| const active = activeStepId ?? internalActive; |
There was a problem hiding this comment.
Guard against empty steps array.
If steps is empty or the first step lacks an id, internalActive will be set to an empty string (line 28), which is then passed as initialPage to SettingsProvider (line 63). This misconfiguration could cause the provider to operate in an invalid state.
🛡️ Proposed guard
}: OnboardingProps) {
+ if (!steps.length) {
+ console.error('Onboarding: steps array cannot be empty');
+ return null;
+ }
const [internalActive, setInternalActive] = useState<string>(activeStepId ?? steps[0]?.id ?? '');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/onboarding/index.tsx` around lines 28 - 29, The code sets
internalActive to an empty string when steps is empty which can produce an
invalid initialPage for SettingsProvider; update the initializer and the value
passed to SettingsProvider to prefer a real step id or undefined instead of ''.
For example, initialize internalActive using a safe lookup: useState<string |
undefined>(() => activeStepId ?? steps.find(s => s?.id)?.id), keep const active
= activeStepId ?? internalActive, and when providing initialPage to
SettingsProvider pass active (which will be undefined if no valid id) or compute
const initialPage = active ?? steps.find(s => s?.id)?.id and pass that; ensure
you reference internalActive, setInternalActive, active, activeStepId, steps and
SettingsProvider when making the change.
Summary
Adds a reusable, schema-driven
<Onboarding>stepper component to@wedevs/plugin-ui. It reuses the existing settings infrastructure (SettingsProvider,SettingsContent,FieldRenderer, dependency evaluation,applyFiltersfield hooks) and only replaces the chrome: the sidebar becomes a step indicator, and the per-scope save button becomes stepper navigation (Back / Skip / Continue / Finish).OnboardingStep.schemais a "page" subtree; navigating the stepper drives the provider's active page.onNext/onFinish → save(active, getPageValues(active)) → provider handleOnSave → consumer onStepSave(stepId, treeValues, flatValues)(awaited before navigating/completing).orientationsupports horizontal and vertical; the indicator and footer are fully overridable viarenderStepIndicator/renderFooterrender props.FieldRendereras the Settings page, so a consuming wizard stays pixel-consistent with its settings UI.Files
src/components/onboarding/—onboarding-navigation.ts(+ test),onboarding-types.ts,step-indicator.tsx,step-footer.tsx,step-body.tsx,index.tsx,Onboarding.stories.tsxsrc/index.ts— exportsOnboarding+ typespackage.json— addstestscript (wp-scripts test-unit-js)This is the first of two PRs; the consumer side (Dokan Lite onboarding wizard converging onto this component + the flat settings schema) lands separately.
Test Plan
npm run typecheck— cleannpm run lint(onboarding + index) — cleanwp-scripts test-unit-js src/components/onboarding— 4/4 passnpm run build— succeeds;Onboardingpresent indist/index.d.ts+ bundlenpm run build-storybook— succeedsSummary by CodeRabbit
Release Notes
New Features
Tests