Истории состояний и управляемые параметры
Контракт Button уже содержит варианты и размеры. Теперь опишем его через несколько CSF-историй и controls. Это урок о том, как превратить библиотечный интерфейс в обозримый набор состояний. Мы не добавляем автоматические проверки и не утверждаем, что все сочетания были выполнены; подготовленные истории являются исходными примерами для последующего чтения и независимого запуска.
История должна отвечать на конкретный вопрос о компоненте. Основная кнопка показывает стандартное использование, вторичная — другое назначение, отключённая — недоступность действия, длинная подпись — поведение в ограниченной области. Если истории отличаются только случайными словами, они не помогают понять контракт. Если одна история содержит огромную форму со всеми состояниями, трудно определить, какое свойство стало причиной наблюдаемого изменения.
Общие args и частные варианты
В meta заданы общие args: подпись, вариант и размер. Именованные истории переопределяют только нужные значения. Secondary меняет вариант, Danger — вариант и текст, Disabled — нативный флаг. Такое устройство сохраняет связь между стандартным примером и новым условием. Читатель видит причину разницы прямо в маленьком объекте, не сравнивая два больших JSX-дерева.
Полный файл src/Button.stories.tsx для этого состояния:
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';
const meta = {
title: 'Nabor/Button', component: Button, tags: ['autodocs'],
args: { children: 'Сохранить', variant: 'primary', size: 'regular' },
argTypes: {
variant: { control: 'select', options: ['primary', 'secondary', 'danger'] },
size: { control: 'radio', options: ['regular', 'compact'] },
children: { control: 'text' }, onClick: { control: false },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {};
export const Secondary: Story = { args: { variant: 'secondary' } };
export const Danger: Story = { args: { variant: 'danger', children: 'Удалить' } };
export const Disabled: Story = { args: { disabled: true } };
export const LongLabel: Story = { args: { children: 'Сохранить изменения в настройках организации' }, decorators: [(Story) => <div style={{ maxWidth: 220 }}><Story /></div>] };
TypeScript выводит тип отдельной истории из meta. Это особенно полезно при ограниченных строковых вариантах: опечатка в variant должна обнаруживаться на уровне исходников. Однако статическая типизация не доказывает, что цвет читаем или подпись не выходит за границу. Она отвечает за форму передаваемых данных. Визуальные и пользовательские свойства рассматриваются по собственным наблюдаемым критериям.
Controls как исследование контракта
В argTypes вариант представлен выпадающим выбором, размер — радио-переключателем, содержимое — текстовым полем. Controls позволяют человеку менять входы конкретной истории без правки исходников. Они удобны для изучения, но не заменяют именованные пограничные примеры. Случайное сочетание, полученное в панели, исчезает вместе с текущим состоянием интерфейса. Важное состояние лучше сохранить отдельной историей с объяснением.
Обработчик onClick исключён из редактируемых controls. Он является функцией, а не смысловым текстовым входом. Для демонстрации поведения далее появится отдельный React-пример с локальным состоянием. Не нужно превращать каждую историю в приложение с собственной системой событий. Если цель состоит в оформлении обычной кнопки, статических аргументов достаточно. Если цель — последовательность действий, потребуется другая композиция истории.
Визуальный размер и semantic variant независимы, поэтому пользователь controls может выбрать компактное опасное действие. Это сочетание допустимо по типам. Но документация должна объяснять, где оно уместно. Набор разрешённых значений показывает технические возможности компонента, а примеры использования — дизайн-решения. Эти уровни полезно разделять, чтобы потребитель не принимал любую комбинацию панели за рекомендуемый интерфейс.
Длинная подпись и ограниченная область
История LongLabel помещает кнопку в контейнер с максимальной шириной 220 пикселей. Это локальный декоратор одной истории. Он не меняет компонент и не устанавливает глобальное правило для всей библиотеки. Благодаря этому другой стандартный пример продолжит показывать кнопку в обычном пространстве. Внешний контекст становится частью именованного сценария, а не скрытым CSS, влияющим на остальные истории.
Надпись «Сохранить изменения в настройках организации» намеренно длиннее обычной. Она помогает обсудить перенос текста, высоту и окружающую область. Но одна русская фраза не проверяет все языки и масштабы. Реальная библиотека может получить длинные составные слова, увеличенный шрифт или другой набор символов. Историю следует считать конкретным исходным случаем, который читатель способен расширить, а не доказательством универсальной адаптивности.
Когда текст переносится на две строки, нельзя автоматически считать кнопку сломанной. Иногда это правильное поведение, сохраняющее полную подпись. Проблемой становится потеря текста, наложение на соседний элемент или непонятное сокращение действия. Критерий должен следовать пользовательской задаче. Для сетки одинаковых кнопок можно предусмотреть контейнерную геометрию, но не скрывать смысл каждой подписи многоточием без альтернативного доступного имени.
Истории и документация
Тег autodocs позволяет подготовить страницу документации из метаданных и историй в выбранном профиле Storybook. Это полезная оболочка, но она не пишет за автора правила применения. Компонент может иметь понятные TypeScript-пропсы и всё же нуждаться в объяснении выбора type, причины отключения и ограничения опасного варианта. Подробные правила мы добавим в уроке о документации после появления остальных элементов.
Не называйте страницу Autodocs «полным покрытием». Даже если перечислены все основные варианты, между ними есть комбинации и временные переходы. Здесь мы сознательно сохраняем конечные состояния кнопки. Позднее ожидание операции потребует дополнительного контракта и поведения. Объектные истории остаются полезными, потому что имеют чёткую область и не должны объяснять весь жизненный цикл приложения одновременно.
Порядок именованных экспортов помогает чтению исходников, но долгосрочную организацию мастерской лучше определять понятными названиями. История Case7 говорит лишь о месте в файле, а Disabled — о состоянии. При расширении библиотеки смысловое название уменьшает зависимость от прежнего расположения. Это тот же принцип, который применяется к устойчивым ключам инфраструктуры: идентичность обозначает назначение, а не случайный индекс.
Теперь мастерская содержит набор причинно связанных примеров кнопки. Пользователь может выбрать сохранённое состояние или исследовать входы controls. Следующий урок перенесёт этот подход на поле ввода, где внешний вид связан с подписью, значением, ошибкой и доступным описанием. Там метаданные помогут увидеть, что библиотечный контракт включает не только цвет рамки, но и связи настоящих HTML-элементов.
Важное сочетание сохраняется по имени
Если компактная отключённая кнопка с длинной подписью является настоящим сценарием продукта, создайте отдельную историю с этим условием. Она не должна существовать только в памяти автора после игры с controls. Сохранённый пример полезен дизайнеру, разработчику и будущему потребителю, потому что каждый увидит одинаковые входы. При изменении компонента можно обсуждать тот же сценарий.
В то же время не создавайте десятки одинаковых историй ради численности. Разделяйте примеры по существенной причине: другое назначение, иной доступ, ограниченная ширина, временной переход. Комбинации, которые не добавляют нового знания, допустимо исследовать controls. Такой баланс делает мастерскую обозримой и позволяет читателю учиться по именованным состояниям вместо поиска нужной кнопки среди случайных копий.
Оглавление серии, общая подборка и полный архив исходников. Для этого урока выберите lesson-04.
Первичные источники: Storybook CSF, React Vite integration, Аргументы историй, Темы и globals, Декораторы, Accessibility addon, Автоматическая документация, React useId, HTML dialog.
Редакционный статус: локальный черновик. Учебные команды, приложение и проверки не запускались; описанные результаты ожидаемые. Профиль версий закреплён, разрешённый lockfile не создавался.