# Stepper

## Overview

A visual multi-step progress indicator that shows the user's current position within a sequential process. Uses a composable primitive pattern: `<Stepper>` wraps one or more `<Step>` children, automatically inferring the total number of steps.

The package also exports a **headless `useStepper` hook** for building fully custom step-navigation UIs — including async validation guards before advancing — while reusing all the step-state logic.

---

## Exports

| Export             | Description                                      |
| ------------------ | ------------------------------------------------ |
| `Stepper`          | Visual step indicator component                  |
| `Step`             | Individual step item                             |
| `useStepper`       | Headless hook — all step navigation logic, no UI |
| `UseStepperProps`  | TypeScript props type for the hook               |
| `UseStepperReturn` | TypeScript return type for the hook              |

---

## When to Use

- Multi-step forms (checkout, registration, onboarding)
- Sequential configuration wizards
- Any process with 2–7 ordered steps

## When NOT to Use

- Processes with more than 7 steps — collapse or restructure.
- Simple 2-step flows — a dialog with "Next"/"Back" is sufficient.

---

## Anatomy

```tsx
<Stepper currentStep={2}>
  <Step step={1} label="Account" description="Create your account" />
  <Step step={2} label="Plan" description="Choose a plan" />
  <Step step={3} label="Confirm" description="Review and confirm" />
</Stepper>
```

- Steps are numbered from `1` (not `0`).
- Steps before `currentStep` render as **completed** (green checkmark).
- The step equal to `currentStep` renders as **active** (filled primary).
- Steps after `currentStep` render as **pending** (muted).

---

## Sub-Components

### `Stepper`

| Prop          | Type                         | Required | Description                                |
| ------------- | ---------------------------- | -------- | ------------------------------------------ |
| `currentStep` | `number`                     | **Yes**  | The currently active step (1-indexed)      |
| `orientation` | `'horizontal' \| 'vertical'` | No       | Layout direction (default: `'horizontal'`) |
| `className`   | `string`                     | No       | Additional CSS classes                     |

The step wrapper div renders with `role="list"` and `aria-label="Progresso: etapa N de M"` automatically.

### `Step`

| Prop          | Type      | Required | Description                                         |
| ------------- | --------- | -------- | --------------------------------------------------- |
| `step`        | `number`  | **Yes**  | Position of this step (1-indexed, must match order) |
| `label`       | `string`  | **Yes**  | Step label text                                     |
| `description` | `string`  | No       | Optional supplementary text below the label         |
| `error`       | `boolean` | No       | When `true`, renders the step in error state (red)  |
| `className`   | `string`  | No       | Additional CSS classes                              |

Each `<Step>` renders with `role="listitem"`, `aria-current="step"` (active step only), and `aria-label="Etapa N: Label[, concluída | , atual]"` automatically — no manual ARIA attributes needed.

---

## Examples

### Basic 3-Step Flow

```tsx
import { Stepper, Step, Button } from 'xertica-ui/ui';
import { useState } from 'react';

const [currentStep, setCurrentStep] = useState(1);

<div className="space-y-8">
  <Stepper currentStep={currentStep}>
    <Step step={1} label="Account" description="Create your account" />
    <Step step={2} label="Plan" description="Choose a plan" />
    <Step step={3} label="Confirm" description="Review and confirm" />
  </Stepper>

  <div className="min-h-[200px] flex items-center justify-center">
    <p className="text-muted-foreground">Content for step {currentStep}</p>
  </div>

  <div className="flex justify-between">
    <Button
      variant="outline"
      onClick={() => setCurrentStep(s => Math.max(1, s - 1))}
      disabled={currentStep === 1}
    >
      Previous
    </Button>
    <Button onClick={() => setCurrentStep(s => Math.min(3, s + 1))} disabled={currentStep === 3}>
      {currentStep === 3 ? 'Finish' : 'Next'}
    </Button>
  </div>
</div>;
```

---

## `useStepper` Hook

A headless hook that manages step navigation state. Supports both **uncontrolled** (internal state) and **controlled** (external `step` prop) modes, plus an optional async `onBeforeNext` guard for validating the current step before advancing.

### Props

| Prop           | Type                                                   | Default | Description                                                                                                       |
| -------------- | ------------------------------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `totalSteps`   | `number`                                               | —       | **Required.** Total number of steps in the flow                                                                   |
| `initialStep`  | `number`                                               | `1`     | Starting step (uncontrolled mode only). Automatically clamped to `[1, totalSteps]` — out-of-range values are safe |
| `step`         | `number`                                               | —       | Controlled current step — when provided, the hook uses this value instead of internal state                       |
| `onStepChange` | `(step: number) => void`                               | —       | Called whenever the step changes                                                                                  |
| `onBeforeNext` | `(currentStep: number) => boolean \| Promise<boolean>` | —       | Optional async guard — return `false` (or resolve to `false`) to block advancing to the next step                 |

### Return Value

| Property      | Type                     | Description                                                      |
| ------------- | ------------------------ | ---------------------------------------------------------------- |
| `currentStep` | `number`                 | The currently active step (1-indexed)                            |
| `totalSteps`  | `number`                 | Total number of steps                                            |
| `isFirstStep` | `boolean`                | Whether the current step is the first step                       |
| `isLastStep`  | `boolean`                | Whether the current step is the last step                        |
| `canGoPrev`   | `boolean`                | Whether navigating to the previous step is possible              |
| `canGoNext`   | `boolean`                | Whether navigating to the next step is possible                  |
| `next`        | `() => Promise<void>`    | Advance to the next step (runs `onBeforeNext` guard if provided) |
| `prev`        | `() => void`             | Go back to the previous step                                     |
| `goTo`        | `(step: number) => void` | Jump to a specific step number                                   |
| `reset`       | `() => void`             | Reset to the initial step                                        |

### Uncontrolled Example

```tsx
import { useStepper, Stepper, Step, Button } from 'xertica-ui/ui';

function OnboardingWizard() {
  const { currentStep, isFirstStep, isLastStep, next, prev } = useStepper({
    totalSteps: 3,
  });

  return (
    <div className="space-y-8">
      <Stepper currentStep={currentStep}>
        <Step step={1} label="Account" />
        <Step step={2} label="Plan" />
        <Step step={3} label="Confirm" />
      </Stepper>

      <div className="min-h-[200px]">
        {currentStep === 1 && <AccountForm />}
        {currentStep === 2 && <PlanSelector />}
        {currentStep === 3 && <ConfirmationSummary />}
      </div>

      <div className="flex justify-between">
        <Button variant="outline" onClick={prev} disabled={isFirstStep}>
          Previous
        </Button>
        <Button onClick={next} disabled={isLastStep}>
          {isLastStep ? 'Finish' : 'Next'}
        </Button>
      </div>
    </div>
  );
}
```

### With Async Validation Guard

```tsx
import { useStepper, Stepper, Step, Button } from 'xertica-ui/ui';
import { useForm } from 'react-hook-form';

function ValidatedWizard() {
  const form = useForm();

  const { currentStep, isFirstStep, isLastStep, next, prev } = useStepper({
    totalSteps: 3,
    onBeforeNext: async step => {
      // Validate only the fields relevant to the current step
      const fieldsPerStep: Record<number, string[]> = {
        1: ['email', 'password'],
        2: ['plan'],
      };
      const fields = fieldsPerStep[step];
      if (!fields) return true;
      return form.trigger(fields as never[]);
    },
  });

  return (
    <div className="space-y-8">
      <Stepper currentStep={currentStep}>
        <Step step={1} label="Account" />
        <Step step={2} label="Plan" />
        <Step step={3} label="Confirm" />
      </Stepper>

      <form>{/* step content */}</form>

      <div className="flex justify-between">
        <Button variant="outline" onClick={prev} disabled={isFirstStep}>
          Previous
        </Button>
        <Button onClick={next}>{isLastStep ? 'Submit' : 'Next'}</Button>
      </div>
    </div>
  );
}
```

### Controlled Example

```tsx
import { useStepper } from 'xertica-ui/ui';
import { useState } from 'react';

function ControlledWizard() {
  const [step, setStep] = useState(1);

  const { currentStep, next, prev, isFirstStep, isLastStep } = useStepper({
    totalSteps: 4,
    step,               // controlled
    onStepChange: setStep,
  });

  // currentStep === step at all times
  return (/* ... */);
}
```

---

## AI Rules

- `step` props on `<Step>` are **1-indexed** — the first step is `step={1}`, not `step={0}`.
- Each `<Step>` `step` prop must match its position sequentially: `1, 2, 3, ...`.
- `currentStep` on `<Stepper>` must be a number from `1` to the total number of steps.
- Always render the navigation buttons (Previous/Next) below the Stepper.
- Disable "Previous" on step `1` (`isFirstStep`) and "Next" on the final step (`isLastStep`).
- Use `onBeforeNext` for async form validation — return `false` to block advancing.
- `next()` is **async** — always `await` it or use it as an `onClick` handler directly (React handles the Promise).
- In controlled mode, pass both `step` and `onStepChange` — omitting `onStepChange` makes the hook read-only.
- Use `reset()` to return to step 1 after a successful submission.
- `initialStep` is automatically clamped to `[1, totalSteps]` — passing `0`, negative values, or values beyond `totalSteps` is safe and will not throw.
- Do NOT add `role="list"`, `aria-current`, or `aria-label` manually to `<Stepper>` or `<Step>` — they are applied automatically since v2.1.9.

---

## Related Components

- [`Progress`](./progress.md) — For generic percentage progress display
- [`Form`](./form.md) — Commonly used inside multi-step steppers
