# Stepper

The steps of a process, with where the person is. A step is drawn as done only when the app says `done`; pressing a step opens it (through `onStepSelect`) and never completes it. The tick is drawn, and the rail after it filled, when `done` turns true while the stepper is on screen; steps that were already done when it appeared are still. Nothing is current until you set `current`.

- Registry name: `stepper` (`@candor/stepper`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/stepper

## Install

```bash
npx candor-ui add stepper
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/stepper`.
It brings in: locale, locked, motion, utils.

## Example

```tsx
"use client"

import * as React from "react"
import { Stepper, type StepperStep } from "@/components/ui/stepper"
import { Button } from "@/components/ui/button"

// What each step has really been through is the app's data: "done" is set by the save, not by pressing the step.
export default function Example() {
  const [current, setCurrent] = React.useState("details")
  const [saved, setSaved] = React.useState<string[]>([])
  const steps: StepperStep[] = [
    { id: "details", title: "Details", description: "Name and region", done: saved.includes("details") },
    { id: "access", title: "Access", description: "Who can open it", done: saved.includes("access") },
    {
      id: "review",
      title: "Review",
      description: "Check before creating",
      locked: saved.includes("details") && saved.includes("access") ? null : "Save Details and Access first.",
    },
  ]
  return (
    <div className="flex max-w-xl flex-col gap-4">
      <Stepper steps={steps} current={current} onStepSelect={setCurrent} label="New project" />
      <div className="flex gap-2">
        <Button
          size="sm"
          variant="outline"
          onClick={() => setSaved((all) => (all.includes(current) ? all : [...all, current]))}
          disabled={current === "review"}
        >
          Save this step
        </Button>
      </div>
    </div>
  )
}
```

## <Stepper>

Props:

- `steps: StepperStep[]`: The steps in order.
- `current?: string | undefined`: The id of the step the person is on. Left out, no step is current and no count shows.
- `onStepSelect?: ((id: string) => void) | undefined`: Called with a step's id when a person presses it. Left out, the steps are plain text, not buttons.
- `orientation?: "horizontal" | "vertical" | undefined` (default `"horizontal"`): Lay the steps out in a row or a column.
- `showCount?: boolean | undefined` (default `true`): Prints "Step 2 of 5" above the steps when `current` is set.
- `label?: string | undefined`: The list's accessible name. Defaults to "Steps".
- `className?: string | undefined`

## Strings (`stepper`)

The fixed words, English by default. Override any of them under `stepper` in `<LocaleProvider strings={{ stepper: { … } }}>`; the table is exported as `stepperStrings`.

- `steps`: Steps
- `count`: Step {current} of {total}
- `done`: Done
- `current`: Current step
- `failed`: Failed
- `upcoming`: Not started

## Types

### StepperStep

One step. What it is and whether it is done come from the app; the stepper never decides either.

```ts
{ /** A stable id, passed to `onStepSelect` and matched against `current`. */ id: string /** The step's name. */ title: React.ReactNode /** A line under the title. */ description?: React.ReactNode | undefined /** True only when the app knows the step is finished (saved, verified, accepted). Pressing a step never sets it. */ done?: boolean | undefined /** True when the app knows the step failed. Dr
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
