Skip to content

feat: add Onboarding stepper component#101

Open
mrabbani wants to merge 13 commits into
mainfrom
feat/plugin-ui-onboarding-component
Open

feat: add Onboarding stepper component#101
mrabbani wants to merge 13 commits into
mainfrom
feat/plugin-ui-onboarding-component

Conversation

@mrabbani

@mrabbani mrabbani commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds a reusable, schema-driven <Onboarding> stepper component to @wedevs/plugin-ui. It reuses the existing settings infrastructure (SettingsProvider, SettingsContent, FieldRenderer, dependency evaluation, applyFilters field 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).

  • Each OnboardingStep.schema is a "page" subtree; navigating the stepper drives the provider's active page.
  • Step save routes onNext/onFinish → save(active, getPageValues(active)) → provider handleOnSave → consumer onStepSave(stepId, treeValues, flatValues) (awaited before navigating/completing).
  • orientation supports horizontal and vertical; the indicator and footer are fully overridable via renderStepIndicator / renderFooter render props.
  • Fields render through the exact same FieldRenderer as 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.tsx
  • src/index.ts — exports Onboarding + types
  • package.json — adds test script (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 — clean
  • npm run lint (onboarding + index) — clean
  • wp-scripts test-unit-js src/components/onboarding — 4/4 pass
  • npm run build — succeeds; Onboarding present in dist/index.d.ts + bundle
  • npm run build-storybook — succeeds
  • Review the 4 stories under Components → Onboarding (Horizontal, Vertical, NoIndicator, CustomIndicator)

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a multi-step onboarding component with horizontal and vertical orientation support.
    • Features customizable step indicators, navigation footer with back/skip/next/finish controls, and optional custom renderers.
  • Tests

    • Added test coverage for step navigation utilities.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

Onboarding Wizard Component

Layer / File(s) Summary
Type contracts and navigation utilities
src/components/onboarding/onboarding-types.ts, src/components/onboarding/onboarding-navigation.ts, src/components/onboarding/onboarding-navigation.test.ts
OnboardingStep, StepIndicatorRenderProps, StepFooterRenderProps, and OnboardingProps define the wizard data model and render interfaces. StepRef type and navigation helpers (stepIndexOf, nextStepId, prevStepId, isFirstStep, isLastStep) provide step traversal with bounds checking, tested with comprehensive cases.
Onboarding UI leaf components
src/components/onboarding/step-indicator.tsx, src/components/onboarding/step-footer.tsx, src/components/onboarding/step-body.tsx
StepIndicator renders horizontal or vertical step buttons with active/completed styling and click navigation. StepFooter conditionally shows Back/Skip and Continue/Finish buttons, disabling the primary action on validation errors. StepBody wraps SettingsContent with flexbox layout.
Onboarding orchestrator component
src/components/onboarding/index.tsx
Onboarding manages internal active-step state for uncontrolled mode, builds a merged settings schema enforcing hide_save on step pages so only the footer controls persistence. OnboardingInner derives step metadata (first/last flags), wires step indicator and footer callbacks, implements persist() to conditionally save current step values before advancing, and renders the complete wizard layout with customizable indicator/footer renderers falling back to defaults.
Onboarding documentation and showcase
src/components/onboarding/Onboarding.stories.tsx
Storybook file with stepSchema helper and a steps fixture (basic, commission, withdraw configurations). Exports four stories: Horizontal and Vertical for orientation variants, NoIndicator hiding the step progress, and CustomIndicator with a custom button-based step renderer.
Public API exports
src/index.ts
Exports Onboarding component and type contracts (OnboardingProps, OnboardingStep, StepIndicatorRenderProps, StepFooterRenderProps) to the public library API.
Test infrastructure setup
package.json
Adds test npm script invoking wp-scripts test-unit-js.

Sequence Diagram

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops through the code, a wizard takes form,
Steps march in order, a multi-page swarm,
Navigation buttons dance left and right,
Settings persist as we hop through the night,
A form reborn as a journey so bright!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Onboarding stepper component' directly and clearly describes the main change: introducing a new Onboarding stepper component with full feature implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plugin-ui-onboarding-component

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/index.ts (1)

389-396: ⚡ Quick win

Inconsistent type import pattern breaks module boundaries.

Types are imported directly from the internal onboarding-types.ts file (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.tsx currently re-exports only OnboardingProps and OnboardingStep, but not the render props types. This forces consumers to bypass the module boundary and depend on internal file structure.

Recommend:

  1. Add StepIndicatorRenderProps and StepFooterRenderProps to the re-exports in src/components/onboarding/index.tsx
  2. Update this import to use './components/onboarding' for all types

This 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 win

Document or validate the schema page-element contract.

The code on line 39 assumes each step.schema contains a page element where el.id === step.id. If a step's schema lacks this matching page element, hide_save won'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

📥 Commits

Reviewing files that changed from the base of the PR and between f5d308f and 76aa151.

📒 Files selected for processing (10)
  • package.json
  • src/components/onboarding/Onboarding.stories.tsx
  • src/components/onboarding/index.tsx
  • src/components/onboarding/onboarding-navigation.test.ts
  • src/components/onboarding/onboarding-navigation.ts
  • src/components/onboarding/onboarding-types.ts
  • src/components/onboarding/step-body.tsx
  • src/components/onboarding/step-footer.tsx
  • src/components/onboarding/step-indicator.tsx
  • src/index.ts

Comment on lines +28 to +29
const [internalActive, setInternalActive] = useState<string>(activeStepId ?? steps[0]?.id ?? '');
const active = activeStepId ?? internalActive;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant