npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-system
import { 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

Подготовка пакета

  1. Установить зависимости: npm ci.
  2. Запустить npm run lint.
  3. Запустить npm run typecheck.
  4. Запустить npm run test и сохранить результаты в отчёт.
  5. Запустить npm run test:package.
  6. Собрать Storybook: npm run build-storybook.
  7. Проверить содержимое архива: npm run pack:check.
  8. Указать финальные name, version, license, repository и при необходимости publishConfig в package.json.
  9. Создать архив: npm pack.
  10. Установить .tgz в чистое React-приложение и проверить ESM/CJS, типы и CSS.
  11. После проверки выполнить npm publish в выбранный registry. Публикация и изменение версии выполняются отдельно и в текущую задачу не входят.

Подключение к другому проекту

  1. Установить опубликованный пакет или локальный tarball.
  2. Один раз импортировать @efficiency-point/design-system/styles.css в entry приложения.
  3. Убедиться, что React/React DOM удовлетворяют peer range.
  4. Подключить DSToastProvider, если используются toast.
  5. Создать shared/ui/modal/modal.registry.tsx и подключить ModalSystemProvider, если используются модалки.
  6. Заменять локальные 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 остаётся совместимым именем основного акцента.