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
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>
);
}С валидатором
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
StepperProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
allowFreeNavigation | boolean | — | no | Позволяет свободно переключаться между разными шагами без валидации |
children | (params: StepperApi) => ReactElement<any, string | JSXElementConstructor<any>> | — | yes | Render function. Принимает `stepper` — JSX-элемент степпера, а также api: `goNext`, `goPrev`, `resetValidation`, `setValidator`, `isCompleted`, `currentStepIndex`, `stepCount`. |
className | string | — | no | CSS-класс |
data-test-id | string | — | no | data-test-id |
defaultCurrentStepIndex | number | — | no | Индекс текущего шага по-дефолту |
onChangeCurrentStep | ((newValue: number, prevValue: number) => void) | — | no | Колбек смены текущего степа |
onCompleteChange | ((isCompleted: boolean) => void) | — | no | Колбек изменения завершённости |
steps | StepData[] | — | yes | Массив шагов |
validator | StepsValidator | — | no | Валидатор шагов. Выполняется при смене шага. Принимает первым аргументом индекс текущего, вторым — индекс нового шага. Возвращает Promise<boolean>: false → шаг помечается как Rejected. |
Types
StepperProps
StepData
StepperApi
StepsValidator
Адаптивность
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 — компактный индикатор
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>
);
}Подробнее о модели адаптивности — Адаптивность — паттерн.