@efficiency-point/design-system
v0.4.8
Published
Efficiency-point react design system
Readme
Efficiency Point Design System
React/TypeScript дизайн-система с CSS Modules, публичными типами, Storybook и библиотечной сборкой ESM/CJS.
Требования
- Node.js, совместимый с Vite 8;
- React и React DOM
>=18 <20в приложении-потребителе; - npm как пакетный менеджер.
Подключение
npm install @efficiency-point/design-systemimport { DSButton, DSInfoBlock } from '@efficiency-point/design-system';
import '@efficiency-point/design-system/styles.css';
export function Example() {
return (
<DSInfoBlock title="Информация">
<DSButton>Продолжить</DSButton>
</DSInfoBlock>
);
}Компоненты форм контролируемые: value/checked хранится у приложения и обновляется через callback.
Темизация
Доступны две полные палитры: РПФ (signal, по умолчанию) и Стекло (teal, исходная тема версии 0.3.0). Тема «РПФ» использует зелёный primary #007b45, жёлтый secondary #f6c500 с тёмным текстом, нейтральные серые поверхности, синие информационные и красные ошибочные состояния. Геометрия, типографика и API существующих компонентов не меняются.
В теме «Стекло» сохранены исходные значения: primary #16756d; brand ссылается на primary. Сливовый secondary #76528b (hover #634575, active #533963, surface #f3eef7) предназначен для специальных действий. Для сохранения прежнего отображения при обновлении задайте <html data-ds-theme="teal"> до первой отрисовки.
styles.css содержит обе темы. Атрибут data-ds-theme="teal" или data-ds-theme="signal" на html переключает всю палитру, включая порталы. Локальный атрибут поддерживает сравнение обычных компонентов; порталы наследуют тему документа. Переопределения потребителя через поздний :root сохраняют приоритет.
Переключатель и сохранение
import { useState } from 'react';
import { applyDSTheme, DSThemeSwitcher, getStoredDSTheme } from '@efficiency-point/design-system';
import '@efficiency-point/design-system/styles.css';
// В клиентской точке входа, до createRoot().render().
applyDSTheme(getStoredDSTheme());
export function ThemePicker() {
const [theme, setTheme] = useState(getStoredDSTheme);
return <DSThemeSwitcher value={theme} onChange={(next) => {
applyDSTheme(next);
setTheme(next);
}} />;
}DSThemeSwitcher — controlled-компонент (value, onChange, необязательные compact и disabled). Меню управляется клавиатурой: Tab, стрелки, Space, Escape. compact скрывает подпись, сохраняя доступное имя и подсказку. applyDSTheme применяет тему к document.documentElement и сохраняет её под ключом efficiency-point.design-system.theme; getStoredDSTheme возвращает тему «РПФ» (signal) при отсутствии/невалидности значения или блокировке storage. Импорт библиотеки сам не обращается к DOM и не меняет storage. При SSR задавайте начальный атрибут на сервере либо восстанавливайте выбор ранним клиентским скриптом до отображения страницы.
В Storybook переключатель в верхней панели синхронизирован с Design System → Foundations → Themes → Предпросмотр тем и примером самого переключателя. Выбор сохраняется после перезагрузки, применяется в Canvas/Docs и синхронизируется между вкладками. Сравнение двух палитр на странице предпросмотра остаётся фиксированным.
Регрессия тем: после npm run build запустите node tests/theme-browser.mjs. Проверяются исходные токены «Стекло» (teal), состояния кнопок, полнота темы «РПФ» (signal), переопределения потребителя и forced-colors. Для проверки переключателя, localStorage, вкладок, Docs и mobile соберите и запустите Storybook, задайте STORYBOOK_URL (по умолчанию http://127.0.0.1:6093) и выполните node tests/theme-storybook.mjs.
Информационный акцент темы «Стекло»: --color-info #087d71, текст #11665d, фон #e8f4f1. Цвета переопределяются публичными CSS-токенами; --color-background ссылается на --color-surface. Светлый --color-accent-on-dark #5ed5c8 используется на тёмной панели видеоплеера. В теме «РПФ» видеоплеер использует нейтральную тёмную палитру и жёлтый акцент. Цвета backdrop и тени также вынесены в токены.
Компоненты используют CSS custom properties из tokens.css. Их можно переопределить после импорта библиотечного CSS:
:root {
--color-primary: #2563eb;
--color-secondary: #76528b;
--border-radius: 12px;
--ds-dropdown-menu-max-height: 280px;
}Компоненты
DSButton
Кнопка с вариантами primary, secondary, outline, ghost, empty, destructive, destructiveOutline и размерами sm, md, lg. secondary использует сплошной secondary-акцент, а ghost — прозрачный фон с brand-текстом и мягкой подложкой при наведении.
<DSButton variant="primary" isLoading={saving} onClick={save}>
Сохранить
</DSButton>type по умолчанию равен button. При isLoading кнопка блокируется и получает aria-busy.
DSInput
Текстовое или числовое контролируемое поле с label, hint, error, required и очисткой.
<DSInput
label="Название"
value={name}
onChange={(value) => setName(String(value))}
error={nameError}
required
/>DSCheckbox
Поддерживает controlled state, indeterminate, readOnly, disabled, hint/error и нативные form-атрибуты.
<DSCheckbox checked={enabled} onChange={setEnabled} label="Активен" />DSTextarea
Контролируемое многострочное поле в стилистике DSInput. По умолчанию
показывает две строки и автоматически увеличивается по содержимому. maxRows
ограничивает рост и включает внутреннюю прокрутку; autoResize={false} включает
ручное изменение высоты.
<DSTextarea
label="Комментарий"
value={comment}
onChange={setComment}
minRows={2}
maxRows={8}
maxLength={500}
showCount
/>DSRadioButton
Двухпозиционный switch. Несмотря на историческое имя, в DOM используется checkbox с role="switch".
<DSRadioButton checked={enabled} onChange={setEnabled} label="Уведомления" />DSDropDown
Одиночный выбор. Поддерживаются disabled options, hint/error, disabled display и клавиши ArrowUp/ArrowDown, Home, End, Enter, Space, Escape и Tab.
<DSDropDown
label="Статус"
options={[{ label: 'Активен', value: 'active' }]}
value={status}
onChange={setStatus}
maxMenuHeight={280}
/>Высота списка по умолчанию задаётся --ds-dropdown-menu-max-height: 240px; содержимое прокручивается, активная keyboard-опция подводится в видимую область.
DSDropDownMultiple
Множественный controlled dropdown. onChange возвращает массив значений и выбранные объекты. По умолчанию меню остаётся открытым после выбора.
<DSDropDownMultiple
options={options}
value={selectedIds}
onChange={setSelectedIds}
closeOnSelect={false}
/>DSSearchDropDown
Контролируемый одиночный выбор с live-поиском по label и value. Меню показывает не более пяти строк одновременно и прокручивает остальные варианты.
<DSSearchDropDown
label="Отдел"
options={options}
value={departmentId}
onChange={setDepartmentId}
placeholder="Найдите отдел"
/>DSDateTimePicker
Контролируемое поле для date, time или datetime, принимающее Date | null, min и max. Дату можно выбрать в календаре или ввести вручную восемью цифрами по маске дд.мм.гггг; разделители подставляются автоматически, а невозможные даты отклоняются во время ввода.
<DSDateTimePicker mode="datetime" value={date} onChange={setDate} />DSDateRange
Контролируемый диапазон дат или локальных даты/времени. Обе границы поддерживают календарь и ручной ввод по маске. onChange получает только завершённую валидную пару Date; промежуточное состояние доступно через onDraftChange.
<DSDateRange mode="datetime" value={range} onChange={setRange} onDraftChange={setDraftRange} />DSDataTable и DSSort
DSDataTable всегда рендерит строки блоками: сначала 30, затем ещё 30 при появлении любой из последних 8 строк в области просмотра. virtualizationThreshold={50} меняет размер начального и последующих блоков. Полный массив data сохраняется: сортировка, общий счётчик и выбор всех строк работают со всем набором (либо текущей страницей при пагинации). Передавайте весь displayedItems, без предварительного slice. Уже показанные строки остаются в DOM. Работает прокрутка страницы, вложенного контейнера и мобильных карточек; maxHeight необязателен.
Типизированная таблица на стабильной @tanstack/react-table с обязательной сортировкой. По умолчанию мобильный вид отображает строки карточками; tableOnMobile сохраняет таблицу. breakpoint="default" переключает вид ниже 640 px, breakpoint="wide" — ниже 1280 px.
<DSDataTable columns={columns} data={rows} breakpoint="wide" />По умолчанию ниже выбранного breakpoint строки превращаются в карточки. Для реестров, где на mobile важно сохранить таблицу, используйте явный mobileView="table"; исторический tableOnMobile остается совместимым. density="compact" уменьшает высоту строк, не меняя семантику и mobile-режим.
<DSDataTable columns={columns} data={rows} mobileView="table" density="compact" />Универсальные renderBulkActions и renderExpandedRow позволяют потребителю собрать собственную панель массовых действий и master-detail строку. Business actions и их подписи остаются в приложении. Sticky-колонки задаются через column.sticky и column.stickyOffset.
Пагинация выключена по умолчанию. pagination включает клиентское разбиение
отсортированных строк, pageSize задаёт размер страницы (по умолчанию 30),
а visibleColumnIds ограничивает набор отображаемых колонок.
<DSDataTable
columns={columns}
data={rows}
pagination
pageSize={30}
visibleColumnIds={['name', 'status', 'updatedAt']}
/>Чтобы показать компактный выбор размера страницы сверху и снизу таблицы,
передайте onPageSizeChange. По умолчанию доступны 10, 30, 50 и -1.
Через pageSizeOptions можно передать свой набор положительных целых размеров,
например [10, 30, 50, 100, -1] (-1 отображается как «Все»).
Выбор остаётся черновым до нажатия
«Применить», после чего таблица вызывает onPageChange(1) и
onPageSizeChange. В client-режиме -1 показывает весь массив; в
server-режиме таблица предупреждает о загрузке всех записей.
<DSDataTable
columns={columns}
data={currentPageRows}
pagination
paginationMode="server"
page={page}
pageSize={pageSize}
totalCount={totalCount}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>Если размер страницы должен применяться общей кнопкой фильтров, используйте
controlled-черновик и pageSizeApplyMode="external". В этом режиме собственная
кнопка таблицы скрыта, а pageSize остаётся последним применённым значением.
<DSDataTable
columns={columns}
data={currentPageRows}
pagination
pageSize={appliedPageSize}
pageSizeDraft={draftPageSize}
pageSizeApplyMode="external"
onPageSizeDraftChange={setDraftPageSize}
/>Для полностью встроенной панели передайте filterBar. Поля описываются
декларативно (search/text, multi-select, switch) либо через custom.
Опции multi-select могут быть массивом или асинхронной функцией. Таблица не
владеет бизнес-состоянием: draftValues и appliedValues контролируются
потребителем, а единый onApply получает { filters, pageSize }. Validation
errors блокируют применение; applying включает loading-состояние. При наличии
filterBar отдельная кнопка применения размера страницы скрывается.
<DSDataTable
columns={columns}
data={rows}
pagination
pageSize={appliedPageSize}
pageSizeDraft={draftPageSize}
onPageSizeDraftChange={setDraftPageSize}
filterBar={{
fields: [
{ key: 'search', label: 'Поиск', type: 'search' },
{ key: 'places', label: 'Места работы', type: 'multi-select', options: loadPlaces },
{ key: 'attention', label: 'Только требующие внимания', type: 'switch' },
],
draftValues: draftFilters,
appliedValues: appliedFilters,
onDraftValuesChange: setDraftFilters,
onApply: ({ filters, pageSize }) => applyFilters(filters, pageSize),
resetValues: emptyFilters,
onReset: ({ filters, pageSize }) => resetFilters(filters, pageSize),
applying: isLoading,
}}
/>Для данных, уже разбитых сервером на страницы, используется явный controlled-режим.
page нумеруется с единицы, totalCount содержит общее число строк, а
onPageChange запускает загрузку выбранной страницы. В этом режиме data не
разбивается повторно; при loading пагинация остаётся видимой и временно
недоступной, если известен totalCount.
<DSDataTable
columns={columns}
data={currentPageRows}
pagination
paginationMode="server"
page={page}
pageSize={30}
totalCount={totalCount}
onPageChange={setPage}
/>Выбор строк включается через selectable. Без selectedRowIds таблица хранит
его локально; для серверной пагинации используйте управляемый режим.
Заголовочный checkbox
выбирает строки только текущей страницы, а «Сбросить» очищает весь переданный
набор. В режиме выбора обычный клик по строке меняет selection, двойной клик и
Enter вызывают onRowDoubleClick, Space меняет selection. Без selectable
прежнее поведение onRowClick сохраняется.
<DSDataTable
columns={columns}
data={currentPageRows}
getRowId={(row) => String(row.id)}
selectable
selectedRowIds={selectedIds}
onSelectedRowIdsChange={setSelectedIds}
onRowDoubleClick={openRow}
getRowSelectionLabel={(row) => `Выбрать ${row.name}`}
selectAllRowsLabel="Выбрать все строки на этой странице"
resetSelectionLabel="Сбросить выбор"
openRowLabel="Открыть строку"
/>selectedRowIds принадлежит приложению: таблица не удаляет идентификаторы строк
с других серверных страниц. Поэтому один набор можно накапливать при переходах
по пагинации и сохранять после повторной загрузки данных.
DSSort можно использовать отдельно для сортировки по полям объектов или строковым значениям.
totalCountVariant="highlighted" добавляет компактный виджет «Всего» над строками:
totalCountLabel меняет его подпись и доступное имя (по умолчанию «Всего»).
в server-режиме он использует totalCount, в client-режиме — весь массив до пагинации.
По умолчанию (default) оформление не меняется. toolbarContent — свободный слот
рядом с виджетом, после фильтров; счётчики и действия определяет приложение.
getRowClassName(row) и getRowStyle(row) применяются к строкам и оболочкам
мобильных карточек (включая renderMobileCard). Для фона задавайте CSS variables
--ds-table-row-background и --ds-table-row-hover-background через класс или style.
Фон sticky-ячеек использует те же переменные; используйте непрозрачный цвет
или color-mix с var(--color-surface). Обводка выбранной строки сохраняется.
<DSDataTable
columns={columns}
data={rows}
totalCountVariant="highlighted"
toolbarContent={<DSButton onClick={exportRows}>Выгрузить</DSButton>}
getRowClassName={(row) => row.needsAttention ? styles.attention : undefined}
/>DSFilters
Контролируемая композиция фильтров типов input, dropdown, multiple, checkbox, radio и dateRange, построенная из компонентов дизайн-системы.
<DSFilters filters={filters} values={values} onChange={setValues} />DSFileInput
Контролируемая зона выбора и перетаскивания файлов. accept, maxSize и
maxFiles ограничивают файлы, multiple разрешает множественный выбор,
readOnly сохраняет просмотр без изменений, а onReject сообщает причины
отклонения (accept, maxSize, maxFiles).
<DSFileInput
label="Документы"
value={files}
onChange={setFiles}
multiple
accept=".pdf,image/*"
maxSize={10 * 1024 * 1024}
maxFiles={5}
onReject={handleRejectedFiles}
/>DSVideoPlayerFrame
Адаптивный видеоплеер с собственными controls, несколькими sources/tracks, poster, loading/error-состояниями, горячими клавишами, настройкой скорости, субтитров, loop, Picture-in-Picture и fullscreen. Редкие действия собраны в меню настроек; controls={false} скрывает панель управления.
<DSVideoPlayerFrame title="Обучение" sources={[{ src: '/video.mp4', type: 'video/mp4' }]} />DSInfoBlock
Постоянно видимая карточка-виджет с заголовком и произвольным содержимым. dismissible только показывает кнопку; скрытием управляет приложение через onDismiss.
{visible && (
<DSInfoBlock title="Сводка" dismissible onDismiss={() => setVisible(false)}>
Контент
</DSInfoBlock>
)}DSBadge
Неинтерактивная метка. Варианты: neutral, brand, info, success, warning, error; размеры sm, md; доступны dot и icon. brand предназначен для несмысловых продуктовых акцентов; состояния успеха, предупреждения и ошибки по-прежнему используют семантические варианты.
<DSBadge variant="success" dot>Готово</DSBadge>DSAccordion
При раскрытии Accordion автоматически прокручивает страницу после завершения анимации: короткий контент показывается целиком у нижней границы viewport, а высокий фиксируется заголовком у верхней границы. Поведение можно отключить через autoScrollOnOpen={false}; отступ настраивается scrollOffset.
DSTabs
Контролируемые горизонтальные line-tabs. Компонент сохраняет содержимое всех панелей в DOM, а неактивные панели скрывает через hidden, поэтому состояние вложенных полей не сбрасывается. Поддерживаются disabled/loading-состояния, горизонтальный overflow и автоматическая активация клавишами Left/Right/Home/End.
const items = [
{ id: 'general', label: 'Общее', content: <GeneralForm /> },
{ id: 'documents', label: 'Документы', content: <DocumentsForm /> },
];
<DSTabs
items={items}
value={activeTab}
onChange={setActiveTab}
ariaLabel="Разделы формы"
/>DSSkeleton
Декоративный placeholder с вариантами text, rectangular, circular, несколькими строками и отключаемой анимацией. Skeleton скрыт от accessibility tree; aria-busy должен задавать родитель.
<section aria-busy="true"><DSSkeleton lines={3} /></section>DSTooltip
Короткая неинтерактивная подсказка. Открывается по hover/focus/touch, закрывается по blur, pointer leave и Escape. Для кнопок и ссылок внутри содержимого используйте popover, а не tooltip.
Подсказка рендерится через portal и поэтому не обрезается родителями с
overflow: hidden. Указанный placement является предпочтительным: у края
экрана компонент автоматически переносит подсказку на противоположную сторону
и сдвигает её внутрь viewport.
<DSTooltip content="Редактировать" placement="top">
<button type="button">...</button>
</DSTooltip>DSIcon
Иконки доступны через типизированный реестр DSIcon. Помимо базового набора,
в него входят прикладные outline-иконки IconPlug, IconBookText,
IconArchive, IconCopy, IconFileText и IconTrash2.
<DSIcon name="IconFileText" title="Документы" />DSLoader и DSPageLoader
DSLoader — компактный индикатор с role="status" или декоративным режимом. DSPageLoader — центрированный loading-state области страницы.
DSIcon
Типизированный реестр иконок. Публичны union DSIconName и readonly-список всех доступных имён DSIconNames. Размер и цвет задаются через width, height и color. Без title иконка декоративная; с title получает роль изображения и доступное имя.
<DSIcon name="IconCheck" color="var(--color-success)" title="Подтверждено" />Для переключателей вида доступны IconPanelsTopLeft и IconList.
Field primitives
DSFieldLabel и DSFieldMessage унифицируют label, required marker, optional text, hint/error/success сообщения. Публичны все props и DSFieldMessageVariant.
Toast
Toast использует внутренний Zustand store и публичный useToast. Provider обычно подключается один раз рядом с корнем приложения.
function Root() {
return (
<>
<App />
<DSToastProvider position="top-right" />
</>
);
}
function SaveButton() {
const { successToast } = useToast();
return (
<DSButton onClick={() => successToast({ message: 'Сохранено' })}>
Сохранить
</DSButton>
);
}Расширенный API экспортирует useToastStore, ToastStoreState, ToastItem, ShowToastPayload, ToastAction и ToastVariant.
dedupeKey предотвращает размножение одинаковых уведомлений, а maxVisible у provider ограничивает число одновременно видимых toast, сохраняя остальные в FIFO-очереди. Оба поведения opt-in, поэтому прежние вызовы остаются без изменений.
successToast({ message: 'Изменения сохранены', dedupeKey: 'task-save' });
<DSToastProvider maxVisible={2} />Навигация
DSNavigationSurface отображает универсальные группы ссылок со слотами поиска и дополнительного контента. DSBottomNavigation предоставляет mobile-навигацию с safe-area и зонами нажатия не менее 44px. Маршруты, permissions, состав групп и business labels принадлежат приложению; для React Router используется renderLink.
Overlay surfaces
DSDrawer, DSBottomSheet и DSFullscreenDialog построены на общем DSOverlay: поддерживают стабильные header/content/footer области, focus trap/restore, Escape/backdrop reasons, safe-area и mobile fullscreen-поведение. preventClose позволяет приложению включить собственный сценарий защиты несохраненной формы.
Формы и счетчики
DSFormGrid и DSFormGridItem формируют compact/comfortable responsive layout до четырех колонок с переходом в одну колонку на mobile. DSValidationSummary связывает ошибки с полями формы. DSCounterBadge визуально ограничивает значение через max, но сохраняет точное accessible name.
Modal и реестр приложения
Библиотека не импортирует файлы consumer-проекта. Каждый проект хранит реестр по принятому пути, например src/shared/ui/modal/modal.registry.tsx, и явно передаёт его provider.
// src/shared/ui/modal/modal.registry.tsx
import {
builtInModalRegistry,
defineModalRegistry,
type ModalControls,
} from '@efficiency-point/design-system';
import { EmployeeModal } from './EmployeeModal';
export type EmployeeModalPayload = {
employeeId: string;
};
export const modalRegistry = defineModalRegistry({
...builtInModalRegistry,
EmployeeModal: ({ employeeId, modalId, close }: EmployeeModalPayload & ModalControls) => (
<div data-modal-id={modalId}>
Сотрудник: {employeeId}
<button onClick={close}>Закрыть</button>
</div>
),
});
export type AppModalRegistry = typeof modalRegistry;// app providers
import { ModalSystemProvider } from '@efficiency-point/design-system';
import { modalRegistry } from '@/shared/ui/modal/modal.registry';
<ModalSystemProvider registry={modalRegistry}>
<App />
</ModalSystemProvider>const { openModal } = useModals<typeof modalRegistry>();
openModal(
'EmployeeModal',
{ employeeId: '42' },
{
title: 'Карточка сотрудника',
description: 'Заголовок и описание автоматически получит DSModalFrame.',
},
);title и description в третьем аргументе openModal доступны любой модалке:
их не нужно вручную прокидывать через renderer в DSModalFrame. Явно заданные
props самого DSModalFrame имеют приоритет. Старый вариант с передачей заголовка
через payload модалки также поддерживается.
Modal host поддерживает stack, закрытие верхней модалки по Escape, backdrop-close, focus trap и восстановление focus. Поведение Escape/backdrop настраивается в openModal.
Встроенные ConfirmModal и ErrorModal доступны через builtInModalRegistry, useConfirmModal и useErrorModal.
Напрямую экспортируемые DSModal и DSModalFrame — низкоуровневые части
модальной системы. Для прикладных сценариев рекомендуется реестр и useModals:
они обеспечивают stack, Escape/backdrop и восстановление focus согласованно.
Portal и floating menu
DSPortal переносит overlay-контент в существующий или автоматически созданный
контейнер в document.body. DSFloatingMenu позиционирует portal относительно
anchorRef, умеет совпадать с шириной anchor и автоматически выбирает верхнее
или нижнее размещение по свободному месту.
Меню измеряется после появления DOM-узла в portal, включая первый показ в
StrictMode. При изменении размера меню или anchor позиция пересчитывается через
ResizeObserver; если API недоступен, сохраняются первый расчёт и обработка
resize/scroll. Серверный рендер откладывает portal до монтирования в браузере.
Storybook-сценарии InitiallyOpen и ResizingContent проверяют первый показ
без прокрутки/изменения окна и переразмещение динамического содержимого.
<DSFloatingMenu anchorRef={buttonRef} matchAnchorWidth offset={4}>
<div role="menu">...</div>
</DSFloatingMenu>Публичные типы
Все component props, callback signatures, variant unions, store states, hook results, modal registry contracts и payload-типы экспортируются из корневого entry point.
Разработка и проверки
npm run lint
npm run typecheck
npm run test
npm run test:coverage
npm run build
npm run test:package
npm run build-storybook
npm run pack:checkПодготовка пакета
- Установить зависимости:
npm ci. - Запустить
npm run lint. - Запустить
npm run typecheck. - Запустить
npm run testи сохранить результаты в отчёт. - Запустить
npm run test:package. - Собрать Storybook:
npm run build-storybook. - Проверить содержимое архива:
npm run pack:check. - Указать финальные
name,version,license,repositoryи при необходимостиpublishConfigвpackage.json. - Создать архив:
npm pack. - Установить
.tgzв чистое React-приложение и проверить ESM/CJS, типы и CSS. - После проверки выполнить
npm publishв выбранный registry. Публикация и изменение версии выполняются отдельно и в текущую задачу не входят.
Подключение к другому проекту
- Установить опубликованный пакет или локальный tarball.
- Один раз импортировать
@efficiency-point/design-system/styles.cssв entry приложения. - Убедиться, что React/React DOM удовлетворяют peer range.
- Подключить
DSToastProvider, если используются toast. - Создать
shared/ui/modal/modal.registry.tsxи подключитьModalSystemProvider, если используются модалки. - Заменять локальные UI-компоненты постепенно, сохраняя controlled state и импортируя публичные props/payload-типы из пакета.
Регрессионная проверка progressive rendering в браузере: после npm run build-storybook запустите статический сервер каталога storybook-static, задайте STORYBOOK_URL (по умолчанию http://127.0.0.1:6088) и выполните node tests/data-table-progressive.mjs.
При обновлении с 0.2.x кнопки secondary становятся сливовыми. Приложения с собственной темой должны согласовать свои переопределения --color-background-button-secondary, --color-border-secondary и --color-hover-*; более поздние project CSS имеют приоритет. Brand остаётся совместимым именем основного акцента.
