Stepper

Индикатор прогресса для многошаговых сценариев — список шагов с номером, заголовком и (опционально) описанием. Управляется через render-prop, поддерживает controlled и uncontrolled режимы. Stepper адаптивен: раскладку берёт из AdaptiveProvider (@cloud-ru/ds-adaptive) — на desktop горизонтальный ряд шагов, на mobile компактный.

Когда использовать

  • Многошаговые формы, где пользователю важно видеть прогресс и (на desktop) описание каждого шага.
  • Процессы с валидацией между шагами (submit → backend check → next).

Анатомия

Step state

Состояние шага: completed — пройден, current — текущий, loading — в процессе, waiting — ещё не пройден, rejected — отклонён/ошибка.

Layout type

Раскладка: desktop — горизонтальная с подписями, mobile — компактная вертикальная.

Установка

pnpm add @cloud-ru/ds-stepper
import { Stepper } from '@cloud-ru/ds-stepper'

Примеры использования

Базовый flow

Базовый flowТри шага с Next/Prev
Данные
Проверка
Готово
tsx
import { Button } from '@cloud-ru/ds-button';
import { Stepper } from '@cloud-ru/ds-stepper';

export function BasicFlow() {
  return (
    <Stepper steps={[{ title: 'Данные' }, { title: 'Проверка' }, { title: 'Готово' }]}>
      {({ stepper, goNext, goPrev, currentStepIndex, stepCount, isCompleted }) => (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          {stepper}
          <div style={{ display: 'flex', gap: 8 }}>
            <Button
              label='Назад'
              view='outline'
              appearance='neutral'
              size='s'
              onClick={() => goPrev()}
              disabled={currentStepIndex === 0}
            />
            <Button
              label={currentStepIndex === stepCount - 1 ? 'Завершить' : 'Далее'}
              appearance='primary'
              size='s'
              onClick={() => goNext()}
              disabled={isCompleted}
            />
          </div>
        </div>
      )}
    </Stepper>
  );
}

С валидатором

С валидаторомПервая попытка реджектится, вторая проходит
Данные
Проверка
Готово
tsx
import { Button } from '@cloud-ru/ds-button';
import { Stepper, StepsValidator } from '@cloud-ru/ds-stepper';
import { useRef } from 'react';

export function WithValidator() {
  const attempts = useRef(0);
  const validator: StepsValidator = async () => {
    attempts.current += 1;
    return attempts.current >= 2;
  };

  return (
    <Stepper steps={[{ title: 'Данные' }, { title: 'Проверка' }, { title: 'Готово' }]} validator={validator}>
      {({ stepper, goNext, resetValidation }) => (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          {stepper}
          <div style={{ display: 'flex', gap: 8 }}>
            <Button label='Сброс' view='outline' appearance='neutral' size='s' onClick={resetValidation} />
            <Button label='Далее' appearance='primary' size='s' onClick={() => goNext()} />
          </div>
        </div>
      )}
    </Stepper>
  );
}

Props

Types

PropsStepperProps
PropTypeDefaultRequiredDescription
allowFreeNavigationbooleannoПозволяет свободно переключаться между разными шагами без валидации
children(params: StepperApi) => ReactElement<any, string | JSXElementConstructor<any>>yesRender function. Принимает `stepper` — JSX-элемент степпера, а также api: `goNext`, `goPrev`, `resetValidation`, `setValidator`, `isCompleted`, `currentStepIndex`, `stepCount`.
classNamestringnoCSS-класс
data-test-idstringnodata-test-id
defaultCurrentStepIndexnumbernoИндекс текущего шага по-дефолту
onChangeCurrentStep((newValue: number, prevValue: number) => void)noКолбек смены текущего степа
onCompleteChange((isCompleted: boolean) => void)noКолбек изменения завершённости
stepsStepData[]yesМассив шагов
validatorStepsValidatornoВалидатор шагов. Выполняется при смене шага. Принимает первым аргументом индекс текущего, вторым — индекс нового шага. Возвращает Promise<boolean>: false → шаг помечается как Rejected.

Types

StepperProps

Адаптивность

Stepper — адаптивный компонент с переключением поверхности (surface-swap). Раскладку он берёт из AdaptiveProvider (контекст @cloud-ru/ds-adaptive); публичный API единый для обеих платформ:

  • desktop (по умолчанию) — горизонтальный ряд шагов с номером, заголовком и описанием.
  • mobile — компактный вертикальный индикатор: номер текущего шага и прогресс без полного ряда подписей.

Верстайте под desktop и поставьте один <AdaptiveProvider> в корне приложения — mobile-поверхность включается автоматически (desktop-first). Пропа layoutType у компонента нет: источник раскладки — только контекст.

Как форсировать платформу

Форс — только контекстом, не пропом:

  • Поддерево — вложенный провайдер:
    import { AdaptiveProvider } from '@cloud-ru/ds-adaptive'
    
    <AdaptiveProvider layoutType='mobile'>
      <Stepper steps={steps}>{renderStep}</Stepper>
    </AdaptiveProvider>
  • Отдельный компонент — withLayoutType (module-scope, сахар над провайдером):
    import { withLayoutType } from '@cloud-ru/ds-adaptive'
    import { Stepper } from '@cloud-ru/ds-stepper'
    
    const MobileStepper = withLayoutType(Stepper, 'mobile')

Платформенных пропов у Stepper нет — обе поверхности используют один набор пропсов.

Mobile — компактный индикатор

Mobile — компактный индикаторРаскладка форсирована в mobile: вместо горизонтального ряда — компактный вертикальный индикатор шага.
Данные
tsx
import { AdaptiveProvider, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive';
import { Button } from '@cloud-ru/ds-button';
import { Stepper } from '@cloud-ru/ds-stepper';

export function MobileLayout() {
  return (
    <AdaptiveProvider layoutType={LAYOUT_TYPE.Mobile}>
      <Stepper steps={[{ title: 'Данные' }, { title: 'Проверка' }, { title: 'Готово' }]}>
        {({ stepper, goNext, goPrev, currentStepIndex, stepCount, isCompleted }) => (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
            {stepper}
            <div style={{ display: 'flex', gap: 8 }}>
              <Button
                label='Назад'
                view='outline'
                appearance='neutral'
                size='s'
                onClick={() => goPrev()}
                disabled={currentStepIndex === 0}
              />
              <Button
                label={currentStepIndex === stepCount - 1 ? 'Завершить' : 'Далее'}
                appearance='primary'
                size='s'
                onClick={() => goNext()}
                disabled={isCompleted}
              />
            </div>
          </div>
        )}
      </Stepper>
    </AdaptiveProvider>
  );
}

Подробнее о модели адаптивности — Адаптивность — паттерн.

Storybook

Figma