Ошибки, пустые результаты и сообщения статуса
Ожидание операции уже выражено отдельно. Теперь добавим Notice для ошибок и статуса и рассмотрим пустой результат. Учебная цель состоит в сообщении, которое помогает пользователю понять, что случилось и что делать дальше. Компонент не получает сырое исключение сервера: приложение передаёт осмысленный текст и выбирает семантический тип.
В «Наборе» различаем не срочное обновление состояния и ошибку, требующую внимания. Сохранение завершилось — сообщение статуса. Сохранение не удалось — сообщение ошибки с пояснением, что введённый текст остался в форме. Организаций ещё нет — пустое состояние, которое само по себе не является отказом сервера. Цвет, иконка и роль должны следовать этому различию, а не превращать любую необычную ситуацию в красную тревогу.
Контракт сообщения
NoticeProps содержит kind, заголовок и необязательное содержимое. kind ограничен status и error; сообщение возвращает соответствующий role. Содержимое может быть текстом или составным фрагментом, например объяснением с доступным действием. Но компонент не знает, как повторить запрос, создать организацию или отменить операцию. Эти действия передаются приложением либо располагаются рядом в его форме.
Полный файл src/Notice.tsx для этого состояния:
export interface NoticeProps { kind: 'status' | 'error'; title: string; children?: import('react').ReactNode; }
export function Notice({ kind, title, children }: NoticeProps) {
return <section className={`nabor-notice nabor-notice--${kind}`} role={kind === 'error' ? 'alert' : 'status'}>
<strong>{title}</strong>{children && <div>{children}</div>}
</section>;
}
Полный файл src/Notice.stories.tsx для этого состояния:
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Notice } from './Notice';
const meta = { title: 'Nabor/Notice', component: Notice, tags:['autodocs'], args: { kind:'status', title:'Изменения сохранены' } } satisfies Meta<typeof Notice>;
export default meta;
type Story=StoryObj<typeof meta>;
export const Saved:Story={};
export const Failure:Story={args:{kind:'error',title:'Не удалось сохранить',children:'Ваш текст остаётся в форме. Попробуйте ещё раз.'}};
export const Empty:Story={args:{kind:'status',title:'Организаций пока нет',children:'Создайте первую организацию, чтобы добавить участников.'}};
role="status" выражает не срочное обновление. role="alert" предназначен для более срочной информации. Выбор роли влияет на взаимодействие со вспомогательными технологиями, но не означает одинаковое озвучивание при любых способах появления DOM. Реальные объявления зависят от времени вставки, изменения текста, браузера и используемого средства чтения. Поэтому наша библиотека описывает назначение роли и требует отдельного подтверждения пользовательского поведения, не заявляя готовую проверку доступности.
Заголовок выведен через strong, потому что в данном небольшом сообщении это краткая выделенная фраза, а не новый уровень структуры всей страницы. Если продуктовая панель требует полноценный заголовок, можно пересмотреть контракт. Не надо автоматически превращать каждый Notice в h2: это создаёт лишние разделы внутри формы. Семантика страницы зависит от контекста, а библиотечный элемент должен иметь объяснённый исходный выбор.
Ошибка, которую можно исправить
История Failure говорит: «Не удалось сохранить. Ваш текст остаётся в форме. Попробуйте ещё раз». В ней названа операция, сохранность результата ввода и доступный следующий шаг. Это полезнее строки Error 500, которую пользователь не обязан понимать. Технические подробности могут понадобиться для поддержки, но пользовательское сообщение должно объяснять последствия и действие. Приложение решает, какие сведения допустимо показывать публично.
Не очищайте поле только потому, что сервер вернул ошибку. Потеря текста добавит пользователю работу и может привести к отказу от задачи. Notice сам не управляет полями, поэтому такой контракт фиксируется в составной форме. Если повтор опасен из-за неизвестного результата, надо назвать эту неопределённость и использовать подходящий механизм сверки. Простая кнопка повторения не подходит всем операциям, особенно если сервер мог уже выполнить запрос.
Сообщение также должно различать ошибку ввода и общий отказ сохранения. Ошибка одного поля привязывается к полю через механизм прошлого урока. Общая недоступность сервиса описывается на уровне формы. Выводить обе ситуации одним глобальным alert удобно разработчику, но неудобно человеку: он не знает, куда переместить фокус и что исправить. Компоненты «Набора» дают две разные формы связи, чтобы приложение могло выбрать нужную.
Пустой результат
История Empty описывает отсутствие организаций. Это корректное состояние нового аккаунта, поэтому используется status и спокойное приглашение создать первую организацию. Не следует показывать «Ошибка: нет данных», если запрос успешно вернул пустой набор. Пользователь решит, что система сломана, хотя ему просто надо начать работу. Пустота имеет смысл предметной области и должна быть объяснена этим смыслом.
Другой пустой случай — поиск не нашёл совпадений. Здесь следующий шаг будет другим: изменить запрос или убрать фильтр. Можно использовать те же базовые стили, но текст должен назвать конкретную причину. Поэтому Notice не содержит универсальную встроенную фразу «Материал готовится». Система компонентов не должна одинаково отвечать на разные пользовательские ситуации только ради удобства повторного использования.
Время жизни сообщения
Статус «Изменения сохранены» относится к определённой версии ввода. Если пользователь затем изменил название организации, прежнее сообщение нельзя оставить как доказательство сохранения нового текста. Составная форма позднее сбросит флаг успеха при изменении значения. Это не косметический эффект: сообщение описывает факт. Библиотечный компонент отображает его, а владелец формы поддерживает актуальность условия.
Автоматическое исчезновение тоже требует смысла. Ошибку с необходимой инструкцией нельзя убирать так быстро, что пользователь не успевает прочитать её. Для нейтрального статуса краткое время может быть допустимо, если результат понятен и доступен другим способом. Наш базовый Notice не содержит таймер. Потребитель выбирает политику времени, документируя её для конкретной задачи. Так библиотека не навязывает один сценарий всем продуктовым сообщениям.
В стилях ошибка получает иной контур, но текст по-прежнему остаётся основным источником значения. Если отключить CSS, сообщения должны быть понятны из слов и разметки. Это полезный мыслительный способ заметить зависимость смысла исключительно от цвета. Он не заменяет практическую проверку, но помогает выбрать структуру ещё до запуска. Обновление дизайна не должно превращать исправимую ошибку в набор декоративных символов.
Теперь библиотека различает сообщения статуса, ошибки и конкретное пустое состояние. Они не смешиваются с полевой validation и не скрывают сетевые обязанности приложения. Следующий урок введёт две темы оформления. Семантика Notice останется прежней: тема поменяет значения токенов, но не преобразует успех в ошибку и не изменит договор о сохранности введённого текста.
Содержание сообщения и чувствительные данные
В пользовательский текст не переносится весь объект технического исключения. Он может содержать внутренние адреса, параметры запроса или непонятные детали. Приложение формирует безопасное объяснение последствий, а нужная диагностика остаётся в подходящем канале наблюдения. Notice принимает уже подготовленное сообщение и не превращает неизвестный Error в универсальную фразу автоматически.
Также не делайте действие повторения обязательной частью каждого Notice. В пустом новом аккаунте уместно создание, в поиске — изменение условий, при неопределённом результате платежа — выяснение факта выполнения. Компонент должен позволять выразить конкретную задачу, а не навязать один шаблон кнопки. Семантическое повторное использование хорошо работает, когда общий элемент сохраняет возможность честно назвать разные пользовательские причины.
Оглавление серии, общая подборка и полный архив исходников. Для этого урока выберите lesson-08.
Первичные источники: Storybook CSF, React Vite integration, Аргументы историй, Темы и globals, Декораторы, Accessibility addon, Автоматическая документация, React useId, HTML dialog.
Редакционный статус: локальный черновик. Учебные команды, приложение и проверки не запускались; описанные результаты ожидаемые. Профиль версий закреплён, разрешённый lockfile не создавался.