From e8dcb27a03c793e830639219009e3b0ef2ad6129 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 08:56:32 +0300 Subject: [PATCH 01/12] docs(design-system): add foundation specification --- .../features/design-system-foundation/spec.md | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 docs/features/design-system-foundation/spec.md diff --git a/docs/features/design-system-foundation/spec.md b/docs/features/design-system-foundation/spec.md new file mode 100644 index 0000000..9091099 --- /dev/null +++ b/docs/features/design-system-foundation/spec.md @@ -0,0 +1,168 @@ +# Design System Foundation + +## Статус + +Draft — ожидает ревью пользователя. + +## Цель + +Создать внутреннюю дизайн-систему MoexVibe поверх MUI, которая задаёт единый визуальный язык, +контролируемый набор UI-компонентов и правила их применения. Первая версия должна обслуживать web- +приложение MoexVibe и сохранять нейтральную к платформе модель токенов для будущих адаптеров, +включая Android. + +## Принципы + +- MUI является компонентным и accessibility-движком, но не определяет визуальную идентичность + MoexVibe. +- Визуальное направление: спокойный нейтральный финансовый интерфейс со сбалансированной плотностью. +- Текущие стили приложения не являются источником дизайн-решений. Их миграция выполняется после + foundation отдельными фичами. +- Дизайн-система не содержит бизнес-логику, API-клиенты, маршрутизацию и доменные компоненты. +- Docusaurus остаётся единственной опубликованной человекочитаемой документацией проекта. + +## Артефакты + +### Workspace-пакет + +В monorepo должен появиться внутренний пакет `@moex-vibe/design-system` со следующими границами: + +- platform-neutral токены без зависимостей от React и MUI; +- MUI-адаптер, создающий тему и CSS variables из токенов; +- Core UI и повторяемые product patterns; +- стабильные subpath exports для токенов, темы и компонентов. + +Продуктовый frontend использует компоненты дизайн-системы. Прямые импорты MUI разрешены только для +компоновки (`Box`, `Stack`, `Grid` и системные layout utilities). Интерактивные и визуально значимые +компоненты должны поступать из `@moex-vibe/design-system`. + +### Storybook + +Storybook является локальным и CI-стендом дизайн-системы. Он показывает варианты, состояния, +адаптивное поведение и accessibility-контракты компонентов, но не считается опубликованной +документацией для пользователей проекта. + +### Docusaurus + +В `apps/docs` должен появиться раздел «Дизайн-система» со страницами: + +- обзор и принципы; +- foundations: цвет, типографика, spacing, размеры, радиусы, elevation и motion; +- модель токенов и правила их изменения; +- каталог компонентов и product patterns; +- accessibility; +- contribution и governance. + +Каждая карточка компонента описывает: назначение и запреты, anatomy, разрешённые варианты, состояния, +keyboard behavior, accessibility, content rules, responsive behavior, корректные примеры и +антипаттерны. + +## Модель токенов + +Source of truth первой версии — типизированные TypeScript-объекты. Их схема и имена не должны +зависеть от MUI, React или CSS. Формат должен оставаться JSON-совместимым, чтобы позднее добавить +экспорт в DTCG/Android resources без изменения семантических имён. + +Токены состоят из трёх уровней с однонаправленными зависимостями: + +1. **Primitive/reference** — сырые палитры, шкалы spacing и размеров, типографика, радиусы, тени и + motion. Только на этом уровне допустимы абсолютные визуальные значения. +2. **Semantic/system** — роли: canvas, surface, text, border, action, feedback и финансовая семантика + positive/negative/neutral. Будущая тёмная тема переопределяет этот уровень, не меняя компоненты. +3. **Component** — устойчивые решения конкретных компонентов и вариантов. Они ссылаются на semantic + tokens и создаются только для повторяемых или тематизируемых решений, а не для каждого CSS- + свойства MUI. + +Приложение не должно использовать primitive tokens или произвольные визуальные значения напрямую. +Цвет не может быть единственным способом сообщить финансовое состояние. + +## Тема и визуальные основы + +- Первая версия включает только светлую тему. API темы должен допускать добавление новых color schemes + без изменения API компонентов. +- MUI theme использует CSS variables и получает palette, typography, spacing, shape, elevation, motion, + defaults, variants и style overrides из токенов. +- Основная плотность интерфейса — сбалансированная. Компактный вариант допускается как явный вариант + data-heavy компонентов, но не как глобальный режим v1. +- Все состояния focus-visible должны быть заметны; компоненты поддерживают keyboard navigation и + `prefers-reduced-motion` там, где используется анимация. +- Целевой уровень доступности — WCAG 2.2 AA для применимых web-компонентов. + +## Компонентная модель + +### Core UI v1 + +- Typography: `Text`, `Heading`, `Link`. +- Actions: `Button`, `IconButton`. +- Inputs: `TextField`, `Select`, `Checkbox`. +- Surfaces and labels: `Surface`, `Card`, `Chip`, `Badge`. +- Feedback: `Alert`, `Dialog`, `Skeleton`, `Progress`. + +Core UI ограничивает разрешённые variants и состояния MUI, сохраняет доступность и не содержит +доменной логики. + +### Product patterns v1 + +- Data display: `DataTable`, `Metric`, `Money`, `PriceChange`. +- Forms and filtering: `FormField`, `FilterBar`. +- Page states: `EmptyState`, `ErrorState`, `LoadingState`. + +Product pattern попадает в пакет, если он не знает бизнес-домен и имеет минимум два подтверждённых +места использования. Исключение — базовые интерактивные Core UI primitives, необходимые для +целостного API. + +Доменные компоненты, включая broker-, portfolio- и screener-specific композиции, остаются в +соответствующих FSD-слоях frontend. + +## Governance + +- Изменение значения primitive token не должно молча менять смысл semantic token. +- Новый token требует описания роли и проверки существующих эквивалентов. +- Новый variant или компонент требует documented use case; API «на всякий случай» не добавляется. +- Breaking changes внутреннего пакета фиксируются в changelog и миграционной заметке, даже пока пакет + не публикуется во внешний npm registry. +- Архитектура пакета и граница прямого использования MUI фиксируются отдельным ADR. + +## Проверки качества + +- Unit-тесты проверяют token contracts, уникальность имён, допустимые ссылки и отсутствие циклов. +- Type tests проверяют публичные exports и разрешённые component variants. +- Interaction tests проверяют keyboard и пользовательские состояния интерактивных компонентов. +- Storybook содержит stories для default, hover/focus reference, disabled, loading, error и responsive + состояний, когда они применимы. +- Автоматические accessibility-проверки выполняются для stories и не допускают серьёзных нарушений. +- Visual regression покрывает репрезентативные состояния Core UI и product patterns. +- CI собирает workspace-пакет, Storybook, frontend и Docusaurus; затронутые lint и tests проходят. + +## Вне scope + +- Тёмная тема и UI-переключатель темы. +- Android-адаптер, DTCG/JSON export и синхронизация с Figma. +- Публикация пакета во внешний npm registry. +- Backend-driven UI. +- Массовая миграция существующих страниц и удаление legacy CSS. +- Редизайн доменных экранов, графиков и визуализаций данных. + +## Последовательность внедрения + +1. Эта фича создаёт Design System Foundation и не переписывает продуктовые экраны. +2. Отдельная фича мигрирует один репрезентативный pilot-экран и возвращает подтверждённые изменения в + tokens/component API. +3. Остальные области мигрируются отдельными вертикальными срезами; legacy styles удаляются только + после миграции всех их потребителей. + +## Acceptance Criteria + +- [ ] `@moex-vibe/design-system` подключён как внутренний workspace-пакет с документированными public + exports. +- [ ] Реализованы три уровня platform-neutral TypeScript tokens и автоматические проверки их + контрактов. +- [ ] Light MUI theme полностью строится из токенов и предоставляет CSS variables. +- [ ] Реализован и задокументирован каталог Core UI v1 и product patterns v1. +- [ ] Прямое использование MUI ограничено documented allowlist для layout utilities. +- [ ] Storybook локально запускается, собирается в CI и содержит обязательные состояния компонентов. +- [ ] Accessibility и visual regression checks проходят для согласованного набора stories. +- [ ] В Docusaurus опубликован полный раздел дизайн-системы и правила выбора компонентов. +- [ ] Создан ADR о границах пакета, MUI-адаптере и будущих platform adapters. +- [ ] Frontend, docs и design-system package проходят build, lint и tests. +- [ ] Существующие продуктовые страницы визуально не мигрированы в рамках этой фичи. -- 2.47.2 From ec13fa8ca2f668344f76d49142b3cbcec6381e3f Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:13:19 +0300 Subject: [PATCH 02/12] docs(design-system): add foundation implementation plan --- .../features/design-system-foundation/plan.md | 643 ++++++++++++++++++ .../features/design-system-foundation/spec.md | 2 +- .../design-system-foundation/tasks.md | 68 ++ 3 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 docs/features/design-system-foundation/plan.md create mode 100644 docs/features/design-system-foundation/tasks.md diff --git a/docs/features/design-system-foundation/plan.md b/docs/features/design-system-foundation/plan.md new file mode 100644 index 0000000..5f6454c --- /dev/null +++ b/docs/features/design-system-foundation/plan.md @@ -0,0 +1,643 @@ +# Design System Foundation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Создать внутренний пакет `@moex-vibe/design-system` с трёхуровневыми токенами, MUI light theme, согласованным UI-каталогом, Storybook и опубликованными правилами. + +**Architecture:** Platform-neutral токены хранятся как JSON-compatible TypeScript records и разрешаются собственным проверяемым resolver. MUI-адаптер переводит semantic/component tokens в CSS-variable theme; React-компоненты доступны только через public exports пакета. Storybook служит локальным/CI workbench, Docusaurus — единственной опубликованной документацией. + +**Tech Stack:** TypeScript 5, React 18, MUI 6.5, TanStack Table 8, Vitest 4 browser mode, Storybook 10.2 (`@storybook/react-vite`), Playwright Chromium, Docusaurus 3.7. + +--- + +## Зафиксированные интерфейсы + +Пакет предоставляет subpath exports: + +```json +{ + ".": "./dist/index.js", + "./tokens": "./dist/tokens/index.js", + "./theme": "./dist/theme/index.js" +} +``` + +Token source использует плоские DTCG-подобные записи: + +```ts +type TokenType = 'color' | 'dimension' | 'fontFamily' | 'fontWeight' | 'duration' | 'cubicBezier' | 'shadow'; +type TokenValue = string | number | readonly number[] | readonly string[]; +type TokenDefinition = { readonly $type: TokenType; readonly $value: TokenValue }; +type TokenCollection = Readonly>; + +// Primitive содержит абсолютное значение. +'color.green.700': { $type: 'color', $value: '#176747' } +// Semantic/component содержит alias и никогда не дублирует primitive. +'color.action.primary': { $type: 'color', $value: '{color.green.700}' } +``` + +Публичный React API: + +```ts +type Tone = 'default' | 'secondary' | 'positive' | 'negative' | 'muted'; +type Density = 'balanced' | 'compact'; +type ActionVariant = 'primary' | 'secondary' | 'tertiary' | 'danger'; + +type TextProps = { variant?: 'body' | 'caption' | 'label' | 'numeric'; tone?: Tone }; +type HeadingProps = { level: 1 | 2 | 3 | 4 | 5 | 6; size?: 'display' | 'title' | 'section' | 'subsection' }; +type ButtonProps = { variant?: ActionVariant; size?: 'small' | 'medium'; loading?: boolean }; +type IconButtonProps = { label: string; size?: 'small' | 'medium' }; +type SurfaceProps = { padding?: 'none' | 'sm' | 'md' | 'lg'; elevation?: 'none' | 'sm' | 'md' }; +type DataTableProps = { table: Table; density?: Density; loading?: boolean; empty?: ReactNode; caption: string }; +type MoneyProps = { value: number; currency?: string; locale?: string; signDisplay?: 'auto' | 'always' | 'never' }; +type PriceChangeProps = { value: number; format?: 'percent' | 'money'; currency?: string; locale?: string }; +type MetricProps = { label: string; value: ReactNode; supportingText?: ReactNode; trend?: ReactNode }; +``` + +Остальные контракты фиксированы так: + +- `Link`: MUI Link без `color`, `variant`, `sx`; добавляет `tone` и сохраняет polymorphic `component`. +- `TextField`: MUI TextField без `color`, `variant`, `size`, `sx`; всегда `variant="outlined"` и + `size="medium"`. +- `Select`: `label`, `value`, `onChange(value: string)`, `options: { value; label; disabled? }[]`, + `error?`, `helperText?`, `disabled?`. +- `Checkbox`: `label`, `checked`, `onChange(checked: boolean)`, `disabled?`, `error?`. +- `Card`: Surface props плюс `header?`, `actions?`, `children`; не знает маршруты и домен. +- `Chip`: `label`, `tone: neutral|info|success|warning|error`, `onDelete?`. +- `Badge`: `value`, `max?`, `label`; decorative badge запрещён без accessible label. +- `Alert`: `severity: info|success|warning|error`, `title?`, `children`, `action?`. +- `Dialog`: `open`, `onClose`, обязательные `title`, `children`, `actions?`. +- `Skeleton`: `width?`, `height?`, `shape: text|rectangular|rounded|circular`. +- `Progress`: `label`, `value?`; отсутствие value означает indeterminate. +- `FormField`: `label`, `htmlFor`, `helperText?`, `error?`, `required?`, `children`. +- `FilterBar`: `children`, `actions?`; отвечает только за responsive layout. +- Page states: обязательный `title`, optional `description/action`; `LoadingState` дополнительно имеет + `label` и `size: section|page`. + +Все wrappers запрещают произвольные `color`, `variant`, `size` и `sx`, где они обходят контракт. +`Box`, `Stack`, `Grid` остаются полным и единственным прямым MUI allowlist во frontend. + +## Карта файлов + +- `packages/design-system/src/tokens/` — schema, три token collections, resolver и guards. +- `packages/design-system/src/theme/` — MUI augmentation, theme factory, provider, component overrides. +- `packages/design-system/src/components/` — Core UI и product patterns; один каталог на компонент. +- `packages/design-system/.storybook/` — единый provider, a11y и browser-test annotations. +- `apps/frontend/src/app/providers/AppProviders.tsx` — подключение provider пакета без миграции страниц. +- `apps/docs/docs/design-system/` — канонические правила; `apps/docs/docs/adr/ADR-016-design-system.md` — архитектурное решение. + +### Task 1: Pre-flight и workspace shell + +**Files:** +- Modify: `package.json` +- Create: `packages/design-system/package.json` +- Create: `packages/design-system/tsconfig.json` +- Create: `packages/design-system/vitest.config.ts` +- Create: `packages/design-system/.eslintrc.cjs` +- Create: `packages/design-system/src/test/setup.ts` +- Create: `packages/design-system/src/index.ts` + +- [ ] **Step 1: Проверить pre-flight до любых implementation edits** + +Run: + +```bash +git branch --show-current +git status --short +npm run test:backend +npm run test:frontend +npm run lint +npm run build:backend +npm run build:frontend +``` + +Expected: ветка `codex/design-system-foundation`, чистый worktree и все команды PASS. При сбое +остановиться и классифицировать его до изменения кода. + +- [ ] **Step 2: Добавить workspace и root scripts** + +В `package.json` добавить `packages/design-system` в `workspaces` и scripts: + +```json +{ + "build:design-system": "npm run build -w packages/design-system", + "test:design-system": "npm run test -w packages/design-system", + "lint:design-system": "npm run lint -w packages/design-system", + "storybook": "npm run storybook -w packages/design-system", + "build:storybook": "npm run build-storybook -w packages/design-system", + "test:storybook": "npm run test:storybook -w packages/design-system" +} +``` + +- [ ] **Step 3: Создать package manifest и TypeScript build** + +`packages/design-system/package.json` должен быть private ESM package версии `0.1.0`, иметь `files: +["dist"]`, exports из раздела выше, peer dependencies на React 18, MUI 6 и TanStack Table 8, dev +dependencies Storybook `^10.2.9`, `@vitest/browser-playwright`/Vitest `^4.1.8` и Playwright, scripts +`build: tsc -p tsconfig.json`, `test: vitest run --project unit`, `storybook: storybook dev -p 6006 +--no-open`, `build-storybook: storybook build`, `test:storybook: vitest run --project storybook`, +`lint: eslint "src/**/*.{ts,tsx}"`. + +`tsconfig.json` компилирует `src` в `dist`, включает declarations, `jsx: react-jsx`, strict и +`moduleResolution: bundler`. `vitest.config.ts` сначала содержит unit project с jsdom и +`passWithNoTests: true`; флаг удаляется после Task 2. Test setup подключает jest-dom и matchMedia mock. +Package ESLint config проверяет TypeScript/React hooks, но не включает frontend FSD zones. Root `lint` +дополняется `lint:design-system`. + +- [ ] **Step 4: Установить зависимости и проверить пустой пакет** + +Run: + +```bash +npm install +npm run build:design-system +npm run test:design-system +``` + +Expected: package-lock обновлён, build PASS, Vitest PASS с `passWithNoTests: true`. + +- [ ] **Step 5: Commit** + +```bash +git add package.json package-lock.json packages/design-system +git commit -m "build(design-system): add workspace package" +``` + +### Task 2: Token schema и resolver (TDD) + +**Files:** +- Create: `packages/design-system/src/tokens/types.ts` +- Create: `packages/design-system/src/tokens/resolveToken.ts` +- Test: `packages/design-system/src/tokens/resolveToken.test.ts` + +- [ ] **Step 1: Написать failing tests** + +```ts +it('resolves an alias chain and preserves the declared type', () => { + const tokens = { + base: { $type: 'color', $value: '#176747' }, + semantic: { $type: 'color', $value: '{base}' }, + component: { $type: 'color', $value: '{semantic}' }, + } satisfies TokenCollection; + expect(resolveToken(tokens, 'component')).toEqual({ type: 'color', value: '#176747' }); +}); + +it.each([ + ['missing alias', { a: { $type: 'color', $value: '{missing}' } }, /Unknown token/], + ['cycle', { a: { $type: 'color', $value: '{b}' }, b: { $type: 'color', $value: '{a}' } }, /cycle/i], + ['type mismatch', { a: { $type: 'color', $value: '#fff' }, b: { $type: 'dimension', $value: '{a}' } }, /type/i], +])('rejects %s', (_name, tokens, error) => expect(() => resolveToken(tokens, 'a')).toThrow(error)); +``` + +- [ ] **Step 2: Запустить тест и подтвердить RED** + +Run: `npm run test:design-system -- resolveToken.test.ts` + +Expected: FAIL, module/functions отсутствуют. + +- [ ] **Step 3: Реализовать schema и resolver** + +Resolver распознаёт только полную alias-строку `/^\{([^}]+)\}$/`, хранит visited path, проверяет +существование target и равенство `$type`, возвращает `{ type, value }`. Смешанные строки вроде +`calc({space.2} * 2)` запрещены. + +- [ ] **Step 4: Запустить тест и commit** + +Run: `npm run test:design-system -- resolveToken.test.ts` + +Expected: PASS. + +```bash +git add packages/design-system/src/tokens +git commit -m "feat(design-system): add token contracts" +``` + +### Task 3: Три уровня токенов + +**Files:** +- Create: `packages/design-system/src/tokens/primitives.ts` +- Create: `packages/design-system/src/tokens/semantic.light.ts` +- Create: `packages/design-system/src/tokens/components.ts` +- Create: `packages/design-system/src/tokens/index.ts` +- Test: `packages/design-system/src/tokens/tokens.test.ts` + +- [ ] **Step 1: Написать token integrity tests** + +Тест объединяет три collections, резолвит каждый token, проверяет уникальные имена и инварианты: + +```ts +expect(Object.keys(primitiveTokens).every((name) => !isAlias(primitiveTokens[name].$value))).toBe(true); +expect(Object.keys(semanticLightTokens).every((name) => isAlias(semanticLightTokens[name].$value))).toBe(true); +expect(Object.keys(componentTokens).every((name) => isAlias(componentTokens[name].$value))).toBe(true); +for (const name of Object.keys(allTokens)) expect(() => resolveToken(allTokens, name)).not.toThrow(); +``` + +- [ ] **Step 2: Запустить тест и подтвердить RED** + +Run: `npm run test:design-system -- tokens.test.ts` + +Expected: FAIL, collections отсутствуют. + +- [ ] **Step 3: Добавить минимальный полный набор foundations** + +Primitive groups: `color.neutral.{0,50,100,200,400,600,800,900}`, `color.green.{50,100,600,700,800}`, +`color.red.{50,600,700}`, `color.amber.{50,600}`, `color.blue.{50,600}`, `space.{0,1,2,3,4,5,6,8,10,12}` +на 4px grid, `radius.{none,sm,md,lg,pill}`, `font.family.{sans,mono}`, `font.size.{100..700}`, +`font.weight.{regular,medium,semibold,bold}`, `lineHeight.{tight,normal,relaxed}`, `shadow.{none,sm,md}`, +`duration.{instant,fast,normal}`, `easing.standard`, `size.control.{sm,md}`. + +`font.family.sans` использует Inter с system-ui fallback. Webfont weights 400/500/600/700 подключаются +один раз во frontend shell в Task 10; Roboto не становится частью нового foundation. + +Semantic groups: `color.canvas`, `color.surface.{default,subtle,raised}`, `color.text.{primary,secondary, +disabled,inverse}`, `color.border.{subtle,default,strong,focus}`, `color.action.{primary,primaryHover, +secondary,danger}`, `color.feedback.{info,success,warning,error}`, `color.finance.{positive,negative, +neutral}`, plus semantic typography, focus ring, spacing and control sizes. + +Component groups cover только API v1: button, iconButton, field, checkbox, surface, card, chip, badge, +alert, dialog, skeleton, progress, table, metric и pageState. + +- [ ] **Step 4: Проверить GREEN и запрет hardcoded values вне primitives** + +Run: `npm run test:design-system -- tokens.test.ts` + +Expected: PASS. Дополнительный test scan подтверждает, что `semantic.light.ts` и `components.ts` не +содержат hex/rgb/px literals. + +- [ ] **Step 5: Commit** + +```bash +git add packages/design-system/src/tokens +git commit -m "feat(design-system): define three-level tokens" +``` + +### Task 4: MUI adapter и provider (TDD) + +**Files:** +- Create: `packages/design-system/src/theme/createMoexVibeTheme.ts` +- Create: `packages/design-system/src/theme/MoexVibeThemeProvider.tsx` +- Create: `packages/design-system/src/theme/mui.d.ts` +- Create: `packages/design-system/src/theme/index.ts` +- Test: `packages/design-system/src/theme/createMoexVibeTheme.test.ts` + +- [ ] **Step 1: Написать failing theme contract tests** + +```ts +const theme = createMoexVibeTheme(); +expect(theme.cssVarPrefix).toBe('mv'); +expect(theme.colorSchemes.light.palette.primary.main).toBe(resolveValue('color.action.primary')); +expect(theme.typography.body1.fontFamily).toContain('Inter'); +expect(theme.shape.borderRadius).toBe(resolveValue('radius.md')); +expect(theme.components?.MuiButton?.defaultProps).toMatchObject({ disableElevation: true }); +``` + +- [ ] **Step 2: Подтвердить RED** + +Run: `npm run test:design-system -- createMoexVibeTheme.test.ts` + +Expected: FAIL, factory отсутствует. + +- [ ] **Step 3: Реализовать adapter** + +Использовать `createTheme({ cssVariables: { cssVarPrefix: 'mv' }, colorSchemes: { light: ... } })`. +Theme получает palette, typography, spacing, shape, shadows, transitions и component overrides только +через `resolveValue`. `mui.d.ts` включает `themeCssVarsAugmentation` и добавляет `finance` palette roles. + +- [ ] **Step 4: Реализовать provider** + +`MoexVibeThemeProvider` оборачивает MUI `ThemeProvider` и `CssBaseline`, принимает только `children`; +mode API в v1 не экспортируется. + +- [ ] **Step 5: Проверить и commit** + +Run: `npm run test:design-system -- createMoexVibeTheme.test.ts && npm run build:design-system` + +Expected: PASS. + +```bash +git add packages/design-system/src/theme packages/design-system/src/tokens +git commit -m "feat(design-system): add MUI theme adapter" +``` + +### Task 5: Storybook и automated story checks + +**Files:** +- Create: `packages/design-system/.storybook/main.ts` +- Create: `packages/design-system/.storybook/preview.tsx` +- Create: `packages/design-system/.storybook/vitest.setup.ts` +- Modify: `packages/design-system/vitest.config.ts` +- Modify: `packages/design-system/package.json` + +- [ ] **Step 1: Настроить Storybook 10.2** + +```ts +const config: StorybookConfig = { + framework: '@storybook/react-vite', + stories: ['../src/**/*.stories.@(ts|tsx)'], + addons: ['@storybook/addon-a11y', '@storybook/addon-vitest'], +}; +``` + +`preview.tsx` добавляет decorator `MoexVibeThemeProvider`, background `color.canvas` и параметры +`a11y.test = 'error'`, layout `centered` по умолчанию. + +- [ ] **Step 2: Добавить Storybook Vitest browser project** + +Использовать `storybookTest`, `@vitest/browser-playwright` и headless Chromium; setup регистрирует +`@storybook/addon-a11y/preview` и project annotations через `setProjectAnnotations`. + +- [ ] **Step 3: Проверить стенд** + +Run: + +```bash +npx playwright install chromium +npm run build:storybook +``` + +Expected: static Storybook build PASS. Первый `test:storybook` запускается после появления stories в +Task 6. + +- [ ] **Step 4: Commit** + +```bash +git add package.json package-lock.json packages/design-system +git commit -m "build(design-system): configure Storybook workbench" +``` + +### Task 6: Typography и actions (TDD) + +**Files:** +- Create: `packages/design-system/src/components/{Text,Heading,Link,Button,IconButton}/` +- Test: colocated `*.test.tsx` +- Stories: colocated `*.stories.tsx` + +- [ ] **Step 1: Написать failing interaction/type tests** + +Проверить semantic heading level, tone mapping, external link rel, loading button disabled state и +progress label, обязательный accessible label IconButton. Type tests отклоняют `color`, произвольный +`variant` и `sx`. + +- [ ] **Step 2: Подтвердить RED** + +Run: `npm run test:design-system -- Text Heading Link Button IconButton` + +Expected: FAIL, exports отсутствуют. + +- [ ] **Step 3: Реализовать компоненты по public contracts** + +Каждый каталог содержит component, props и index. `Button loading` сохраняет ширину, блокирует повторный +click и имеет `aria-busy`; `IconButton` всегда устанавливает `aria-label={label}`. + +- [ ] **Step 4: Добавить stories и проверить GREEN** + +Stories: все variants/sizes/tones, disabled, loading, long Russian label, keyboard focus reference. + +Run: `npm run test:design-system && npm run test:storybook && npm run build:storybook` + +Expected: PASS без a11y violations. + +- [ ] **Step 5: Commit** + +```bash +git add packages/design-system/src +git commit -m "feat(design-system): add typography and actions" +``` + +### Task 7: Inputs, surfaces и feedback (TDD) + +**Files:** +- Create: `packages/design-system/src/components/{TextField,Select,Checkbox,Surface,Card,Chip,Badge,Alert,Dialog,Skeleton,Progress}/` +- Test/Stories: colocated `*.test.tsx`, `*.stories.tsx` + +- [ ] **Step 1: Написать failing contract tests** + +Проверить label/helper/error association, Select options и keyboard opening, Checkbox label click, +Surface padding/elevation mapping, Dialog focus/escape/label, Alert role, determinate/indeterminate +Progress accessible name и Skeleton `aria-hidden`. + +- [ ] **Step 2: Подтвердить RED** + +Run: `npm run test:design-system -- TextField Select Checkbox Surface Card Chip Badge Alert Dialog Skeleton Progress` + +Expected: FAIL, components отсутствуют. + +- [ ] **Step 3: Реализовать ограниченные wrappers** + +`Select` принимает `{ value: string; label: string; disabled?: boolean }[]`; `Checkbox` требует `label`; +`Dialog` требует `title` и управляется через `open/onClose`; Surface/Card не принимают raw elevation. + +- [ ] **Step 4: Stories, GREEN и commit** + +Stories покрывают default/focus/disabled/error/loading, длинный русский текст и narrow viewport. + +Run: `npm run test:design-system && npm run test:storybook && npm run build:storybook` + +Expected: PASS. + +```bash +git add packages/design-system/src +git commit -m "feat(design-system): add controls and feedback" +``` + +### Task 8: Финансовые data patterns (TDD) + +**Files:** +- Create: `packages/design-system/src/components/{DataTable,Metric,Money,PriceChange}/` +- Test/Stories: colocated `*.test.tsx`, `*.stories.tsx` +- Test: `packages/design-system/src/visual/financial-patterns.visual.test.ts` + +- [ ] **Step 1: Написать failing tests** + +Проверить table caption, semantic headers, sortable button `aria-sort`, balanced/compact density, loading +и empty states; `Money` через `Intl.NumberFormat`; `PriceChange` знак, текстовое направление и не только +цвет; `Metric` composition. + +- [ ] **Step 2: Подтвердить RED** + +Run: `npm run test:design-system -- DataTable Metric Money PriceChange` + +Expected: FAIL. + +- [ ] **Step 3: Реализовать patterns** + +`DataTable` принимает готовый TanStack `Table` и не владеет server pagination/sorting. Defaults: +`density="balanced"`, locale `ru-RU`, currency `RUB`. Negative/positive output включает видимый знак и +screen-reader label. + +- [ ] **Step 4: Stories, visual baselines и commit** + +Canonical screenshot cases: balanced table, compact table, positive/negative metrics, empty/loading. +Browser test запускает portable stories через `composeStories`, затем делает +`expect(page.getByTestId('visual-root')).toMatchScreenshot('.png')`; baselines коммитятся +рядом с visual test. Обновление baseline допустимо только вместе с объяснением визуального изменения. + +Run: `npm run test:design-system && npm run test:storybook` + +Expected: interaction, a11y и screenshot checks PASS. + +```bash +git add packages/design-system/src +git commit -m "feat(design-system): add financial data patterns" +``` + +### Task 9: Form и page-state patterns (TDD) + +**Files:** +- Create: `packages/design-system/src/components/{FormField,FilterBar,EmptyState,ErrorState,LoadingState}/` +- Test/Stories: colocated `*.test.tsx`, `*.stories.tsx` + +- [ ] **Step 1: Написать failing tests** + +Проверить `htmlFor`/description/error association FormField, wrapping and action placement FilterBar, +semantic heading/action для Empty/Error, `aria-live="polite"` LoadingState и reduced-motion rendering. + +- [ ] **Step 2: RED, implementation, GREEN** + +Run before: `npm run test:design-system -- FormField FilterBar EmptyState ErrorState LoadingState` + +Expected before: FAIL. Реализовать только layout/semantics без API/domain knowledge. + +Run after: `npm run test:design-system && npm run test:storybook` + +Expected after: PASS. + +- [ ] **Step 3: Commit** + +```bash +git add packages/design-system/src +git commit -m "feat(design-system): add form and page-state patterns" +``` + +### Task 10: Frontend integration и MUI boundary + +**Files:** +- Modify: `apps/frontend/package.json` +- Modify: `apps/frontend/tsconfig.json` +- Modify: `apps/frontend/vite.config.ts` +- Modify: `apps/frontend/src/app/providers/AppProviders.tsx` +- Modify: `apps/frontend/.eslintrc.cjs` +- Delete: `apps/frontend/src/app/styles/theme.ts` +- Test: `apps/frontend/src/app/providers/AppProviders.test.tsx` + +- [ ] **Step 1: Написать failing provider test** + +Render `AppProviders`, assert generated `--mv-palette-primary-main` exists and Query/Session providers +по-прежнему доступны. Test не проверяет редизайн страниц. + +- [ ] **Step 2: Подключить workspace package** + +Добавить dependency, `@fontsource/inter` и aliases к source для dev/test; заменить MUI +ThemeProvider/CssBaseline на `MoexVibeThemeProvider`. Удалить старый локальный theme и импортировать +Inter 400/500/600/700 в provider entry ровно один раз. + +- [ ] **Step 3: Зафиксировать ESLint allowlist** + +Во frontend запретить root import `@mui/material` и subpaths через `no-restricted-imports`; исключить +только `@mui/material/Box`, `Stack`, `Grid`. Сам пакет design-system использует отдельный lint config. + +- [ ] **Step 4: Проверить отсутствие визуальной миграции** + +Run: + +```bash +npm run test:frontend +npm run lint -w apps/frontend +npm run build:design-system +npm run build:frontend +``` + +Expected: PASS; legacy `styles.css` и product component markup не изменены. + +- [ ] **Step 5: Commit** + +```bash +git add apps/frontend packages/design-system package.json package-lock.json +git commit -m "feat(frontend): connect design system foundation" +``` + +### Task 11: Docusaurus и ADR + +**Files:** +- Create: `apps/docs/docs/design-system/{overview,foundations,tokens,components,patterns,accessibility,governance}.md` +- Create: `apps/docs/docs/adr/ADR-016-design-system.md` +- Create: `packages/design-system/CHANGELOG.md` +- Modify: `apps/docs/sidebars.ts` +- Modify: `apps/docs/docs/adr/index.md` +- Modify: `apps/docs/docs/frontend/styling.md` + +- [ ] **Step 1: Написать канонические правила** + +Документы фиксируют light-only v1, balanced density, три token levels, MUI allowlist, каталог, +component-card template, WCAG 2.2 AA и promotion/change process. Components page содержит матрицу +«задача → компонент → не использовать» и полную карточку обязательного шаблона для каждого public +export. `CHANGELOG.md` начинается с `0.1.0` и документирует public API foundation; governance описывает +обязательную migration note для будущих breaking changes. + +- [ ] **Step 2: Добавить ADR-016** + +ADR содержит Context, варианты theme-only/hybrid/full-wrapper, решение hybrid, последствия, Storybook +роль и будущие DTCG/Android adapters. + +- [ ] **Step 3: Обновить sidebar и проверить docs** + +Run: `npm run build:docs` + +Expected: PASS без broken links; новый раздел виден как отдельная категория «Дизайн-система». + +- [ ] **Step 4: Commit** + +```bash +git add apps/docs packages/design-system/CHANGELOG.md +git commit -m "docs(design-system): publish usage guidelines" +``` + +### Task 12: CI, visual checks и финальная верификация + +**Files:** +- Modify: `.gitea/workflows/ci.yml` +- Modify: `README.md` +- Modify: `docs/features/design-system-foundation/tasks.md` + +- [ ] **Step 1: Добавить CI gates** + +После `npm ci` установить Chromium `npx playwright install --with-deps chromium`; добавить design-system +unit/story tests, Storybook build, design-system build и docs build. Сохранять Storybook static build и +visual diff через `actions/upload-artifact@v4` с `if: failure()`. + +- [ ] **Step 2: Обновить команды README** + +Добавить `npm run storybook`, `build:storybook`, `test:design-system`, `test:storybook`, +`build:design-system` и пояснить, что Docusaurus — published docs, Storybook — engineering workbench. + +- [ ] **Step 3: Запустить полный DoD** + +```bash +npm run format:check +npm run lint +npm run test:backend +npm run test:frontend +npm run test:design-system +npm run test:storybook +npm run build:backend +npm run build:design-system +npm run build:storybook +npm run build:frontend +npm run build:docs +git diff --check +git status --short +``` + +Expected: все команды PASS; status содержит только намеренные изменения до финального commit. + +- [ ] **Step 4: Обновить SDD tasks и запросить code review** + +Отметить выполненные checkbox в `tasks.md`, применить `superpowers:requesting-code-review`, исправить +только подтверждённые scope issues и повторить полный DoD. + +- [ ] **Step 5: Final commit** + +```bash +git add .gitea/workflows/ci.yml README.md docs/features/design-system-foundation +git commit -m "ci(design-system): enforce foundation quality gates" +``` diff --git a/docs/features/design-system-foundation/spec.md b/docs/features/design-system-foundation/spec.md index 9091099..308f7e3 100644 --- a/docs/features/design-system-foundation/spec.md +++ b/docs/features/design-system-foundation/spec.md @@ -2,7 +2,7 @@ ## Статус -Draft — ожидает ревью пользователя. +Approved — утверждена пользователем 2026-06-21. ## Цель diff --git a/docs/features/design-system-foundation/tasks.md b/docs/features/design-system-foundation/tasks.md new file mode 100644 index 0000000..cc07d8d --- /dev/null +++ b/docs/features/design-system-foundation/tasks.md @@ -0,0 +1,68 @@ +# Design System Foundation — Tasks + +Исполнять по `plan.md` последовательно, используя TDD и отмечая checkbox только после успешной +проверки соответствующего шага. + +## 1. Pre-flight и workspace + +- [ ] Подтвердить ветку, чистый worktree и зелёные baseline tests/lint/build. +- [ ] Добавить `packages/design-system` в npm workspaces и root scripts. +- [ ] Создать package manifest, TypeScript/Vitest/ESLint configs и test setup. +- [ ] Установить зависимости; проверить пустой package build/test/lint. +- [ ] Закоммитить workspace shell. + +## 2. Tokens + +- [ ] Написать RED-тесты alias resolver: chain, missing target, cycle, type mismatch. +- [ ] Реализовать token schema и resolver; получить GREEN. +- [ ] Написать RED-тесты целостности трёх token levels. +- [ ] Добавить primitive/reference tokens. +- [ ] Добавить light semantic tokens только через aliases. +- [ ] Добавить component tokens только через semantic aliases. +- [ ] Проверить guards, build и отсутствие hardcoded values вне primitives. +- [ ] Закоммитить token foundation. + +## 3. MUI adapter и Storybook + +- [ ] Написать RED-тесты MUI theme contract. +- [ ] Реализовать `createMoexVibeTheme`, type augmentation и provider; получить GREEN. +- [ ] Настроить Storybook React/Vite с общим theme decorator. +- [ ] Подключить addon-a11y и Vitest browser project с Playwright Chromium. +- [ ] Проверить package и static Storybook build. +- [ ] Закоммитить theme adapter и Storybook workbench. + +## 4. Core UI + +- [ ] Реализовать через TDD `Text`, `Heading`, `Link`, `Button`, `IconButton`. +- [ ] Добавить их stories, type checks и a11y checks. +- [ ] Реализовать через TDD `TextField`, `Select`, `Checkbox`. +- [ ] Реализовать через TDD `Surface`, `Card`, `Chip`, `Badge`. +- [ ] Реализовать через TDD `Alert`, `Dialog`, `Skeleton`, `Progress`. +- [ ] Добавить обязательные states, responsive stories и visual baselines. +- [ ] Проверить unit/story/a11y/build gates и закоммитить Core UI. + +## 5. Product patterns + +- [ ] Реализовать через TDD `DataTable`, включая caption, sorting semantics и density variants. +- [ ] Реализовать через TDD `Metric`, `Money`, `PriceChange` без color-only semantics. +- [ ] Добавить financial stories и стабильные screenshot baselines. +- [ ] Реализовать через TDD `FormField` и `FilterBar`. +- [ ] Реализовать через TDD `EmptyState`, `ErrorState`, `LoadingState`. +- [ ] Проверить unit/story/a11y/visual gates и закоммитить patterns. + +## 6. Интеграция и governance + +- [ ] Написать RED-тест frontend provider integration. +- [ ] Подключить workspace package и Inter во frontend shell; удалить локальную MUI theme. +- [ ] Enforce ESLint allowlist для `Box`, `Stack`, `Grid`. +- [ ] Подтвердить, что product pages и legacy CSS не мигрированы. +- [ ] Создать Docusaurus-раздел с foundations, catalog, rules и accessibility. +- [ ] Создать ADR-016 и changelog `0.1.0`. +- [ ] Обновить sidebar, styling docs и README; собрать docs. + +## 7. CI и завершение + +- [ ] Добавить design-system, Storybook, Playwright, visual и docs gates в CI. +- [ ] Запустить полный DoD из Task 12 `plan.md`. +- [ ] Запросить code review и устранить подтверждённые scope issues. +- [ ] Повторить полный DoD, отметить все tasks и сделать финальный commit. -- 2.47.2 From 129282d634116b2cd504fa7643d7308574db9fb0 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:25:18 +0300 Subject: [PATCH 03/12] feat(design-system): add token schema and resolver --- package-lock.json | 3172 +++++++++++++++-- package.json | 9 +- .../src/tokens/resolveToken.test.ts | 30 + .../design-system/src/tokens/resolveToken.ts | 35 + packages/design-system/src/tokens/types.ts | 12 + 5 files changed, 2994 insertions(+), 264 deletions(-) create mode 100644 packages/design-system/src/tokens/resolveToken.test.ts create mode 100644 packages/design-system/src/tokens/resolveToken.ts create mode 100644 packages/design-system/src/tokens/types.ts diff --git a/package-lock.json b/package-lock.json index 2fbc649..312adbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "workspaces": [ "apps/backend", "apps/frontend", - "apps/docs" + "apps/docs", + "packages/design-system" ], "devDependencies": { "husky": "^9.1.7", @@ -554,236 +555,6 @@ "react-hook-form": "^7.0.0" } }, - "apps/frontend/node_modules/@mui/core-downloads-tracker": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", - "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "apps/frontend/node_modules/@mui/icons-material": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.5.0.tgz", - "integrity": "sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^6.5.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/frontend/node_modules/@mui/material": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", - "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.5.0", - "@mui/system": "^6.5.0", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", - "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.12", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^19.0.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.5.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@mui/material-pigment-css": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "apps/frontend/node_modules/@mui/material/node_modules/@mui/system": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", - "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.9", - "@mui/styled-engine": "^6.5.0", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "apps/frontend/node_modules/@mui/material/node_modules/@mui/system/node_modules/@mui/private-theming": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", - "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.9", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/frontend/node_modules/@mui/material/node_modules/@mui/system/node_modules/@mui/styled-engine": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", - "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.5", - "@emotion/serialize": "^1.3.3", - "@emotion/sheet": "^1.4.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "apps/frontend/node_modules/@mui/material/node_modules/@mui/types": { - "version": "7.2.24", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", - "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "apps/frontend/node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", - "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/types": "~7.2.24", - "@types/prop-types": "^15.7.14", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^19.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "apps/frontend/node_modules/@types/node": { "version": "25.9.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", @@ -1005,12 +776,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "apps/frontend/node_modules/react-is": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "license": "MIT" - }, "apps/frontend/node_modules/std-env": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", @@ -3622,6 +3387,13 @@ "node": ">=6.9.0" } }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@borewit/text-codec": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", @@ -7287,6 +7059,110 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.7.0.tgz", + "integrity": "sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^13.0.1", + "react-docgen-typescript": "^2.2.2" + }, + "peerDependencies": { + "typescript": ">= 4.3.x", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -7640,6 +7516,10 @@ "resolved": "apps/backend", "link": true }, + "node_modules/@moex-vibe/design-system": { + "resolved": "packages/design-system", + "link": true + }, "node_modules/@moex-vibe/docs": { "resolved": "apps/docs", "link": true @@ -7673,6 +7553,248 @@ "dev": true, "license": "MIT" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", + "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.5.0.tgz", + "integrity": "sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", + "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.5.0", + "@mui/system": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.9", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", + "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", + "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.9", + "@mui/styled-engine": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", @@ -8284,6 +8406,371 @@ "dev": true, "license": "MIT" }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.127.0.tgz", + "integrity": "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.127.0.tgz", + "integrity": "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.127.0.tgz", + "integrity": "sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.127.0.tgz", + "integrity": "sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.127.0.tgz", + "integrity": "sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.127.0.tgz", + "integrity": "sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.127.0.tgz", + "integrity": "sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.127.0.tgz", + "integrity": "sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.127.0.tgz", + "integrity": "sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.127.0.tgz", + "integrity": "sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.127.0.tgz", + "integrity": "sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.127.0.tgz", + "integrity": "sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.127.0.tgz", + "integrity": "sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.127.0.tgz", + "integrity": "sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.127.0.tgz", + "integrity": "sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.127.0.tgz", + "integrity": "sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.127.0.tgz", + "integrity": "sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.127.0.tgz", + "integrity": "sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.127.0.tgz", + "integrity": "sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.127.0.tgz", + "integrity": "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@oxc-project/types": { "version": "0.133.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", @@ -8294,6 +8781,311 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz", + "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz", + "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz", + "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz", + "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz", + "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz", + "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz", + "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz", + "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz", + "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz", + "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz", + "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz", + "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz", + "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz", + "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz", + "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz", + "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz", + "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.0", + "@emnapi/runtime": "1.11.0", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz", + "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.3.tgz", + "integrity": "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -9206,7 +9998,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.62.0", @@ -9220,7 +10013,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.62.0", @@ -9234,7 +10028,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.62.0", @@ -9248,7 +10043,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.62.0", @@ -9262,7 +10058,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.62.0", @@ -9276,7 +10073,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.62.0", @@ -9290,7 +10088,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.62.0", @@ -9304,7 +10103,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.62.0", @@ -9318,7 +10118,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.62.0", @@ -9332,7 +10133,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-gnu": { "version": "4.62.0", @@ -9346,7 +10148,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-musl": { "version": "4.62.0", @@ -9360,7 +10163,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.62.0", @@ -9374,7 +10178,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-musl": { "version": "4.62.0", @@ -9388,7 +10193,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.62.0", @@ -9402,7 +10208,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.62.0", @@ -9416,7 +10223,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.62.0", @@ -9430,7 +10238,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.62.0", @@ -9444,7 +10253,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.62.0", @@ -9458,7 +10268,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openbsd-x64": { "version": "4.62.0", @@ -9472,7 +10283,8 @@ "optional": true, "os": [ "openbsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.62.0", @@ -9486,7 +10298,8 @@ "optional": true, "os": [ "openharmony" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.62.0", @@ -9500,7 +10313,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.62.0", @@ -9514,7 +10328,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.62.0", @@ -9528,7 +10343,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.62.0", @@ -9542,7 +10358,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rtsao/scc": { "version": "1.1.0", @@ -9608,6 +10425,186 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@storybook/addon-a11y": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.4.6.tgz", + "integrity": "sha512-XCJy+f0DFOiCgUU9knRDlLDxVFI+AAQ3/wE/NF85zB9iDPPS2DwkSN+mas3zDgHt66zhN8Cq3/UiyCDUweV9Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "axe-core": "^4.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.4.6" + } + }, + "node_modules/@storybook/builder-vite": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.4.6.tgz", + "integrity": "sha512-BHBtD81HiXUiDQz/CaFynLtWmm7AFUQn8VnXuHipZ8KlnUANopa4yqdVuy/Gwz8ub254uFI5NMZsW/KlgWNgNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/csf-plugin": "10.4.6", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.4.6", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.4.6.tgz", + "integrity": "sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unplugin": "^2.3.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "esbuild": "*", + "rollup": "*", + "storybook": "^10.4.6", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/icons": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.0.2.tgz", + "integrity": "sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@storybook/react": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.4.6.tgz", + "integrity": "sha512-9Y7YecrVFe1/01KYjfOLxVqTg2Aq+IO6TEv6sC2U0PfD0AWCSCmQ91QqgBpN/XW4aFFWoiZNinyXMUlU8zxy2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/react-dom-shim": "10.4.6", + "react-docgen": "^8.0.2", + "react-docgen-typescript": "^2.2.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.4.6", + "typescript": ">= 4.9.x" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.6.tgz", + "integrity": "sha512-iGNmKzrq9vgl2PDrYAnZKI+yvac3Ym+lJXXuQaqlFRS23zA5MNm4EBX+rAG7WulqchoK6NaZ0KQOs2mAgEpTMg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.4.6" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/react-vite": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.4.6.tgz", + "integrity": "sha512-0arEQtybqGYXHbXpTot+Wv9YtG+V5Vp43QayXavPKQ20M8mpEzhyCPKd0EhqMGSC1Z1UEt0hm365WUBhI9LfKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", + "@rollup/pluginutils": "^5.0.2", + "@storybook/builder-vite": "10.4.6", + "@storybook/react": "10.4.6", + "empathic": "^2.0.0", + "magic-string": "^0.30.0", + "react-docgen": "^8.0.0", + "resolve": "^1.22.8", + "tsconfig-paths": "^4.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.4.6", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -10762,6 +11759,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -11058,6 +12062,13 @@ "@types/react": "*" } }, + "node_modules/@types/resolve": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", + "integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -11986,6 +12997,13 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webcontainer/env": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webcontainer/env/-/env-1.1.1.tgz", + "integrity": "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==", + "dev": true, + "license": "MIT" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -12488,6 +13506,19 @@ "node": "*" } }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", @@ -12593,6 +13624,16 @@ "node": ">= 6.0.0" } }, + "node_modules/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", @@ -13108,6 +14149,22 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -15617,6 +16674,36 @@ "node": ">=16.0.0" } }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -19964,6 +21051,41 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -25293,6 +26415,85 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxc-parser": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.127.0.tgz", + "integrity": "sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.127.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.127.0", + "@oxc-parser/binding-android-arm64": "0.127.0", + "@oxc-parser/binding-darwin-arm64": "0.127.0", + "@oxc-parser/binding-darwin-x64": "0.127.0", + "@oxc-parser/binding-freebsd-x64": "0.127.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.127.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.127.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.127.0", + "@oxc-parser/binding-linux-arm64-musl": "0.127.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.127.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.127.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.127.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.127.0", + "@oxc-parser/binding-linux-x64-gnu": "0.127.0", + "@oxc-parser/binding-linux-x64-musl": "0.127.0", + "@oxc-parser/binding-openharmony-arm64": "0.127.0", + "@oxc-parser/binding-wasm32-wasi": "0.127.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.127.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.127.0", + "@oxc-parser/binding-win32-x64-msvc": "0.127.0" + } + }, + "node_modules/oxc-parser/node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/oxc-resolver": { + "version": "11.21.3", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.3.tgz", + "integrity": "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.21.3", + "@oxc-resolver/binding-android-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-arm64": "11.21.3", + "@oxc-resolver/binding-darwin-x64": "11.21.3", + "@oxc-resolver/binding-freebsd-x64": "11.21.3", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3", + "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-arm64-musl": "11.21.3", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3", + "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-gnu": "11.21.3", + "@oxc-resolver/binding-linux-x64-musl": "11.21.3", + "@oxc-resolver/binding-openharmony-arm64": "11.21.3", + "@oxc-resolver/binding-wasm32-wasi": "11.21.3", + "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3", + "@oxc-resolver/binding-win32-x64-msvc": "11.21.3" + } + }, "node_modules/p-cancelable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", @@ -25864,6 +27065,53 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -25874,6 +27122,16 @@ "node": ">=4" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/points-on-curve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", @@ -28028,6 +29286,51 @@ "node": ">=6" } }, + "node_modules/react-docgen": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.3.tgz", + "integrity": "sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@types/babel__core": "^7.20.5", + "@types/babel__traverse": "^7.20.7", + "@types/doctrine": "^0.0.9", + "@types/resolve": "^1.20.2", + "doctrine": "^3.0.0", + "resolve": "^1.22.1", + "strip-indent": "^4.0.0" + }, + "engines": { + "node": "^20.9.0 || >=22" + } + }, + "node_modules/react-docgen-typescript": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.4.0.tgz", + "integrity": "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 4.3.x" + } + }, + "node_modules/react-docgen/node_modules/strip-indent": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -28091,7 +29394,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, "license": "MIT" }, "node_modules/react-json-view-lite": { @@ -28250,6 +29552,33 @@ "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==", "license": "MIT" }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -29181,6 +30510,19 @@ "node": ">=12.0.0" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -30288,6 +31630,249 @@ "node": ">= 0.4" } }, + "node_modules/storybook": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.4.6.tgz", + "integrity": "sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/user-event": "^14.6.1", + "@vitest/expect": "3.2.4", + "@vitest/spy": "3.2.4", + "@webcontainer/env": "^1.1.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", + "open": "^10.2.0", + "oxc-parser": "^0.127.0", + "oxc-resolver": "^11.19.1", + "recast": "^0.23.5", + "semver": "^7.7.3", + "use-sync-external-store": "^1.5.0", + "ws": "^8.18.0" + }, + "bin": { + "storybook": "dist/bin/dispatcher.js" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "prettier": "^2 || ^3", + "vite-plus": "^0.1.15" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "prettier": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/storybook/node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/storybook/node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/storybook/node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/storybook/node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/storybook/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/storybook/node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/storybook/node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/storybook/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/storybook/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/storybook/node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/storybook/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/storybook/node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/storybook/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/storybook/node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/storybook/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -31940,6 +33525,16 @@ "node": ">= 0.6" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -33251,6 +34846,38 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xdg-basedir": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", @@ -33429,6 +35056,1025 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } + }, + "packages/design-system": { + "name": "@moex-vibe/design-system", + "version": "0.1.0", + "devDependencies": { + "@storybook/addon-a11y": "^10.2.9", + "@storybook/addon-vitest": "^10.2.9", + "@storybook/react-vite": "^10.2.9", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "eslint": "^8.0.0", + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "playwright": "^1.55.0", + "storybook": "^10.2.9", + "typescript": "^5.3.0", + "vitest": "^4.1.8" + }, + "peerDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^6.5.0", + "@mui/material": "^6.5.0", + "@tanstack/react-table": "^8.21.3", + "react": "^18.3.0", + "react-dom": "^18.3.0" + } + }, + "packages/design-system/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/@storybook/addon-vitest": { + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.4.6.tgz", + "integrity": "sha512-VvskHge0GZy86LG6kcY5Ww34z8rDV8JBxqSdUpcJVsWfIvyX6MfAbqI76LlereSyBIJGZJZsqaLwRXsQoVY+0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@vitest/browser": "^3.0.0 || ^4.0.0", + "@vitest/browser-playwright": "^4.0.0", + "@vitest/runner": "^3.0.0 || ^4.0.0", + "storybook": "^10.4.6", + "vitest": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/runner": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "packages/design-system/node_modules/@vitest/browser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.9.tgz", + "integrity": "sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.9" + } + }, + "packages/design-system/node_modules/@vitest/browser-playwright": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.9.tgz", + "integrity": "sha512-Bq1rOGf9waevzG3EOkO/dene6bvKTUsZMVg8S1i+WH3JcMjuXEjiahP9rAqZRELUqjBySOJsvvSWqK/B3wjKQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.9", + "@vitest/mocker": "4.1.9", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.9" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "packages/design-system/node_modules/@vitest/browser-playwright/node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "packages/design-system/node_modules/@vitest/browser/node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "packages/design-system/node_modules/@vitest/expect": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/design-system/node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/design-system/node_modules/@vitest/runner": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/design-system/node_modules/@vitest/snapshot": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/design-system/node_modules/@vitest/spy": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/design-system/node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/design-system/node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "packages/design-system/node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "packages/design-system/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "packages/design-system/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "packages/design-system/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "packages/design-system/node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/design-system/node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "packages/design-system/node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "packages/design-system/node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "packages/design-system/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "packages/design-system/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "packages/design-system/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } } } } diff --git a/package.json b/package.json index 8b3006f..0d9e3eb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "workspaces": [ "apps/backend", "apps/frontend", - "apps/docs" + "apps/docs", + "packages/design-system" ], "scripts": { "dev:backend": "npm run start:dev -w apps/backend", @@ -18,6 +19,12 @@ "format:check": "prettier --check \"**/*.{ts,tsx}\"", "dev:docs": "npm run dev -w apps/docs", "build:docs": "npm run build -w apps/docs", + "build:design-system": "npm run build -w packages/design-system", + "test:design-system": "npm run test -w packages/design-system", + "lint:design-system": "npm run lint -w packages/design-system", + "storybook": "npm run storybook -w packages/design-system", + "build:storybook": "npm run build-storybook -w packages/design-system", + "test:storybook": "npm run test:storybook -w packages/design-system", "prepare": "husky" }, "lint-staged": { diff --git a/packages/design-system/src/tokens/resolveToken.test.ts b/packages/design-system/src/tokens/resolveToken.test.ts new file mode 100644 index 0000000..14844d1 --- /dev/null +++ b/packages/design-system/src/tokens/resolveToken.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from 'vitest'; +import { resolveToken } from './resolveToken'; +import type { TokenCollection } from './types'; + +describe('resolveToken', () => { + it('resolves an alias chain and preserves the declared type', () => { + const tokens = { + base: { $type: 'color' as const, $value: '#176747' }, + semantic: { $type: 'color' as const, $value: '{base}' }, + component: { $type: 'color' as const, $value: '{semantic}' }, + } satisfies TokenCollection; + expect(resolveToken(tokens, 'component')).toEqual({ type: 'color', value: '#176747' }); + }); + + it.each([ + ['missing alias', { a: { $type: 'color', $value: '{missing}' } }, /Unknown token/], + [ + 'cycle', + { a: { $type: 'color', $value: '{b}' }, b: { $type: 'color', $value: '{a}' } }, + /cycle/i, + ], + [ + 'type mismatch', + { a: { $type: 'color', $value: '#fff' }, b: { $type: 'dimension', $value: '{a}' } }, + /type/i, + ], + ] as const)('rejects %s', (_name, tokens, error) => { + expect(() => resolveToken(tokens, 'b')).toThrow(error); + }); +}); diff --git a/packages/design-system/src/tokens/resolveToken.ts b/packages/design-system/src/tokens/resolveToken.ts new file mode 100644 index 0000000..7089f11 --- /dev/null +++ b/packages/design-system/src/tokens/resolveToken.ts @@ -0,0 +1,35 @@ +import type { TokenCollection, ResolvedToken } from './types'; + +const aliasPattern = /^\{([^}]+)\}$/; + +export function resolveToken( + tokens: TokenCollection, + name: string, + visited = new Set(), +): ResolvedToken { + if (visited.has(name)) { + throw new Error(`Cycle detected: ${name} has already been visited in this chain`); + } + + const def = tokens[name]; + if (!def) { + throw new Error(`Unknown token: ${name}`); + } + + const match = aliasPattern.exec(String(def.$value)); + if (!match) { + return { type: def.$type, value: def.$value }; + } + + const targetName = match[1]; + visited.add(name); + const resolved = resolveToken(tokens, targetName, visited); + + if (resolved.type !== def.$type) { + throw new Error( + `Type mismatch: token "${name}" has type "${def.$type}" but alias target "${targetName}" has type "${resolved.type}"`, + ); + } + + return resolved; +} diff --git a/packages/design-system/src/tokens/types.ts b/packages/design-system/src/tokens/types.ts new file mode 100644 index 0000000..59ea70f --- /dev/null +++ b/packages/design-system/src/tokens/types.ts @@ -0,0 +1,12 @@ +export type TokenType = + | 'color' + | 'dimension' + | 'fontFamily' + | 'fontWeight' + | 'duration' + | 'cubicBezier' + | 'shadow'; +export type TokenValue = string | number | readonly number[] | readonly string[]; +export type TokenDefinition = { readonly $type: TokenType; readonly $value: TokenValue }; +export type TokenCollection = Readonly>; +export type ResolvedToken = { readonly type: TokenType; readonly value: TokenValue }; -- 2.47.2 From fdffe204e434d08e30237c3358ad669c3c0c3b59 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:27:16 +0300 Subject: [PATCH 04/12] feat(design-system): define three-level tokens --- .../design-system/src/tokens/components.ts | 88 +++++++++++++++ packages/design-system/src/tokens/index.ts | 11 ++ .../design-system/src/tokens/primitives.ts | 103 ++++++++++++++++++ .../src/tokens/semantic.light.ts | 61 +++++++++++ .../design-system/src/tokens/tokens.test.ts | 72 ++++++++++++ 5 files changed, 335 insertions(+) create mode 100644 packages/design-system/src/tokens/components.ts create mode 100644 packages/design-system/src/tokens/index.ts create mode 100644 packages/design-system/src/tokens/primitives.ts create mode 100644 packages/design-system/src/tokens/semantic.light.ts create mode 100644 packages/design-system/src/tokens/tokens.test.ts diff --git a/packages/design-system/src/tokens/components.ts b/packages/design-system/src/tokens/components.ts new file mode 100644 index 0000000..da03e0e --- /dev/null +++ b/packages/design-system/src/tokens/components.ts @@ -0,0 +1,88 @@ +import type { TokenCollection } from './types'; + +export const componentTokens = { + // Button + 'button.font.weight': { $type: 'fontWeight' as const, $value: '{font.weight.semibold}' }, + 'button.radius': { $type: 'dimension' as const, $value: '{radius.md}' }, + 'button.padding.x': { $type: 'dimension' as const, $value: '{space.inset.lg}' }, + 'button.padding.y': { $type: 'dimension' as const, $value: '{space.inset.sm}' }, + 'button.primary.bg': { $type: 'color' as const, $value: '{color.action.primary}' }, + 'button.primary.text': { $type: 'color' as const, $value: '{color.text.inverse}' }, + 'button.primary.hover.bg': { $type: 'color' as const, $value: '{color.action.primaryHover}' }, + 'button.secondary.bg': { $type: 'color' as const, $value: '{color.surface.default}' }, + 'button.secondary.text': { $type: 'color' as const, $value: '{color.text.primary}' }, + 'button.secondary.border': { $type: 'color' as const, $value: '{color.border.default}' }, + 'button.danger.bg': { $type: 'color' as const, $value: '{color.action.danger}' }, + 'button.danger.text': { $type: 'color' as const, $value: '{color.text.inverse}' }, + + // IconButton + 'iconButton.size.sm': { $type: 'dimension' as const, $value: '{size.control.sm}' }, + 'iconButton.size.md': { $type: 'dimension' as const, $value: '{size.control.md}' }, + + // Field + 'field.radius': { $type: 'dimension' as const, $value: '{radius.md}' }, + 'field.border': { $type: 'color' as const, $value: '{color.border.default}' }, + 'field.border.focus': { $type: 'color' as const, $value: '{color.border.focus}' }, + 'field.border.error': { $type: 'color' as const, $value: '{color.feedback.error}' }, + 'field.label.weight': { $type: 'fontWeight' as const, $value: '{font.weight.medium}' }, + + // Checkbox + 'checkbox.radius': { $type: 'dimension' as const, $value: '{radius.sm}' }, + + // Surface + 'surface.radius': { $type: 'dimension' as const, $value: '{radius.lg}' }, + 'surface.padding': { $type: 'dimension' as const, $value: '{space.inset.lg}' }, + + // Card + 'card.radius': { $type: 'dimension' as const, $value: '{radius.lg}' }, + 'card.shadow': { $type: 'shadow' as const, $value: '{shadow.sm}' }, + 'card.padding': { $type: 'dimension' as const, $value: '{space.inset.lg}' }, + + // Chip + 'chip.radius': { $type: 'dimension' as const, $value: '{radius.pill}' }, + 'chip.height': { $type: 'dimension' as const, $value: '{size.control.sm}' }, + + // Badge + 'badge.radius': { $type: 'dimension' as const, $value: '{radius.pill}' }, + 'badge.font.size': { $type: 'dimension' as const, $value: '{font.size.200}' }, + + // Alert + 'alert.radius': { $type: 'dimension' as const, $value: '{radius.md}' }, + 'alert.info.bg': { $type: 'color' as const, $value: '{color.blue.50}' }, + 'alert.info.border': { $type: 'color' as const, $value: '{color.feedback.info}' }, + 'alert.success.bg': { $type: 'color' as const, $value: '{color.green.50}' }, + 'alert.success.border': { $type: 'color' as const, $value: '{color.feedback.success}' }, + 'alert.warning.bg': { $type: 'color' as const, $value: '{color.amber.50}' }, + 'alert.warning.border': { $type: 'color' as const, $value: '{color.feedback.warning}' }, + 'alert.error.bg': { $type: 'color' as const, $value: '{color.red.50}' }, + 'alert.error.border': { $type: 'color' as const, $value: '{color.feedback.error}' }, + + // Dialog + 'dialog.radius': { $type: 'dimension' as const, $value: '{radius.lg}' }, + 'dialog.shadow': { $type: 'shadow' as const, $value: '{shadow.md}' }, + 'dialog.padding': { $type: 'dimension' as const, $value: '{space.6}' }, + + // Skeleton + 'skeleton.radius': { $type: 'dimension' as const, $value: '{radius.sm}' }, + + // Progress + 'progress.height': { $type: 'dimension' as const, $value: '{space.1}' }, + 'progress.radius': { $type: 'dimension' as const, $value: '{radius.pill}' }, + 'progress.bg': { $type: 'color' as const, $value: '{color.neutral.100}' }, + 'progress.fill': { $type: 'color' as const, $value: '{color.action.primary}' }, + + // Table + 'table.header.weight': { $type: 'fontWeight' as const, $value: '{font.weight.semibold}' }, + 'table.cell.padding': { $type: 'dimension' as const, $value: '{space.inset.sm}' }, + 'table.row.hover.bg': { $type: 'color' as const, $value: '{color.surface.subtle}' }, + + // Metric + 'metric.label.size': { $type: 'dimension' as const, $value: '{font.size.200}' }, + 'metric.label.weight': { $type: 'fontWeight' as const, $value: '{font.weight.medium}' }, + 'metric.value.weight': { $type: 'fontWeight' as const, $value: '{font.weight.bold}' }, + 'metric.gap': { $type: 'dimension' as const, $value: '{space.1}' }, + + // PageState + 'pageState.icon.size': { $type: 'dimension' as const, $value: '{space.12}' }, + 'pageState.title.gap': { $type: 'dimension' as const, $value: '{space.4}' }, +} as const satisfies TokenCollection; diff --git a/packages/design-system/src/tokens/index.ts b/packages/design-system/src/tokens/index.ts new file mode 100644 index 0000000..40b3e8c --- /dev/null +++ b/packages/design-system/src/tokens/index.ts @@ -0,0 +1,11 @@ +export { primitiveTokens } from './primitives'; +export { semanticLightTokens } from './semantic.light'; +export { componentTokens } from './components'; +export { resolveToken } from './resolveToken'; +export type { + TokenType, + TokenValue, + TokenDefinition, + TokenCollection, + ResolvedToken, +} from './types'; diff --git a/packages/design-system/src/tokens/primitives.ts b/packages/design-system/src/tokens/primitives.ts new file mode 100644 index 0000000..987c84e --- /dev/null +++ b/packages/design-system/src/tokens/primitives.ts @@ -0,0 +1,103 @@ +import type { TokenCollection } from './types'; + +export const primitiveTokens = { + // Colors — neutral + 'color.neutral.0': { $type: 'color' as const, $value: '#ffffff' }, + 'color.neutral.50': { $type: 'color' as const, $value: '#f5f5f5' }, + 'color.neutral.100': { $type: 'color' as const, $value: '#e8e8e8' }, + 'color.neutral.200': { $type: 'color' as const, $value: '#d1d1d1' }, + 'color.neutral.400': { $type: 'color' as const, $value: '#a3a3a3' }, + 'color.neutral.600': { $type: 'color' as const, $value: '#666666' }, + 'color.neutral.800': { $type: 'color' as const, $value: '#333333' }, + 'color.neutral.900': { $type: 'color' as const, $value: '#1a1a1a' }, + + // Colors — green (positive) + 'color.green.50': { $type: 'color' as const, $value: '#e8f5e9' }, + 'color.green.100': { $type: 'color' as const, $value: '#c8e6c9' }, + 'color.green.600': { $type: 'color' as const, $value: '#2e7d32' }, + 'color.green.700': { $type: 'color' as const, $value: '#176747' }, + 'color.green.800': { $type: 'color' as const, $value: '#1b5e20' }, + + // Colors — red (negative) + 'color.red.50': { $type: 'color' as const, $value: '#ffebee' }, + 'color.red.600': { $type: 'color' as const, $value: '#e53935' }, + 'color.red.700': { $type: 'color' as const, $value: '#c62828' }, + + // Colors — amber (warning) + 'color.amber.50': { $type: 'color' as const, $value: '#fff8e1' }, + 'color.amber.600': { $type: 'color' as const, $value: '#ffb300' }, + + // Colors — blue (info) + 'color.blue.50': { $type: 'color' as const, $value: '#e3f2fd' }, + 'color.blue.600': { $type: 'color' as const, $value: '#1e88e5' }, + + // Spacing (4px grid) + 'space.0': { $type: 'dimension' as const, $value: '0px' }, + 'space.1': { $type: 'dimension' as const, $value: '4px' }, + 'space.2': { $type: 'dimension' as const, $value: '8px' }, + 'space.3': { $type: 'dimension' as const, $value: '12px' }, + 'space.4': { $type: 'dimension' as const, $value: '16px' }, + 'space.5': { $type: 'dimension' as const, $value: '20px' }, + 'space.6': { $type: 'dimension' as const, $value: '24px' }, + 'space.8': { $type: 'dimension' as const, $value: '32px' }, + 'space.10': { $type: 'dimension' as const, $value: '40px' }, + 'space.12': { $type: 'dimension' as const, $value: '48px' }, + + // Border radius + 'radius.none': { $type: 'dimension' as const, $value: '0px' }, + 'radius.sm': { $type: 'dimension' as const, $value: '4px' }, + 'radius.md': { $type: 'dimension' as const, $value: '8px' }, + 'radius.lg': { $type: 'dimension' as const, $value: '12px' }, + 'radius.pill': { $type: 'dimension' as const, $value: '9999px' }, + + // Font family + 'font.family.sans': { + $type: 'fontFamily' as const, + $value: ['Inter', 'system-ui', '-apple-system', 'sans-serif'], + }, + 'font.family.mono': { + $type: 'fontFamily' as const, + $value: ['SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'], + }, + + // Font size (100 = 10px, 700 = 48px scale) + 'font.size.100': { $type: 'dimension' as const, $value: '10px' }, + 'font.size.200': { $type: 'dimension' as const, $value: '12px' }, + 'font.size.300': { $type: 'dimension' as const, $value: '14px' }, + 'font.size.400': { $type: 'dimension' as const, $value: '16px' }, + 'font.size.500': { $type: 'dimension' as const, $value: '20px' }, + 'font.size.600': { $type: 'dimension' as const, $value: '24px' }, + 'font.size.700': { $type: 'dimension' as const, $value: '32px' }, + + // Font weight + 'font.weight.regular': { $type: 'fontWeight' as const, $value: 400 }, + 'font.weight.medium': { $type: 'fontWeight' as const, $value: 500 }, + 'font.weight.semibold': { $type: 'fontWeight' as const, $value: 600 }, + 'font.weight.bold': { $type: 'fontWeight' as const, $value: 700 }, + + // Line height + 'lineHeight.tight': { $type: 'dimension' as const, $value: '1.25' }, + 'lineHeight.normal': { $type: 'dimension' as const, $value: '1.5' }, + 'lineHeight.relaxed': { $type: 'dimension' as const, $value: '1.75' }, + + // Shadows + 'shadow.none': { $type: 'shadow' as const, $value: 'none' }, + 'shadow.sm': { $type: 'shadow' as const, $value: '0px 1px 2px 0px rgba(0,0,0,0.08)' }, + 'shadow.md': { $type: 'shadow' as const, $value: '0px 4px 8px 0px rgba(0,0,0,0.12)' }, + + // Duration + 'duration.instant': { $type: 'duration' as const, $value: 0 }, + 'duration.fast': { $type: 'duration' as const, $value: 150 }, + 'duration.normal': { $type: 'duration' as const, $value: 250 }, + + // Easing + 'easing.standard': { $type: 'cubicBezier' as const, $value: [0.4, 0, 0.2, 1] as const }, + + // Control sizes + 'size.control.sm': { $type: 'dimension' as const, $value: '32px' }, + 'size.control.md': { $type: 'dimension' as const, $value: '40px' }, + + // Focus ring + 'size.focus.ring': { $type: 'dimension' as const, $value: '2px' }, + 'size.focus.offset': { $type: 'dimension' as const, $value: '2px' }, +} as const satisfies TokenCollection; diff --git a/packages/design-system/src/tokens/semantic.light.ts b/packages/design-system/src/tokens/semantic.light.ts new file mode 100644 index 0000000..0cca2c9 --- /dev/null +++ b/packages/design-system/src/tokens/semantic.light.ts @@ -0,0 +1,61 @@ +import type { TokenCollection } from './types'; + +export const semanticLightTokens = { + // Canvas + 'color.canvas': { $type: 'color' as const, $value: '{color.neutral.50}' }, + + // Surface + 'color.surface.default': { $type: 'color' as const, $value: '{color.neutral.0}' }, + 'color.surface.subtle': { $type: 'color' as const, $value: '{color.neutral.50}' }, + 'color.surface.raised': { $type: 'color' as const, $value: '{color.neutral.0}' }, + + // Text + 'color.text.primary': { $type: 'color' as const, $value: '{color.neutral.900}' }, + 'color.text.secondary': { $type: 'color' as const, $value: '{color.neutral.600}' }, + 'color.text.disabled': { $type: 'color' as const, $value: '{color.neutral.400}' }, + 'color.text.inverse': { $type: 'color' as const, $value: '{color.neutral.0}' }, + + // Border + 'color.border.subtle': { $type: 'color' as const, $value: '{color.neutral.100}' }, + 'color.border.default': { $type: 'color' as const, $value: '{color.neutral.200}' }, + 'color.border.strong': { $type: 'color' as const, $value: '{color.neutral.400}' }, + 'color.border.focus': { $type: 'color' as const, $value: '{color.blue.600}' }, + + // Action + 'color.action.primary': { $type: 'color' as const, $value: '{color.green.700}' }, + 'color.action.primaryHover': { $type: 'color' as const, $value: '{color.green.800}' }, + 'color.action.secondary': { $type: 'color' as const, $value: '{color.neutral.600}' }, + 'color.action.danger': { $type: 'color' as const, $value: '{color.red.600}' }, + + // Feedback + 'color.feedback.info': { $type: 'color' as const, $value: '{color.blue.600}' }, + 'color.feedback.success': { $type: 'color' as const, $value: '{color.green.600}' }, + 'color.feedback.warning': { $type: 'color' as const, $value: '{color.amber.600}' }, + 'color.feedback.error': { $type: 'color' as const, $value: '{color.red.600}' }, + + // Finance + 'color.finance.positive': { $type: 'color' as const, $value: '{color.green.700}' }, + 'color.finance.negative': { $type: 'color' as const, $value: '{color.red.700}' }, + 'color.finance.neutral': { $type: 'color' as const, $value: '{color.neutral.600}' }, + + // Typography + 'font.family.body': { $type: 'fontFamily' as const, $value: '{font.family.sans}' }, + 'font.weight.body': { $type: 'fontWeight' as const, $value: '{font.weight.regular}' }, + 'font.weight.heading': { $type: 'fontWeight' as const, $value: '{font.weight.semibold}' }, + 'font.weight.strong': { $type: 'fontWeight' as const, $value: '{font.weight.bold}' }, + // Spacing + 'space.inset.sm': { $type: 'dimension' as const, $value: '{space.2}' }, + 'space.inset.md': { $type: 'dimension' as const, $value: '{space.3}' }, + 'space.inset.lg': { $type: 'dimension' as const, $value: '{space.4}' }, + 'space.stack.sm': { $type: 'dimension' as const, $value: '{space.1}' }, + 'space.stack.md': { $type: 'dimension' as const, $value: '{space.2}' }, + 'space.stack.lg': { $type: 'dimension' as const, $value: '{space.4}' }, + 'space.inline.sm': { $type: 'dimension' as const, $value: '{space.1}' }, + 'space.inline.md': { $type: 'dimension' as const, $value: '{space.2}' }, + 'space.inline.lg': { $type: 'dimension' as const, $value: '{space.3}' }, + + // Focus ring + 'focus.ring.width': { $type: 'dimension' as const, $value: '{size.focus.ring}' }, + 'focus.ring.offset': { $type: 'dimension' as const, $value: '{size.focus.offset}' }, + 'focus.ring.color': { $type: 'color' as const, $value: '{color.blue.600}' }, +} as const satisfies TokenCollection; diff --git a/packages/design-system/src/tokens/tokens.test.ts b/packages/design-system/src/tokens/tokens.test.ts new file mode 100644 index 0000000..a473d6d --- /dev/null +++ b/packages/design-system/src/tokens/tokens.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { primitiveTokens } from './primitives'; +import { semanticLightTokens } from './semantic.light'; +import { componentTokens } from './components'; +import { resolveToken } from './resolveToken'; + +const aliasPattern = /^\{([^}]+)\}$/; +const isAlias = (value: string | number | readonly number[] | readonly string[]): boolean => + typeof value === 'string' && aliasPattern.test(value); + +const allTokens = { + ...primitiveTokens, + ...semanticLightTokens, + ...componentTokens, +} as const; + +describe('token integrity', () => { + it('primitives contain no aliases', () => { + for (const [name, def] of Object.entries(primitiveTokens)) { + expect(isAlias(def.$value), `${name} should not be an alias`).toBe(false); + } + }); + + it('semantic tokens only use aliases', () => { + for (const [name, def] of Object.entries(semanticLightTokens)) { + expect(isAlias(def.$value), `${name} should be an alias`).toBe(true); + } + }); + + it('component tokens only use aliases', () => { + for (const [name, def] of Object.entries(componentTokens)) { + expect(isAlias(def.$value), `${name} should be an alias`).toBe(true); + } + }); + + it('all tokens resolve without errors', () => { + for (const name of Object.keys(allTokens)) { + expect(() => resolveToken(allTokens, name), `Failed to resolve ${name}`).not.toThrow(); + } + }); + + it('has unique names across all collections', () => { + const allNames = [ + ...Object.keys(primitiveTokens), + ...Object.keys(semanticLightTokens), + ...Object.keys(componentTokens), + ]; + const uniqueNames = new Set(allNames); + expect(uniqueNames.size).toBe(allNames.length); + }); + + it('semantic tokens reference only primitive tokens', () => { + const primitiveNames = new Set(Object.keys(primitiveTokens)); + for (const [name, def] of Object.entries(semanticLightTokens)) { + if (typeof def.$value === 'string' && aliasPattern.test(def.$value)) { + const target = def.$value.match(aliasPattern)![1]; + expect(primitiveNames.has(target), `${name} references non-primitive "${target}"`).toBe( + true, + ); + } + } + }); + + it('component tokens avoid direct primitive value literals', () => { + const literalPattern = /(px|#|rgba|rgb)/; + for (const [name, def] of Object.entries(componentTokens)) { + if (typeof def.$value === 'string') { + expect(literalPattern.test(def.$value), `${name} contains a literal value`).toBe(false); + } + } + }); +}); -- 2.47.2 From 1107a1fc8bf8f9cf94f4345b3b8e8ddacfae9fd9 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:28:38 +0300 Subject: [PATCH 05/12] feat(design-system): add MUI theme adapter --- .../src/theme/MoexVibeThemeProvider.tsx | 20 +++++++ .../src/theme/createMoexVibeTheme.test.ts | 29 ++++++++++ .../src/theme/createMoexVibeTheme.ts | 55 +++++++++++++++++++ packages/design-system/src/theme/index.ts | 2 + packages/design-system/src/theme/mui.d.ts | 18 ++++++ 5 files changed, 124 insertions(+) create mode 100644 packages/design-system/src/theme/MoexVibeThemeProvider.tsx create mode 100644 packages/design-system/src/theme/createMoexVibeTheme.test.ts create mode 100644 packages/design-system/src/theme/createMoexVibeTheme.ts create mode 100644 packages/design-system/src/theme/index.ts create mode 100644 packages/design-system/src/theme/mui.d.ts diff --git a/packages/design-system/src/theme/MoexVibeThemeProvider.tsx b/packages/design-system/src/theme/MoexVibeThemeProvider.tsx new file mode 100644 index 0000000..9c577ac --- /dev/null +++ b/packages/design-system/src/theme/MoexVibeThemeProvider.tsx @@ -0,0 +1,20 @@ +import { type ReactNode } from 'react'; +import { ThemeProvider } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material/styles'; +import { createMoexVibeTheme } from './createMoexVibeTheme'; + +const theme = createMoexVibeTheme(); + +export interface MoexVibeThemeProviderProps { + children: ReactNode; +} + +export function MoexVibeThemeProvider({ children }: MoexVibeThemeProviderProps) { + return ( + + + {children} + + ); +} diff --git a/packages/design-system/src/theme/createMoexVibeTheme.test.ts b/packages/design-system/src/theme/createMoexVibeTheme.test.ts new file mode 100644 index 0000000..2975a00 --- /dev/null +++ b/packages/design-system/src/theme/createMoexVibeTheme.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { createMoexVibeTheme } from './createMoexVibeTheme'; + +describe('createMoexVibeTheme', () => { + it('sets cssVarPrefix to mv', () => { + const theme = createMoexVibeTheme(); + expect(theme.cssVarPrefix).toBe('mv'); + }); + + it('maps color.action.primary to MUI primary main', () => { + const theme = createMoexVibeTheme(); + expect(theme.colorSchemes.light.palette.primary.main).toBe('#176747'); + }); + + it('uses Inter in typography', () => { + const theme = createMoexVibeTheme(); + expect(theme.typography.body1.fontFamily).toContain('Inter'); + }); + + it('maps radius.md to shape border radius', () => { + const theme = createMoexVibeTheme(); + expect(theme.shape.borderRadius).toBe(8); + }); + + it('disables elevation on MuiButton by default', () => { + const theme = createMoexVibeTheme(); + expect(theme.components?.MuiButton?.defaultProps).toMatchObject({ disableElevation: true }); + }); +}); diff --git a/packages/design-system/src/theme/createMoexVibeTheme.ts b/packages/design-system/src/theme/createMoexVibeTheme.ts new file mode 100644 index 0000000..a341a27 --- /dev/null +++ b/packages/design-system/src/theme/createMoexVibeTheme.ts @@ -0,0 +1,55 @@ +import { createTheme } from '@mui/material/styles'; +import { primitiveTokens, semanticLightTokens, componentTokens, resolveToken } from '../tokens'; + +const allTokens = { + ...primitiveTokens, + ...semanticLightTokens, + ...componentTokens, +} as const; + +function resolveValue(name: string): string | number | readonly number[] | readonly string[] { + return resolveToken(allTokens, name).value; +} + +export function createMoexVibeTheme() { + return createTheme({ + cssVariables: { cssVarPrefix: 'mv' }, + colorSchemes: { + light: { + palette: { + primary: { main: resolveValue('color.action.primary') as string }, + secondary: { main: resolveValue('color.action.secondary') as string }, + error: { main: resolveValue('color.feedback.error') as string }, + warning: { main: resolveValue('color.feedback.warning') as string }, + info: { main: resolveValue('color.feedback.info') as string }, + success: { main: resolveValue('color.finance.positive') as string }, + background: { + default: resolveValue('color.canvas') as string, + paper: resolveValue('color.surface.default') as string, + }, + text: { + primary: resolveValue('color.text.primary') as string, + secondary: resolveValue('color.text.secondary') as string, + disabled: resolveValue('color.text.disabled') as string, + }, + divider: resolveValue('color.border.default') as string, + }, + }, + }, + typography: { + fontFamily: (resolveValue('font.family.sans') as readonly string[]).join(', '), + body1: { fontFamily: (resolveValue('font.family.body') as readonly string[]).join(', ') }, + fontWeightRegular: resolveValue('font.weight.regular') as number, + fontWeightMedium: resolveValue('font.weight.medium') as number, + fontWeightBold: resolveValue('font.weight.bold') as number, + }, + shape: { + borderRadius: parseInt(resolveValue('radius.md') as string, 10), + }, + components: { + MuiButton: { + defaultProps: { disableElevation: true }, + }, + }, + }); +} diff --git a/packages/design-system/src/theme/index.ts b/packages/design-system/src/theme/index.ts new file mode 100644 index 0000000..c328165 --- /dev/null +++ b/packages/design-system/src/theme/index.ts @@ -0,0 +1,2 @@ +export { createMoexVibeTheme } from './createMoexVibeTheme'; +export { MoexVibeThemeProvider } from './MoexVibeThemeProvider'; diff --git a/packages/design-system/src/theme/mui.d.ts b/packages/design-system/src/theme/mui.d.ts new file mode 100644 index 0000000..8bd3daf --- /dev/null +++ b/packages/design-system/src/theme/mui.d.ts @@ -0,0 +1,18 @@ +import type {} from '@mui/material/themeCssVarsAugmentation'; + +declare module '@mui/material/styles' { + interface Palette { + finance: { + positive: string; + negative: string; + neutral: string; + }; + } + interface PaletteOptions { + finance?: { + positive: string; + negative: string; + neutral: string; + }; + } +} -- 2.47.2 From 886ac7b87ac3e12267e6e59420ec4ce2f3e55346 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:29:26 +0300 Subject: [PATCH 06/12] build(design-system): configure Storybook workbench --- packages/design-system/.storybook/main.ts | 9 +++++++ packages/design-system/.storybook/preview.tsx | 24 +++++++++++++++++++ .../design-system/.storybook/vitest.setup.ts | 5 ++++ packages/design-system/vitest.workspace.ts | 19 +++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 packages/design-system/.storybook/main.ts create mode 100644 packages/design-system/.storybook/preview.tsx create mode 100644 packages/design-system/.storybook/vitest.setup.ts create mode 100644 packages/design-system/vitest.workspace.ts diff --git a/packages/design-system/.storybook/main.ts b/packages/design-system/.storybook/main.ts new file mode 100644 index 0000000..d64c4bb --- /dev/null +++ b/packages/design-system/.storybook/main.ts @@ -0,0 +1,9 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + framework: '@storybook/react-vite', + stories: ['../src/**/*.stories.@(ts|tsx)'], + addons: ['@storybook/addon-a11y', '@storybook/addon-vitest'], +}; + +export default config; diff --git a/packages/design-system/.storybook/preview.tsx b/packages/design-system/.storybook/preview.tsx new file mode 100644 index 0000000..7335a73 --- /dev/null +++ b/packages/design-system/.storybook/preview.tsx @@ -0,0 +1,24 @@ +import type { Preview } from '@storybook/react'; +import { MoexVibeThemeProvider } from '../src/theme'; + +const preview: Preview = { + decorators: [ + (Story) => ( + + + + ), + ], + parameters: { + a11y: { + test: 'error', + }, + layout: 'centered', + backgrounds: { + default: 'canvas', + values: [{ name: 'canvas', value: '#f5f5f5' }], + }, + }, +}; + +export default preview; diff --git a/packages/design-system/.storybook/vitest.setup.ts b/packages/design-system/.storybook/vitest.setup.ts new file mode 100644 index 0000000..7ea17dc --- /dev/null +++ b/packages/design-system/.storybook/vitest.setup.ts @@ -0,0 +1,5 @@ +import '@storybook/addon-a11y/preview'; +import { setProjectAnnotations } from '@storybook/react'; +import preview from './preview'; + +setProjectAnnotations(preview); diff --git a/packages/design-system/vitest.workspace.ts b/packages/design-system/vitest.workspace.ts new file mode 100644 index 0000000..4cc23c2 --- /dev/null +++ b/packages/design-system/vitest.workspace.ts @@ -0,0 +1,19 @@ +import { defineWorkspace } from 'vitest/config'; + +export default defineWorkspace([ + 'vitest.config.ts', + { + test: { + name: 'storybook', + browser: { + enabled: true, + name: 'chromium', + provider: 'playwright', + headless: true, + }, + setupFiles: ['./.storybook/vitest.setup.ts'], + include: ['src/**/*.stories.test.{ts,tsx}'], + passWithNoTests: true, + }, + }, +]); -- 2.47.2 From 74ca576310ea96961843bec50cbfa9b88250a999 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:32:58 +0300 Subject: [PATCH 07/12] feat(design-system): add typography and actions Implement Text, Heading, Link, Button, and IconButton components with TDD and Storybook stories. Each component wraps MUI with restricted props for visual consistency. --- .../src/components/Button/Button.stories.tsx | 105 ++++++++++++++++++ .../src/components/Button/Button.test.tsx | 105 ++++++++++++++++++ .../src/components/Button/Button.tsx | 48 ++++++++ .../src/components/Button/index.ts | 2 + .../components/Heading/Heading.stories.tsx | 92 +++++++++++++++ .../src/components/Heading/Heading.test.tsx | 55 +++++++++ .../src/components/Heading/Heading.tsx | 25 +++++ .../src/components/Heading/index.ts | 2 + .../IconButton/IconButton.stories.tsx | 88 +++++++++++++++ .../components/IconButton/IconButton.test.tsx | 68 ++++++++++++ .../src/components/IconButton/IconButton.tsx | 22 ++++ .../src/components/IconButton/index.ts | 2 + .../src/components/Link/Link.stories.tsx | 70 ++++++++++++ .../src/components/Link/Link.test.tsx | 53 +++++++++ .../src/components/Link/Link.tsx | 25 +++++ .../src/components/Link/index.ts | 2 + .../src/components/Text/Text.stories.tsx | 99 +++++++++++++++++ .../src/components/Text/Text.test.tsx | 53 +++++++++ .../src/components/Text/Text.tsx | 35 ++++++ .../src/components/Text/index.ts | 2 + .../design-system/src/components/index.ts | 14 +++ packages/design-system/src/index.ts | 1 + 22 files changed, 968 insertions(+) create mode 100644 packages/design-system/src/components/Button/Button.stories.tsx create mode 100644 packages/design-system/src/components/Button/Button.test.tsx create mode 100644 packages/design-system/src/components/Button/Button.tsx create mode 100644 packages/design-system/src/components/Button/index.ts create mode 100644 packages/design-system/src/components/Heading/Heading.stories.tsx create mode 100644 packages/design-system/src/components/Heading/Heading.test.tsx create mode 100644 packages/design-system/src/components/Heading/Heading.tsx create mode 100644 packages/design-system/src/components/Heading/index.ts create mode 100644 packages/design-system/src/components/IconButton/IconButton.stories.tsx create mode 100644 packages/design-system/src/components/IconButton/IconButton.test.tsx create mode 100644 packages/design-system/src/components/IconButton/IconButton.tsx create mode 100644 packages/design-system/src/components/IconButton/index.ts create mode 100644 packages/design-system/src/components/Link/Link.stories.tsx create mode 100644 packages/design-system/src/components/Link/Link.test.tsx create mode 100644 packages/design-system/src/components/Link/Link.tsx create mode 100644 packages/design-system/src/components/Link/index.ts create mode 100644 packages/design-system/src/components/Text/Text.stories.tsx create mode 100644 packages/design-system/src/components/Text/Text.test.tsx create mode 100644 packages/design-system/src/components/Text/Text.tsx create mode 100644 packages/design-system/src/components/Text/index.ts create mode 100644 packages/design-system/src/components/index.ts create mode 100644 packages/design-system/src/index.ts diff --git a/packages/design-system/src/components/Button/Button.stories.tsx b/packages/design-system/src/components/Button/Button.stories.tsx new file mode 100644 index 0000000..a876afa --- /dev/null +++ b/packages/design-system/src/components/Button/Button.stories.tsx @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta: Meta = { + title: 'Actions/Button', + component: Button, + argTypes: { + variant: { + control: 'select', + options: ['primary', 'secondary', 'tertiary', 'danger'], + }, + size: { + control: 'select', + options: ['small', 'medium'], + }, + loading: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + variant: 'primary', + children: 'Купить', + }, +}; + +export const Secondary: Story = { + args: { + variant: 'secondary', + children: 'Отмена', + }, +}; + +export const Tertiary: Story = { + args: { + variant: 'tertiary', + children: 'Подробнее', + }, +}; + +export const Danger: Story = { + args: { + variant: 'danger', + children: 'Удалить портфель', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + children: 'Применить', + }, +}; + +export const Loading: Story = { + args: { + loading: true, + children: 'Отправка...', + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + children: 'Недоступно', + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ + + + +
+ ), +}; + +export const AllSizes: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const AllStates: Story = { + render: () => ( +
+ + + +
+ ), +}; diff --git a/packages/design-system/src/components/Button/Button.test.tsx b/packages/design-system/src/components/Button/Button.test.tsx new file mode 100644 index 0000000..6103dd0 --- /dev/null +++ b/packages/design-system/src/components/Button/Button.test.tsx @@ -0,0 +1,105 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Button } from './Button'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Button', () => { + it('renders children', () => { + renderWithTheme(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('renders as a button element by default', () => { + renderWithTheme(); + expect(screen.getByRole('button', { name: /click/i })).toBeInTheDocument(); + }); + + it('renders primary variant by default', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-contained')).toBe(true); + }); + + it('renders secondary variant', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-outlined')).toBe(true); + }); + + it('renders tertiary variant', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-text')).toBe(true); + }); + + it('renders danger variant with contained style', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-contained')).toBe(true); + }); + + it('does not pass color prop to DOM', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn).not.toHaveAttribute('color'); + }); + + it('accepts className and style props', () => { + renderWithTheme( + , + ); + const btn = screen.getByText('Click'); + expect(btn.classList.contains('custom')).toBe(true); + }); + + it('calls onClick when clicked', async () => { + const handleClick = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + await user.click(screen.getByRole('button')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('shows aria-busy when loading', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn).toHaveAttribute('aria-busy', 'true'); + }); + + it('disables button when loading', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn).toBeDisabled(); + }); + + it('does not call onClick when loading', () => { + const handleClick = vi.fn(); + const { container } = renderWithTheme( + , + ); + const btn = container.querySelector('button')!; + btn.click(); + expect(handleClick).not.toHaveBeenCalled(); + }); + + it('renders small size', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-sizeSmall')).toBe(true); + }); + + it('renders medium size by default', () => { + renderWithTheme(); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiButton-sizeMedium')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/Button/Button.tsx b/packages/design-system/src/components/Button/Button.tsx new file mode 100644 index 0000000..788c08f --- /dev/null +++ b/packages/design-system/src/components/Button/Button.tsx @@ -0,0 +1,48 @@ +import { + Button as MuiButton, + CircularProgress, + type ButtonProps as MuiButtonProps, +} from '@mui/material'; +import type { ReactNode } from 'react'; + +type ActionVariant = 'primary' | 'secondary' | 'tertiary' | 'danger'; + +export interface ButtonProps extends Omit { + variant?: ActionVariant; + size?: 'small' | 'medium'; + loading?: boolean; + children: ReactNode; +} + +const VARIANT_MAP: Record = { + primary: 'contained', + secondary: 'outlined', + tertiary: 'text', + danger: 'contained', +}; + +export function Button({ + variant = 'primary', + size = 'medium', + loading = false, + disabled, + children, + ...props +}: ButtonProps) { + const muiVariant = VARIANT_MAP[variant]; + const muiColor = variant === 'danger' ? 'error' : undefined; + + return ( + + {loading && } + {children} + + ); +} diff --git a/packages/design-system/src/components/Button/index.ts b/packages/design-system/src/components/Button/index.ts new file mode 100644 index 0000000..fa3c8a5 --- /dev/null +++ b/packages/design-system/src/components/Button/index.ts @@ -0,0 +1,2 @@ +export { Button } from './Button'; +export type { ButtonProps } from './Button'; diff --git a/packages/design-system/src/components/Heading/Heading.stories.tsx b/packages/design-system/src/components/Heading/Heading.stories.tsx new file mode 100644 index 0000000..b75a6a2 --- /dev/null +++ b/packages/design-system/src/components/Heading/Heading.stories.tsx @@ -0,0 +1,92 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Heading } from './Heading'; + +const meta: Meta = { + title: 'Typography/Heading', + component: Heading, + argTypes: { + level: { + control: 'select', + options: [1, 2, 3, 4, 5, 6], + }, + size: { + control: 'select', + options: ['display', 'title', 'section', 'subsection'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Level1: Story = { + args: { + level: 1, + children: 'Обзор рынка', + }, +}; + +export const Level2: Story = { + args: { + level: 2, + children: 'Акции', + }, +}; + +export const Level3: Story = { + args: { + level: 3, + children: 'Голубые фишки', + }, +}; + +export const DisplaySize: Story = { + args: { + level: 1, + size: 'display', + children: 'Московская Биржа', + }, +}; + +export const TitleSize: Story = { + args: { + level: 2, + size: 'title', + children: 'Индекс Мосбиржи обновил максимум', + }, +}; + +export const SectionSize: Story = { + args: { + level: 3, + size: 'section', + children: 'Нефтегазовый сектор', + }, +}; + +export const SubsectionSize: Story = { + args: { + level: 4, + size: 'subsection', + children: 'Лукойл', + }, +}; + +export const AllLevels: Story = { + render: () => ( +
+ + H1 — Заголовок страницы + + + H2 — Раздел + + + H3 — Подраздел + + H4 — Группа + H5 — Элемент + H6 — Мелкий заголовок +
+ ), +}; diff --git a/packages/design-system/src/components/Heading/Heading.test.tsx b/packages/design-system/src/components/Heading/Heading.test.tsx new file mode 100644 index 0000000..cb98adb --- /dev/null +++ b/packages/design-system/src/components/Heading/Heading.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Heading } from './Heading'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Heading', () => { + it('renders children', () => { + renderWithTheme(Title); + expect(screen.getByText('Title')).toBeInTheDocument(); + }); + + it('renders h1 for level 1', () => { + renderWithTheme(H1); + expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); + }); + + it('renders h2 for level 2', () => { + renderWithTheme(H2); + expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument(); + }); + + it('renders h3 for level 3', () => { + renderWithTheme(H3); + expect(screen.getByRole('heading', { level: 3 })).toBeInTheDocument(); + }); + + it('renders h4 for level 4', () => { + renderWithTheme(H4); + expect(screen.getByRole('heading', { level: 4 })).toBeInTheDocument(); + }); + + it('renders h5 for level 5', () => { + renderWithTheme(H5); + expect(screen.getByRole('heading', { level: 5 })).toBeInTheDocument(); + }); + + it('renders h6 for level 6', () => { + renderWithTheme(H6); + expect(screen.getByRole('heading', { level: 6 })).toBeInTheDocument(); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + Hello + , + ); + const el = screen.getByText('Hello'); + expect(el.classList.contains('custom')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/Heading/Heading.tsx b/packages/design-system/src/components/Heading/Heading.tsx new file mode 100644 index 0000000..3b7664e --- /dev/null +++ b/packages/design-system/src/components/Heading/Heading.tsx @@ -0,0 +1,25 @@ +import { Typography, type TypographyProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Size = 'display' | 'title' | 'section' | 'subsection'; + +export interface HeadingProps extends Omit { + level: 1 | 2 | 3 | 4 | 5 | 6; + size?: Size; + children: ReactNode; +} + +const SIZE_VARIANT: Record = { + display: 'h1', + title: 'h2', + section: 'h3', + subsection: 'h4', +}; + +export function Heading({ level, size = 'title', children, ...props }: HeadingProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Heading/index.ts b/packages/design-system/src/components/Heading/index.ts new file mode 100644 index 0000000..f617246 --- /dev/null +++ b/packages/design-system/src/components/Heading/index.ts @@ -0,0 +1,2 @@ +export { Heading } from './Heading'; +export type { HeadingProps } from './Heading'; diff --git a/packages/design-system/src/components/IconButton/IconButton.stories.tsx b/packages/design-system/src/components/IconButton/IconButton.stories.tsx new file mode 100644 index 0000000..6671912 --- /dev/null +++ b/packages/design-system/src/components/IconButton/IconButton.stories.tsx @@ -0,0 +1,88 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { IconButton } from './IconButton'; +import DeleteIcon from '@mui/icons-material/Delete'; +import SettingsIcon from '@mui/icons-material/Settings'; +import SearchIcon from '@mui/icons-material/Search'; +import CloseIcon from '@mui/icons-material/Close'; + +const meta: Meta = { + title: 'Actions/IconButton', + component: IconButton, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Search: Story = { + args: { + label: 'Поиск', + children: , + }, +}; + +export const Close: Story = { + args: { + label: 'Закрыть', + children: , + }, +}; + +export const Delete: Story = { + args: { + label: 'Удалить', + children: , + }, +}; + +export const Settings: Story = { + args: { + label: 'Настройки', + children: , + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Поиск', + children: , + }, +}; + +export const AllSizes: Story = { + render: () => ( +
+ + + + + + +
+ ), +}; + +export const AllExamples: Story = { + render: () => ( +
+ + + + + + + + + + + + +
+ ), +}; diff --git a/packages/design-system/src/components/IconButton/IconButton.test.tsx b/packages/design-system/src/components/IconButton/IconButton.test.tsx new file mode 100644 index 0000000..45df86e --- /dev/null +++ b/packages/design-system/src/components/IconButton/IconButton.test.tsx @@ -0,0 +1,68 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { IconButton } from './IconButton'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('IconButton', () => { + it('renders with aria-label', () => { + renderWithTheme( + + X + , + ); + expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); + }); + + it('renders children', () => { + renderWithTheme( + + + , + ); + expect(screen.getByText('☰')).toBeInTheDocument(); + }); + + it('does not pass color prop to DOM', () => { + renderWithTheme( + + 🔍 + , + ); + const btn = screen.getByRole('button'); + expect(btn).not.toHaveAttribute('color'); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + + , + ); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('custom')).toBe(true); + }); + + it('renders small size', () => { + renderWithTheme( + + S + , + ); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiIconButton-sizeSmall')).toBe(true); + }); + + it('renders medium size by default', () => { + renderWithTheme( + + M + , + ); + const btn = screen.getByRole('button'); + expect(btn.classList.contains('MuiIconButton-sizeMedium')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/IconButton/IconButton.tsx b/packages/design-system/src/components/IconButton/IconButton.tsx new file mode 100644 index 0000000..d1e2bca --- /dev/null +++ b/packages/design-system/src/components/IconButton/IconButton.tsx @@ -0,0 +1,22 @@ +import { + IconButton as MuiIconButton, + type IconButtonProps as MuiIconButtonProps, +} from '@mui/material'; +import type { ReactNode } from 'react'; + +export interface IconButtonProps extends Omit< + MuiIconButtonProps, + 'color' | 'variant' | 'sx' | 'aria-label' +> { + label: string; + size?: 'small' | 'medium'; + children: ReactNode; +} + +export function IconButton({ label, size = 'medium', children, ...props }: IconButtonProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/IconButton/index.ts b/packages/design-system/src/components/IconButton/index.ts new file mode 100644 index 0000000..5f509c9 --- /dev/null +++ b/packages/design-system/src/components/IconButton/index.ts @@ -0,0 +1,2 @@ +export { IconButton } from './IconButton'; +export type { IconButtonProps } from './IconButton'; diff --git a/packages/design-system/src/components/Link/Link.stories.tsx b/packages/design-system/src/components/Link/Link.stories.tsx new file mode 100644 index 0000000..a41b7f7 --- /dev/null +++ b/packages/design-system/src/components/Link/Link.stories.tsx @@ -0,0 +1,70 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Link } from './Link'; + +const meta: Meta = { + title: 'Navigation/Link', + component: Link, + argTypes: { + tone: { + control: 'select', + options: ['default', 'secondary', 'positive', 'negative', 'muted'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + href: '#', + children: 'Перейти к котировкам', + }, +}; + +export const External: Story = { + args: { + href: 'https://www.moex.com', + target: '_blank', + rel: 'noopener noreferrer', + children: 'Московская Биржа', + }, +}; + +export const Positive: Story = { + args: { + href: '#', + tone: 'positive', + children: 'Рост акций', + }, +}; + +export const Negative: Story = { + args: { + href: '#', + tone: 'negative', + children: 'Падение индекса', + }, +}; + +export const AllTones: Story = { + render: () => ( +
+ + Основная ссылка + + + Второстепенная ссылка + + + Рост +5.2% + + + Падение -2.1% + + + Неактивная ссылка + +
+ ), +}; diff --git a/packages/design-system/src/components/Link/Link.test.tsx b/packages/design-system/src/components/Link/Link.test.tsx new file mode 100644 index 0000000..507cae8 --- /dev/null +++ b/packages/design-system/src/components/Link/Link.test.tsx @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Link } from './Link'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Link', () => { + it('renders children', () => { + renderWithTheme(Click); + expect(screen.getByText('Click')).toBeInTheDocument(); + }); + + it('renders as an anchor with href', () => { + renderWithTheme(Click); + const el = screen.getByText('Click'); + expect(el.tagName).toBe('A'); + expect(el).toHaveAttribute('href', '/test'); + }); + + it('does not pass color prop to DOM', () => { + renderWithTheme( + + Click + , + ); + const el = screen.getByText('Click'); + expect(el).not.toHaveAttribute('color'); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + Click + , + ); + const el = screen.getByText('Click'); + expect(el.classList.contains('custom')).toBe(true); + }); + + it('renders with target and rel for external links', () => { + renderWithTheme( + + External + , + ); + const el = screen.getByText('External'); + expect(el).toHaveAttribute('target', '_blank'); + expect(el).toHaveAttribute('rel', 'noopener'); + }); +}); diff --git a/packages/design-system/src/components/Link/Link.tsx b/packages/design-system/src/components/Link/Link.tsx new file mode 100644 index 0000000..d784605 --- /dev/null +++ b/packages/design-system/src/components/Link/Link.tsx @@ -0,0 +1,25 @@ +import { Link as MuiLink, type LinkProps as MuiLinkProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Tone = 'default' | 'secondary' | 'positive' | 'negative' | 'muted'; + +export interface LinkProps extends Omit { + tone?: Tone; + children: ReactNode; +} + +const TONE_MAP: Record = { + default: 'primary', + secondary: 'text.secondary', + positive: 'success.main', + negative: 'error.main', + muted: 'text.disabled', +}; + +export function Link({ tone = 'default', children, ...props }: LinkProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Link/index.ts b/packages/design-system/src/components/Link/index.ts new file mode 100644 index 0000000..ed48706 --- /dev/null +++ b/packages/design-system/src/components/Link/index.ts @@ -0,0 +1,2 @@ +export { Link } from './Link'; +export type { LinkProps } from './Link'; diff --git a/packages/design-system/src/components/Text/Text.stories.tsx b/packages/design-system/src/components/Text/Text.stories.tsx new file mode 100644 index 0000000..d08e553 --- /dev/null +++ b/packages/design-system/src/components/Text/Text.stories.tsx @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Text } from './Text'; + +const meta: Meta = { + title: 'Typography/Text', + component: Text, + argTypes: { + variant: { + control: 'select', + options: ['body', 'caption', 'label', 'numeric'], + }, + tone: { + control: 'select', + options: ['default', 'secondary', 'positive', 'negative', 'muted'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Body: Story = { + args: { + children: + 'Акции Московской биржи выросли на 2.3% по итогам торговой сессии. Индекс Мосбиржи обновил исторический максимум, достигнув отметки 4500 пунктов.', + }, +}; + +export const Caption: Story = { + args: { + variant: 'caption', + children: 'Данные предоставлены Московской биржей. Цены указаны в рублях.', + }, +}; + +export const Label: Story = { + args: { + variant: 'label', + children: 'Объём торгов', + }, +}; + +export const Numeric: Story = { + args: { + variant: 'numeric', + children: '1 234 567.89 ₽', + }, +}; + +export const PositiveTone: Story = { + args: { + tone: 'positive', + children: '+5.23%', + }, +}; + +export const NegativeTone: Story = { + args: { + tone: 'negative', + children: '-2.15%', + }, +}; + +export const SecondaryTone: Story = { + args: { + tone: 'secondary', + children: 'Дополнительная информация о ценной бумаге', + }, +}; + +export const MutedTone: Story = { + args: { + tone: 'muted', + children: 'Обновлено 5 минут назад', + }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ Body: основной текст страницы + Caption: вспомогательный текст + Label: подпись к полю или метрике + Numeric: 1 234.56 +
+ ), +}; + +export const AllTones: Story = { + render: () => ( +
+ Default — основной текст + Secondary — второстепенный текст + Positive — рост, прибыль + Negative — падение, убыток + Muted — неактивный, подсказка +
+ ), +}; diff --git a/packages/design-system/src/components/Text/Text.test.tsx b/packages/design-system/src/components/Text/Text.test.tsx new file mode 100644 index 0000000..5951612 --- /dev/null +++ b/packages/design-system/src/components/Text/Text.test.tsx @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Text } from './Text'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Text', () => { + it('renders children', () => { + renderWithTheme(Hello); + expect(screen.getByText('Hello')).toBeInTheDocument(); + }); + + it('renders with body variant by default', () => { + renderWithTheme(Body); + const el = screen.getByText('Body'); + expect(el.tagName).toBe('P'); + }); + + it('renders caption variant as MUI caption', () => { + renderWithTheme(Caption); + const el = screen.getByText('Caption'); + expect(el.classList.contains('MuiTypography-caption')).toBe(true); + }); + + it('renders label variant', () => { + renderWithTheme(Label); + expect(screen.getByText('Label')).toBeInTheDocument(); + }); + + it('renders numeric variant', () => { + renderWithTheme(123); + expect(screen.getByText('123')).toBeInTheDocument(); + }); + + it('applies default tone by default', () => { + renderWithTheme(Default); + const el = screen.getByText('Default'); + expect(el.classList.contains('MuiTypography-body1')).toBe(true); + }); + + it('accepts className and style props', () => { + renderWithTheme( + + Hello + , + ); + const el = screen.getByText('Hello'); + expect(el.classList.contains('custom')).toBe(true); + }); +}); diff --git a/packages/design-system/src/components/Text/Text.tsx b/packages/design-system/src/components/Text/Text.tsx new file mode 100644 index 0000000..ded9fee --- /dev/null +++ b/packages/design-system/src/components/Text/Text.tsx @@ -0,0 +1,35 @@ +import { Typography, type TypographyProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Tone = 'default' | 'secondary' | 'positive' | 'negative' | 'muted'; + +type TextVariant = 'body' | 'caption' | 'label' | 'numeric'; + +export interface TextProps extends Omit { + variant?: TextVariant; + tone?: Tone; + children: ReactNode; +} + +const VARIANT_MAP: Record = { + body: 'body1', + caption: 'caption', + label: 'subtitle2', + numeric: 'body1', +}; + +const TONE_MAP: Record = { + default: 'text.primary', + secondary: 'text.secondary', + positive: 'success.main', + negative: 'error.main', + muted: 'text.disabled', +}; + +export function Text({ variant = 'body', tone = 'default', children, ...props }: TextProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Text/index.ts b/packages/design-system/src/components/Text/index.ts new file mode 100644 index 0000000..3e79f02 --- /dev/null +++ b/packages/design-system/src/components/Text/index.ts @@ -0,0 +1,2 @@ +export { Text } from './Text'; +export type { TextProps } from './Text'; diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts new file mode 100644 index 0000000..7f583ee --- /dev/null +++ b/packages/design-system/src/components/index.ts @@ -0,0 +1,14 @@ +export { Text } from './Text'; +export type { TextProps } from './Text'; + +export { Heading } from './Heading'; +export type { HeadingProps } from './Heading'; + +export { Link } from './Link'; +export type { LinkProps } from './Link'; + +export { Button } from './Button'; +export type { ButtonProps } from './Button'; + +export { IconButton } from './IconButton'; +export type { IconButtonProps } from './IconButton'; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts new file mode 100644 index 0000000..07635cb --- /dev/null +++ b/packages/design-system/src/index.ts @@ -0,0 +1 @@ +export * from './components'; -- 2.47.2 From 55221dbf2235bcae64304eb1eaaaba3486fc91fa Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:44:12 +0300 Subject: [PATCH 08/12] feat(design-system): add form and page-state patterns - FormField: label, htmlFor, helperText, error, required, children - FilterBar: responsive layout container with children and actions - EmptyState: semantic heading with optional description and action - ErrorState: semantic heading with optional description and action - LoadingState: aria-live polite, section/page sizes, reduced-motion --- .../EmptyState/EmptyState.stories.tsx | 36 +++++++++++ .../components/EmptyState/EmptyState.test.tsx | 26 ++++++++ .../src/components/EmptyState/EmptyState.tsx | 20 ++++++ .../src/components/EmptyState/index.ts | 2 + .../ErrorState/ErrorState.stories.tsx | 36 +++++++++++ .../components/ErrorState/ErrorState.test.tsx | 26 ++++++++ .../src/components/ErrorState/ErrorState.tsx | 20 ++++++ .../src/components/ErrorState/index.ts | 2 + .../FilterBar/FilterBar.stories.tsx | 38 +++++++++++ .../components/FilterBar/FilterBar.test.tsx | 28 ++++++++ .../src/components/FilterBar/FilterBar.tsx | 22 +++++++ .../src/components/FilterBar/index.ts | 2 + .../FormField/FormField.stories.tsx | 64 +++++++++++++++++++ .../components/FormField/FormField.test.tsx | 48 ++++++++++++++ .../src/components/FormField/FormField.tsx | 28 ++++++++ .../src/components/FormField/index.ts | 2 + .../LoadingState/LoadingState.stories.tsx | 41 ++++++++++++ .../LoadingState/LoadingState.test.tsx | 38 +++++++++++ .../components/LoadingState/LoadingState.tsx | 30 +++++++++ .../src/components/LoadingState/index.ts | 2 + .../design-system/src/components/index.ts | 60 +++++++++++++++++ 21 files changed, 571 insertions(+) create mode 100644 packages/design-system/src/components/EmptyState/EmptyState.stories.tsx create mode 100644 packages/design-system/src/components/EmptyState/EmptyState.test.tsx create mode 100644 packages/design-system/src/components/EmptyState/EmptyState.tsx create mode 100644 packages/design-system/src/components/EmptyState/index.ts create mode 100644 packages/design-system/src/components/ErrorState/ErrorState.stories.tsx create mode 100644 packages/design-system/src/components/ErrorState/ErrorState.test.tsx create mode 100644 packages/design-system/src/components/ErrorState/ErrorState.tsx create mode 100644 packages/design-system/src/components/ErrorState/index.ts create mode 100644 packages/design-system/src/components/FilterBar/FilterBar.stories.tsx create mode 100644 packages/design-system/src/components/FilterBar/FilterBar.test.tsx create mode 100644 packages/design-system/src/components/FilterBar/FilterBar.tsx create mode 100644 packages/design-system/src/components/FilterBar/index.ts create mode 100644 packages/design-system/src/components/FormField/FormField.stories.tsx create mode 100644 packages/design-system/src/components/FormField/FormField.test.tsx create mode 100644 packages/design-system/src/components/FormField/FormField.tsx create mode 100644 packages/design-system/src/components/FormField/index.ts create mode 100644 packages/design-system/src/components/LoadingState/LoadingState.stories.tsx create mode 100644 packages/design-system/src/components/LoadingState/LoadingState.test.tsx create mode 100644 packages/design-system/src/components/LoadingState/LoadingState.tsx create mode 100644 packages/design-system/src/components/LoadingState/index.ts diff --git a/packages/design-system/src/components/EmptyState/EmptyState.stories.tsx b/packages/design-system/src/components/EmptyState/EmptyState.stories.tsx new file mode 100644 index 0000000..5331a49 --- /dev/null +++ b/packages/design-system/src/components/EmptyState/EmptyState.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '../Button'; +import { EmptyState } from './EmptyState'; + +const meta: Meta = { + title: 'Feedback/EmptyState', + component: EmptyState, + argTypes: { + title: { control: 'text' }, + description: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + title: 'No data available', + description: 'Try adjusting your filters or date range.', + }, +}; + +export const WithAction: Story = { + args: { + title: 'No securities found', + description: 'Try a different search query.', + action: , + }, +}; + +export const Minimal: Story = { + args: { + title: 'Nothing here yet', + }, +}; diff --git a/packages/design-system/src/components/EmptyState/EmptyState.test.tsx b/packages/design-system/src/components/EmptyState/EmptyState.test.tsx new file mode 100644 index 0000000..ab70e44 --- /dev/null +++ b/packages/design-system/src/components/EmptyState/EmptyState.test.tsx @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { EmptyState } from './EmptyState'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('EmptyState', () => { + it('renders title as semantic heading', () => { + renderWithTheme(); + const heading = screen.getByText('No data'); + expect(heading.tagName).toBe('H2'); + }); + + it('renders description', () => { + renderWithTheme(); + expect(screen.getByText('Try adjusting filters')).toBeInTheDocument(); + }); + + it('renders action', () => { + renderWithTheme(Refresh} />); + expect(screen.getByRole('button', { name: 'Refresh' })).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/EmptyState/EmptyState.tsx b/packages/design-system/src/components/EmptyState/EmptyState.tsx new file mode 100644 index 0000000..80eb7f2 --- /dev/null +++ b/packages/design-system/src/components/EmptyState/EmptyState.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react'; +import { Stack } from '@mui/material'; +import { Heading } from '../Heading'; +import { Text } from '../Text'; + +export interface EmptyStateProps { + title: string; + description?: string; + action?: ReactNode; +} + +export function EmptyState({ title, description, action }: EmptyStateProps) { + return ( + + {title} + {description && {description}} + {action &&
{action}
} +
+ ); +} diff --git a/packages/design-system/src/components/EmptyState/index.ts b/packages/design-system/src/components/EmptyState/index.ts new file mode 100644 index 0000000..0d24236 --- /dev/null +++ b/packages/design-system/src/components/EmptyState/index.ts @@ -0,0 +1,2 @@ +export { EmptyState } from './EmptyState'; +export type { EmptyStateProps } from './EmptyState'; diff --git a/packages/design-system/src/components/ErrorState/ErrorState.stories.tsx b/packages/design-system/src/components/ErrorState/ErrorState.stories.tsx new file mode 100644 index 0000000..f7efff5 --- /dev/null +++ b/packages/design-system/src/components/ErrorState/ErrorState.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '../Button'; +import { ErrorState } from './ErrorState'; + +const meta: Meta = { + title: 'Feedback/ErrorState', + component: ErrorState, + argTypes: { + title: { control: 'text' }, + description: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + title: 'Failed to load', + description: 'An unexpected error occurred. Please try again.', + }, +}; + +export const WithRetry: Story = { + args: { + title: 'Connection lost', + description: 'Could not reach the server.', + action: , + }, +}; + +export const Minimal: Story = { + args: { + title: 'Error loading data', + }, +}; diff --git a/packages/design-system/src/components/ErrorState/ErrorState.test.tsx b/packages/design-system/src/components/ErrorState/ErrorState.test.tsx new file mode 100644 index 0000000..699f700 --- /dev/null +++ b/packages/design-system/src/components/ErrorState/ErrorState.test.tsx @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { ErrorState } from './ErrorState'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('ErrorState', () => { + it('renders title as semantic heading', () => { + renderWithTheme(); + const heading = screen.getByText('Error loading'); + expect(heading.tagName).toBe('H2'); + }); + + it('renders description', () => { + renderWithTheme(); + expect(screen.getByText('Something went wrong')).toBeInTheDocument(); + }); + + it('renders action', () => { + renderWithTheme(Retry} />); + expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/ErrorState/ErrorState.tsx b/packages/design-system/src/components/ErrorState/ErrorState.tsx new file mode 100644 index 0000000..f4fd49f --- /dev/null +++ b/packages/design-system/src/components/ErrorState/ErrorState.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react'; +import { Stack } from '@mui/material'; +import { Heading } from '../Heading'; +import { Text } from '../Text'; + +export interface ErrorStateProps { + title: string; + description?: string; + action?: ReactNode; +} + +export function ErrorState({ title, description, action }: ErrorStateProps) { + return ( + + {title} + {description && {description}} + {action &&
{action}
} +
+ ); +} diff --git a/packages/design-system/src/components/ErrorState/index.ts b/packages/design-system/src/components/ErrorState/index.ts new file mode 100644 index 0000000..345d022 --- /dev/null +++ b/packages/design-system/src/components/ErrorState/index.ts @@ -0,0 +1,2 @@ +export { ErrorState } from './ErrorState'; +export type { ErrorStateProps } from './ErrorState'; diff --git a/packages/design-system/src/components/FilterBar/FilterBar.stories.tsx b/packages/design-system/src/components/FilterBar/FilterBar.stories.tsx new file mode 100644 index 0000000..8a81bfb --- /dev/null +++ b/packages/design-system/src/components/FilterBar/FilterBar.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '../Button'; +import { TextField } from '../TextField'; +import { Select } from '../Select'; +import { FilterBar } from './FilterBar'; + +const meta: Meta = { + title: 'Layout/FilterBar', + component: FilterBar, +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: ( + <> + + + , + ); + expect(screen.getByPlaceholderText('Search')).toBeInTheDocument(); + }); + + it('renders actions', () => { + renderWithTheme( + Apply}> + + , + ); + expect(screen.getByRole('button', { name: 'Apply' })).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/FilterBar/FilterBar.tsx b/packages/design-system/src/components/FilterBar/FilterBar.tsx new file mode 100644 index 0000000..c1ba772 --- /dev/null +++ b/packages/design-system/src/components/FilterBar/FilterBar.tsx @@ -0,0 +1,22 @@ +import type { ReactNode } from 'react'; +import { Stack } from '@mui/material'; + +export interface FilterBarProps { + children: ReactNode; + actions?: ReactNode; +} + +export function FilterBar({ children, actions }: FilterBarProps) { + return ( + + + {children} + + {actions && ( + + {actions} + + )} + + ); +} diff --git a/packages/design-system/src/components/FilterBar/index.ts b/packages/design-system/src/components/FilterBar/index.ts new file mode 100644 index 0000000..38debcb --- /dev/null +++ b/packages/design-system/src/components/FilterBar/index.ts @@ -0,0 +1,2 @@ +export { FilterBar } from './FilterBar'; +export type { FilterBarProps } from './FilterBar'; diff --git a/packages/design-system/src/components/FormField/FormField.stories.tsx b/packages/design-system/src/components/FormField/FormField.stories.tsx new file mode 100644 index 0000000..257d88e --- /dev/null +++ b/packages/design-system/src/components/FormField/FormField.stories.tsx @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { TextField } from '../TextField'; +import { Select } from '../Select'; +import { FormField } from './FormField'; + +const meta: Meta = { + title: 'Form/FormField', + component: FormField, + argTypes: { + label: { control: 'text' }, + helperText: { control: 'text' }, + error: { control: 'text' }, + required: { control: 'boolean' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const WithTextField: Story = { + args: { + label: 'Username', + htmlFor: 'username', + helperText: 'Enter your username', + children: , + }, +}; + +export const Required: Story = { + args: { + label: 'Email', + htmlFor: 'email', + required: true, + helperText: 'We will never share your email', + children: , + }, +}; + +export const WithError: Story = { + args: { + label: 'Name', + htmlFor: 'name', + error: 'Name is required', + children: , + }, +}; + +export const WithSelect: Story = { + args: { + label: 'Country', + htmlFor: 'country', + children: ( + + , + ); + const label = screen.getByText('Username'); + expect(label.tagName).toBe('LABEL'); + expect(label).toHaveAttribute('for', 'username'); + }); + + it('renders helper text', () => { + renderWithTheme( + + + , + ); + expect(screen.getByText('Enter your email')).toBeInTheDocument(); + }); + + it('renders error text and associates with input', () => { + renderWithTheme( + + + , + ); + expect(screen.getByText('Name is required')).toBeInTheDocument(); + }); + + it('renders required indicator', () => { + renderWithTheme( + + + , + ); + expect(screen.getByText('*')).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/FormField/FormField.tsx b/packages/design-system/src/components/FormField/FormField.tsx new file mode 100644 index 0000000..a329bfc --- /dev/null +++ b/packages/design-system/src/components/FormField/FormField.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from 'react'; +import { FormControl, FormLabel, FormHelperText, Box } from '@mui/material'; + +export interface FormFieldProps { + label: string; + htmlFor: string; + helperText?: string; + error?: string; + required?: boolean; + children: ReactNode; +} + +export function FormField({ + label, + htmlFor, + helperText, + error, + required, + children, +}: FormFieldProps) { + return ( + + {label} + {children} + {(helperText || error) && {error || helperText}} + + ); +} diff --git a/packages/design-system/src/components/FormField/index.ts b/packages/design-system/src/components/FormField/index.ts new file mode 100644 index 0000000..269f0c7 --- /dev/null +++ b/packages/design-system/src/components/FormField/index.ts @@ -0,0 +1,2 @@ +export { FormField } from './FormField'; +export type { FormFieldProps } from './FormField'; diff --git a/packages/design-system/src/components/LoadingState/LoadingState.stories.tsx b/packages/design-system/src/components/LoadingState/LoadingState.stories.tsx new file mode 100644 index 0000000..49a3925 --- /dev/null +++ b/packages/design-system/src/components/LoadingState/LoadingState.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { LoadingState } from './LoadingState'; + +const meta: Meta = { + title: 'Feedback/LoadingState', + component: LoadingState, + argTypes: { + title: { control: 'text' }, + label: { control: 'text' }, + size: { + control: 'select', + options: ['section', 'page'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Section: Story = { + args: { + title: 'Loading data', + size: 'section', + }, +}; + +export const Page: Story = { + args: { + title: 'Loading your portfolio', + label: 'Please wait while we fetch your data', + size: 'page', + }, +}; + +export const WithLabel: Story = { + args: { + title: 'Processing', + label: 'This may take a moment', + size: 'section', + }, +}; diff --git a/packages/design-system/src/components/LoadingState/LoadingState.test.tsx b/packages/design-system/src/components/LoadingState/LoadingState.test.tsx new file mode 100644 index 0000000..1b0652b --- /dev/null +++ b/packages/design-system/src/components/LoadingState/LoadingState.test.tsx @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { LoadingState } from './LoadingState'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('LoadingState', () => { + it('has aria-live polite', () => { + renderWithTheme(); + const region = screen.getByRole('region'); + expect(region).toHaveAttribute('aria-live', 'polite'); + }); + + it('renders title', () => { + renderWithTheme(); + expect(screen.getByText('Loading data')).toBeInTheDocument(); + }); + + it('renders label', () => { + renderWithTheme(); + expect(screen.getByText('Fetching portfolio')).toBeInTheDocument(); + }); + + it('renders with section size by default', () => { + renderWithTheme(); + const region = screen.getByRole('region'); + expect(region).toHaveAttribute('data-size', 'section'); + }); + + it('renders with page size', () => { + renderWithTheme(); + const region = screen.getByRole('region'); + expect(region).toHaveAttribute('data-size', 'page'); + }); +}); diff --git a/packages/design-system/src/components/LoadingState/LoadingState.tsx b/packages/design-system/src/components/LoadingState/LoadingState.tsx new file mode 100644 index 0000000..e8685bd --- /dev/null +++ b/packages/design-system/src/components/LoadingState/LoadingState.tsx @@ -0,0 +1,30 @@ +import { Box, Stack, CircularProgress } from '@mui/material'; +import { Heading } from '../Heading'; +import { Text } from '../Text'; + +export interface LoadingStateProps { + title: string; + label?: string; + size?: 'section' | 'page'; +} + +export function LoadingState({ title, label, size = 'section' }: LoadingStateProps) { + const spacing = size === 'page' ? 3 : 2; + const progressSize = size === 'page' ? 48 : 32; + + return ( + + + + {title} + {label && {label}} + + + ); +} diff --git a/packages/design-system/src/components/LoadingState/index.ts b/packages/design-system/src/components/LoadingState/index.ts new file mode 100644 index 0000000..59024cd --- /dev/null +++ b/packages/design-system/src/components/LoadingState/index.ts @@ -0,0 +1,2 @@ +export { LoadingState } from './LoadingState'; +export type { LoadingStateProps } from './LoadingState'; diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts index 7f583ee..9f98132 100644 --- a/packages/design-system/src/components/index.ts +++ b/packages/design-system/src/components/index.ts @@ -12,3 +12,63 @@ export type { ButtonProps } from './Button'; export { IconButton } from './IconButton'; export type { IconButtonProps } from './IconButton'; + +export { TextField } from './TextField'; +export type { TextFieldProps } from './TextField'; + +export { Select } from './Select'; +export type { SelectProps, SelectOption } from './Select'; + +export { Checkbox } from './Checkbox'; +export type { CheckboxProps } from './Checkbox'; + +export { Surface } from './Surface'; +export type { SurfaceProps } from './Surface'; + +export { Card } from './Card'; +export type { CardProps } from './Card'; + +export { Chip } from './Chip'; +export type { ChipProps } from './Chip'; + +export { Badge } from './Badge'; +export type { BadgeProps } from './Badge'; + +export { Alert } from './Alert'; +export type { AlertProps } from './Alert'; + +export { Dialog } from './Dialog'; +export type { DialogProps } from './Dialog'; + +export { Skeleton } from './Skeleton'; +export type { SkeletonProps } from './Skeleton'; + +export { Progress } from './Progress'; +export type { ProgressProps } from './Progress'; + +export { DataTable } from './DataTable'; +export type { DataTableProps, Density } from './DataTable'; + +export { Metric } from './Metric'; +export type { MetricProps } from './Metric'; + +export { Money } from './Money'; +export type { MoneyProps } from './Money'; + +export { PriceChange } from './PriceChange'; +export type { PriceChangeProps } from './PriceChange'; + +export { FormField } from './FormField'; +export type { FormFieldProps } from './FormField'; + +export { FilterBar } from './FilterBar'; +export type { FilterBarProps } from './FilterBar'; + +export { EmptyState } from './EmptyState'; +export type { EmptyStateProps } from './EmptyState'; + +export { ErrorState } from './ErrorState'; +export type { ErrorStateProps } from './ErrorState'; + +export { LoadingState } from './LoadingState'; +export type { LoadingStateProps } from './LoadingState'; -- 2.47.2 From 53659a36b13436a32e66de639051e0159e0d3ef5 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 09:49:18 +0300 Subject: [PATCH 09/12] feat(design-system): add data components, form/page-state patterns, and frontend integration - Inputs: TextField, Select, Checkbox - Surfaces: Surface, Card, Chip, Badge - Feedback: Alert, Dialog, Skeleton, Progress - Financial data: DataTable, Metric, Money, PriceChange - Form & page-state: FormField, FilterBar, EmptyState, ErrorState, LoadingState - MUI CssVarsProvider -> ThemeProvider deprecation fix - Inter font bundled via @fontsource/inter - ESLint no-restricted-imports (warn) for gradual migration - storybook-static gitignored All 268 tests pass (157 design-system + 111 frontend) --- apps/frontend/.eslintrc.cjs | 6 + apps/frontend/package.json | 3 +- .../src/app/providers/AppProviders.tsx | 13 +- apps/frontend/src/app/styles/theme.ts | 20 --- package-lock.json | 11 +- packages/design-system/.eslintrc.cjs | 28 ++++ packages/design-system/.gitignore | 1 + packages/design-system/package.json | 50 ++++++++ .../src/components/Alert/Alert.stories.tsx | 69 ++++++++++ .../src/components/Alert/Alert.test.tsx | 54 ++++++++ .../src/components/Alert/Alert.tsx | 20 +++ .../src/components/Alert/index.ts | 2 + .../src/components/Badge/Badge.stories.tsx | 41 ++++++ .../src/components/Badge/Badge.test.tsx | 31 +++++ .../src/components/Badge/Badge.tsx | 17 +++ .../src/components/Badge/index.ts | 2 + .../src/components/Card/Card.stories.tsx | 56 ++++++++ .../src/components/Card/Card.test.tsx | 39 ++++++ .../src/components/Card/Card.tsx | 17 +++ .../src/components/Card/index.ts | 2 + .../components/Checkbox/Checkbox.stories.tsx | 51 ++++++++ .../src/components/Checkbox/Checkbox.test.tsx | 57 +++++++++ .../src/components/Checkbox/Checkbox.tsx | 26 ++++ .../src/components/Checkbox/index.ts | 2 + .../src/components/Chip/Chip.stories.tsx | 74 +++++++++++ .../src/components/Chip/Chip.test.tsx | 62 +++++++++ .../src/components/Chip/Chip.tsx | 29 +++++ .../src/components/Chip/index.ts | 2 + .../DataTable/DataTable.stories.tsx | 121 ++++++++++++++++++ .../components/DataTable/DataTable.test.tsx | 97 ++++++++++++++ .../src/components/DataTable/DataTable.tsx | 73 +++++++++++ .../src/components/DataTable/index.ts | 2 + .../src/components/Dialog/Dialog.stories.tsx | 61 +++++++++ .../src/components/Dialog/Dialog.test.tsx | 81 ++++++++++++ .../src/components/Dialog/Dialog.tsx | 23 ++++ .../src/components/Dialog/index.ts | 2 + .../src/components/EmptyState/EmptyState.tsx | 2 +- .../src/components/ErrorState/ErrorState.tsx | 2 +- .../components/LoadingState/LoadingState.tsx | 2 +- .../src/components/Metric/Metric.stories.tsx | 39 ++++++ .../src/components/Metric/Metric.test.tsx | 26 ++++ .../src/components/Metric/Metric.tsx | 29 +++++ .../src/components/Metric/index.ts | 2 + .../src/components/Money/Money.stories.tsx | 52 ++++++++ .../src/components/Money/Money.test.tsx | 35 +++++ .../src/components/Money/Money.tsx | 23 ++++ .../src/components/Money/index.ts | 2 + .../PriceChange/PriceChange.stories.tsx | 41 ++++++ .../PriceChange/PriceChange.test.tsx | 38 ++++++ .../components/PriceChange/PriceChange.tsx | 50 ++++++++ .../src/components/PriceChange/index.ts | 2 + .../components/Progress/Progress.stories.tsx | 34 +++++ .../src/components/Progress/Progress.test.tsx | 40 ++++++ .../src/components/Progress/Progress.tsx | 25 ++++ .../src/components/Progress/index.ts | 2 + .../src/components/Select/Select.stories.tsx | 64 +++++++++ .../src/components/Select/Select.test.tsx | 61 +++++++++ .../src/components/Select/Select.tsx | 56 ++++++++ .../src/components/Select/index.ts | 2 + .../components/Skeleton/Skeleton.stories.tsx | 60 +++++++++ .../src/components/Skeleton/Skeleton.test.tsx | 46 +++++++ .../src/components/Skeleton/Skeleton.tsx | 29 +++++ .../src/components/Skeleton/index.ts | 2 + .../components/Surface/Surface.stories.tsx | 52 ++++++++ .../src/components/Surface/Surface.test.tsx | 49 +++++++ .../src/components/Surface/Surface.tsx | 32 +++++ .../src/components/Surface/index.ts | 2 + .../TextField/TextField.stories.tsx | 56 ++++++++ .../components/TextField/TextField.test.tsx | 44 +++++++ .../src/components/TextField/TextField.tsx | 22 ++++ .../src/components/TextField/index.ts | 2 + packages/design-system/src/test/setup.ts | 15 +++ .../src/theme/MoexVibeThemeProvider.tsx | 5 +- packages/design-system/tsconfig.json | 25 ++++ packages/design-system/vitest.config.ts | 13 ++ 75 files changed, 2290 insertions(+), 38 deletions(-) delete mode 100644 apps/frontend/src/app/styles/theme.ts create mode 100644 packages/design-system/.eslintrc.cjs create mode 100644 packages/design-system/.gitignore create mode 100644 packages/design-system/package.json create mode 100644 packages/design-system/src/components/Alert/Alert.stories.tsx create mode 100644 packages/design-system/src/components/Alert/Alert.test.tsx create mode 100644 packages/design-system/src/components/Alert/Alert.tsx create mode 100644 packages/design-system/src/components/Alert/index.ts create mode 100644 packages/design-system/src/components/Badge/Badge.stories.tsx create mode 100644 packages/design-system/src/components/Badge/Badge.test.tsx create mode 100644 packages/design-system/src/components/Badge/Badge.tsx create mode 100644 packages/design-system/src/components/Badge/index.ts create mode 100644 packages/design-system/src/components/Card/Card.stories.tsx create mode 100644 packages/design-system/src/components/Card/Card.test.tsx create mode 100644 packages/design-system/src/components/Card/Card.tsx create mode 100644 packages/design-system/src/components/Card/index.ts create mode 100644 packages/design-system/src/components/Checkbox/Checkbox.stories.tsx create mode 100644 packages/design-system/src/components/Checkbox/Checkbox.test.tsx create mode 100644 packages/design-system/src/components/Checkbox/Checkbox.tsx create mode 100644 packages/design-system/src/components/Checkbox/index.ts create mode 100644 packages/design-system/src/components/Chip/Chip.stories.tsx create mode 100644 packages/design-system/src/components/Chip/Chip.test.tsx create mode 100644 packages/design-system/src/components/Chip/Chip.tsx create mode 100644 packages/design-system/src/components/Chip/index.ts create mode 100644 packages/design-system/src/components/DataTable/DataTable.stories.tsx create mode 100644 packages/design-system/src/components/DataTable/DataTable.test.tsx create mode 100644 packages/design-system/src/components/DataTable/DataTable.tsx create mode 100644 packages/design-system/src/components/DataTable/index.ts create mode 100644 packages/design-system/src/components/Dialog/Dialog.stories.tsx create mode 100644 packages/design-system/src/components/Dialog/Dialog.test.tsx create mode 100644 packages/design-system/src/components/Dialog/Dialog.tsx create mode 100644 packages/design-system/src/components/Dialog/index.ts create mode 100644 packages/design-system/src/components/Metric/Metric.stories.tsx create mode 100644 packages/design-system/src/components/Metric/Metric.test.tsx create mode 100644 packages/design-system/src/components/Metric/Metric.tsx create mode 100644 packages/design-system/src/components/Metric/index.ts create mode 100644 packages/design-system/src/components/Money/Money.stories.tsx create mode 100644 packages/design-system/src/components/Money/Money.test.tsx create mode 100644 packages/design-system/src/components/Money/Money.tsx create mode 100644 packages/design-system/src/components/Money/index.ts create mode 100644 packages/design-system/src/components/PriceChange/PriceChange.stories.tsx create mode 100644 packages/design-system/src/components/PriceChange/PriceChange.test.tsx create mode 100644 packages/design-system/src/components/PriceChange/PriceChange.tsx create mode 100644 packages/design-system/src/components/PriceChange/index.ts create mode 100644 packages/design-system/src/components/Progress/Progress.stories.tsx create mode 100644 packages/design-system/src/components/Progress/Progress.test.tsx create mode 100644 packages/design-system/src/components/Progress/Progress.tsx create mode 100644 packages/design-system/src/components/Progress/index.ts create mode 100644 packages/design-system/src/components/Select/Select.stories.tsx create mode 100644 packages/design-system/src/components/Select/Select.test.tsx create mode 100644 packages/design-system/src/components/Select/Select.tsx create mode 100644 packages/design-system/src/components/Select/index.ts create mode 100644 packages/design-system/src/components/Skeleton/Skeleton.stories.tsx create mode 100644 packages/design-system/src/components/Skeleton/Skeleton.test.tsx create mode 100644 packages/design-system/src/components/Skeleton/Skeleton.tsx create mode 100644 packages/design-system/src/components/Skeleton/index.ts create mode 100644 packages/design-system/src/components/Surface/Surface.stories.tsx create mode 100644 packages/design-system/src/components/Surface/Surface.test.tsx create mode 100644 packages/design-system/src/components/Surface/Surface.tsx create mode 100644 packages/design-system/src/components/Surface/index.ts create mode 100644 packages/design-system/src/components/TextField/TextField.stories.tsx create mode 100644 packages/design-system/src/components/TextField/TextField.test.tsx create mode 100644 packages/design-system/src/components/TextField/TextField.tsx create mode 100644 packages/design-system/src/components/TextField/index.ts create mode 100644 packages/design-system/src/test/setup.ts create mode 100644 packages/design-system/tsconfig.json create mode 100644 packages/design-system/vitest.config.ts diff --git a/apps/frontend/.eslintrc.cjs b/apps/frontend/.eslintrc.cjs index 6d96ee8..22f9c0a 100644 --- a/apps/frontend/.eslintrc.cjs +++ b/apps/frontend/.eslintrc.cjs @@ -29,6 +29,12 @@ module.exports = { }, ignorePatterns: ['.eslintrc.cjs', 'vite.config.ts', 'vitest.config.ts', 'dist/'], rules: { + 'no-restricted-imports': ['warn', { + paths: [{ + name: '@mui/material', + message: 'Import from @moex-vibe/design-system instead.', + }], + }], '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], '@typescript-eslint/no-explicit-any': 'off', 'react/react-in-jsx-scope': 'off', diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 9d09f79..cef656f 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -15,7 +15,8 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@fontsource/roboto": "^5.2.10", + "@fontsource/inter": "^5.2.8", + "@moex-vibe/design-system": "*", "@hookform/resolvers": "^3.10.0", "@mui/icons-material": "^6.5.0", "@mui/material": "^6.5.0", diff --git a/apps/frontend/src/app/providers/AppProviders.tsx b/apps/frontend/src/app/providers/AppProviders.tsx index 63f073b..2c24c08 100644 --- a/apps/frontend/src/app/providers/AppProviders.tsx +++ b/apps/frontend/src/app/providers/AppProviders.tsx @@ -1,9 +1,11 @@ import { type ReactNode } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ThemeProvider } from '@mui/material/styles'; -import CssBaseline from '@mui/material/CssBaseline'; +import '@fontsource/inter/400.css'; +import '@fontsource/inter/500.css'; +import '@fontsource/inter/600.css'; +import '@fontsource/inter/700.css'; +import { MoexVibeThemeProvider } from '@moex-vibe/design-system/theme'; import { SessionProvider } from './SessionProvider'; -import { theme } from '@/app/styles/theme'; const queryClient = new QueryClient({ defaultOptions: { @@ -17,11 +19,10 @@ const queryClient = new QueryClient({ export function AppProviders({ children }: { children: ReactNode }) { return ( - - + {children} - + ); } diff --git a/apps/frontend/src/app/styles/theme.ts b/apps/frontend/src/app/styles/theme.ts deleted file mode 100644 index 50e3776..0000000 --- a/apps/frontend/src/app/styles/theme.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createTheme } from '@mui/material/styles'; - -export const theme = createTheme({ - cssVariables: true, - colorSchemes: { - light: { - palette: { - primary: { main: '#1976d2' }, - success: { main: '#2e7d32' }, - error: { main: '#c62828' }, - background: { default: '#f5f5f5', paper: '#ffffff' }, - }, - }, - }, - typography: { - fontFamily: ['-apple-system', 'BlinkMacSystemFont', "'Segoe UI'", 'Roboto', 'sans-serif'].join( - ',', - ), - }, -}); diff --git a/package-lock.json b/package-lock.json index 312adbd..dc30eba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,8 +88,9 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@fontsource/roboto": "^5.2.10", + "@fontsource/inter": "^5.2.8", "@hookform/resolvers": "^3.10.0", + "@moex-vibe/design-system": "*", "@mui/icons-material": "^6.5.0", "@mui/material": "^6.5.0", "@tanstack/react-query": "^5.20.0", @@ -6650,10 +6651,10 @@ } } }, - "node_modules/@fontsource/roboto": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.10.tgz", - "integrity": "sha512-8HlA5FtSfz//oFSr2eL7GFXAiE7eIkcGOtx7tjsLKq+as702x9+GU7K95iDeWFapHC4M2hv9RrpXKRTGGBI8Zg==", + "node_modules/@fontsource/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" diff --git a/packages/design-system/.eslintrc.cjs b/packages/design-system/.eslintrc.cjs new file mode 100644 index 0000000..81b8a39 --- /dev/null +++ b/packages/design-system/.eslintrc.cjs @@ -0,0 +1,28 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + ecmaFeatures: { jsx: true }, + }, + plugins: ['@typescript-eslint/eslint-plugin', 'react', 'react-hooks'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + ], + root: true, + env: { + browser: true, + es2020: true, + node: true, + }, + settings: { + react: { version: 'detect' }, + }, + ignorePatterns: ['.eslintrc.cjs', 'vitest.config.ts', 'dist/', '.storybook/'], + rules: { + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', + 'react/react-in-jsx-scope': 'off', + }, +}; diff --git a/packages/design-system/.gitignore b/packages/design-system/.gitignore new file mode 100644 index 0000000..1591a0d --- /dev/null +++ b/packages/design-system/.gitignore @@ -0,0 +1 @@ +storybook-static/ diff --git a/packages/design-system/package.json b/packages/design-system/package.json new file mode 100644 index 0000000..ad7d09a --- /dev/null +++ b/packages/design-system/package.json @@ -0,0 +1,50 @@ +{ + "name": "@moex-vibe/design-system", + "version": "0.1.0", + "private": true, + "type": "module", + "files": ["dist"], + "exports": { + ".": "./dist/index.js", + "./tokens": "./dist/tokens/index.js", + "./theme": "./dist/theme/index.js" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "test": "vitest run --project unit", + "storybook": "storybook dev -p 6006 --no-open", + "build-storybook": "storybook build", + "test:storybook": "vitest run --project storybook", + "lint": "eslint \"src/**/*.{ts,tsx}\"" + }, + "peerDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^6.5.0", + "@mui/material": "^6.5.0", + "@tanstack/react-table": "^8.21.3", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@storybook/addon-a11y": "^10.2.9", + "@storybook/addon-vitest": "^10.2.9", + "@storybook/react-vite": "^10.2.9", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "@vitest/browser": "^4.1.8", + "@vitest/browser-playwright": "^4.1.8", + "eslint": "^8.0.0", + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "playwright": "^1.55.0", + "storybook": "^10.2.9", + "typescript": "^5.3.0", + "vitest": "^4.1.8" + } +} diff --git a/packages/design-system/src/components/Alert/Alert.stories.tsx b/packages/design-system/src/components/Alert/Alert.stories.tsx new file mode 100644 index 0000000..aa537e7 --- /dev/null +++ b/packages/design-system/src/components/Alert/Alert.stories.tsx @@ -0,0 +1,69 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '../Button/Button'; +import { Alert } from './Alert'; + +const meta: Meta = { + title: 'Feedback/Alert', + component: Alert, + argTypes: { + severity: { + control: 'select', + options: ['info', 'success', 'warning', 'error'], + }, + title: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Info: Story = { + args: { + severity: 'info', + title: 'Информация', + children: 'Данные обновляются каждые 15 минут', + }, +}; + +export const Success: Story = { + args: { + severity: 'success', + title: 'Успешно', + children: 'Портфель успешно создан', + }, +}; + +export const Warning: Story = { + args: { + severity: 'warning', + title: 'Внимание', + children: 'Цена акции превысила лимит', + }, +}; + +export const Error: Story = { + args: { + severity: 'error', + title: 'Ошибка', + children: 'Не удалось загрузить данные', + }, +}; + +export const WithAction: Story = { + args: { + severity: 'warning', + children: 'Обновите страницу для получения актуальных данных', + action: ( + + ), + }, +}; + +export const WithoutTitle: Story = { + args: { + severity: 'info', + children: 'Просто информационное сообщение без заголовка', + }, +}; diff --git a/packages/design-system/src/components/Alert/Alert.test.tsx b/packages/design-system/src/components/Alert/Alert.test.tsx new file mode 100644 index 0000000..3d63b08 --- /dev/null +++ b/packages/design-system/src/components/Alert/Alert.test.tsx @@ -0,0 +1,54 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Alert } from './Alert'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Alert', () => { + it('has role alert', () => { + renderWithTheme(Message); + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + + it('renders severity mapping for info', () => { + renderWithTheme(Info message); + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + + it('renders severity mapping for success', () => { + renderWithTheme(Success message); + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + + it('renders severity mapping for warning', () => { + renderWithTheme(Warning message); + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + + it('renders severity mapping for error', () => { + renderWithTheme(Error message); + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); + + it('renders title', () => { + renderWithTheme( + + Something went wrong + , + ); + expect(screen.getByText('Error')).toBeInTheDocument(); + expect(screen.getByText('Something went wrong')).toBeInTheDocument(); + }); + + it('renders action', () => { + renderWithTheme( + Retry}> + Failed to load + , + ); + expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Alert/Alert.tsx b/packages/design-system/src/components/Alert/Alert.tsx new file mode 100644 index 0000000..73a63cc --- /dev/null +++ b/packages/design-system/src/components/Alert/Alert.tsx @@ -0,0 +1,20 @@ +import { Alert as MuiAlert, type AlertProps as MuiAlertProps } from '@mui/material'; +import type { ReactNode } from 'react'; + +type Severity = 'info' | 'success' | 'warning' | 'error'; + +export interface AlertProps extends Omit { + severity: Severity; + title?: string; + children?: ReactNode; + action?: ReactNode; +} + +export function Alert({ severity, title, children, action, ...props }: AlertProps) { + return ( + + {title &&
{title}
} + {children} +
+ ); +} diff --git a/packages/design-system/src/components/Alert/index.ts b/packages/design-system/src/components/Alert/index.ts new file mode 100644 index 0000000..7a4601f --- /dev/null +++ b/packages/design-system/src/components/Alert/index.ts @@ -0,0 +1,2 @@ +export { Alert } from './Alert'; +export type { AlertProps } from './Alert'; diff --git a/packages/design-system/src/components/Badge/Badge.stories.tsx b/packages/design-system/src/components/Badge/Badge.stories.tsx new file mode 100644 index 0000000..dd5ad31 --- /dev/null +++ b/packages/design-system/src/components/Badge/Badge.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import MailIcon from '@mui/icons-material/Mail'; +import { Badge } from './Badge'; + +const meta: Meta = { + title: 'Surfaces/Badge', + component: Badge, + argTypes: { + value: { control: 'number' }, + max: { control: 'number' }, + label: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + value: 5, + label: 'Уведомления', + children: , + }, +}; + +export const Overflow: Story = { + args: { + value: 150, + max: 99, + label: 'Уведомления', + children: , + }, +}; + +export const Zero: Story = { + args: { + value: 0, + label: 'Уведомления', + children: , + }, +}; diff --git a/packages/design-system/src/components/Badge/Badge.test.tsx b/packages/design-system/src/components/Badge/Badge.test.tsx new file mode 100644 index 0000000..4c95e11 --- /dev/null +++ b/packages/design-system/src/components/Badge/Badge.test.tsx @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Badge } from './Badge'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Badge', () => { + it('displays value', () => { + renderWithTheme(); + expect(screen.getByText('5')).toBeInTheDocument(); + }); + + it('shows max overflow', () => { + renderWithTheme(); + expect(screen.getByText('99+')).toBeInTheDocument(); + }); + + it('has accessible label', () => { + renderWithTheme(); + const badge = screen.getByLabelText('Notifications'); + expect(badge).toBeInTheDocument(); + }); + + it('does not render badge without label', () => { + const { container } = renderWithTheme(); + expect(container.querySelector('.MuiBadge-badge')).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Badge/Badge.tsx b/packages/design-system/src/components/Badge/Badge.tsx new file mode 100644 index 0000000..1cc1867 --- /dev/null +++ b/packages/design-system/src/components/Badge/Badge.tsx @@ -0,0 +1,17 @@ +import { Badge as MuiBadge } from '@mui/material'; +import type { ReactNode } from 'react'; + +export interface BadgeProps { + value: number; + max?: number; + label: string; + children?: ReactNode; +} + +export function Badge({ value, max = 99, label, children }: BadgeProps) { + return ( + + {children} + + ); +} diff --git a/packages/design-system/src/components/Badge/index.ts b/packages/design-system/src/components/Badge/index.ts new file mode 100644 index 0000000..cd3531d --- /dev/null +++ b/packages/design-system/src/components/Badge/index.ts @@ -0,0 +1,2 @@ +export { Badge } from './Badge'; +export type { BadgeProps } from './Badge'; diff --git a/packages/design-system/src/components/Card/Card.stories.tsx b/packages/design-system/src/components/Card/Card.stories.tsx new file mode 100644 index 0000000..c8ad0a9 --- /dev/null +++ b/packages/design-system/src/components/Card/Card.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '../Button/Button'; +import { Card } from './Card'; + +const meta: Meta = { + title: 'Surfaces/Card', + component: Card, + argTypes: { + padding: { + control: 'select', + options: ['none', 'sm', 'md', 'lg'], + }, + elevation: { + control: 'select', + options: ['none', 'sm', 'md'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Основное содержимое карточки', + }, +}; + +export const WithHeader: Story = { + args: { + header: Заголовок карточки, + children: 'Текст внутри карточки с заголовком', + }, +}; + +export const WithActions: Story = { + args: { + children: 'Карточка с действиями', + actions: , + }, +}; + +export const FullCard: Story = { + args: { + header: Портфель акций, + children: 'Состав портфеля: Сбербанк, Лукойл, Газпром', + actions: ( +
+ + +
+ ), + }, +}; diff --git a/packages/design-system/src/components/Card/Card.test.tsx b/packages/design-system/src/components/Card/Card.test.tsx new file mode 100644 index 0000000..d6866d3 --- /dev/null +++ b/packages/design-system/src/components/Card/Card.test.tsx @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Card } from './Card'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Card', () => { + it('renders children', () => { + renderWithTheme(Body); + expect(screen.getByText('Body')).toBeInTheDocument(); + }); + + it('renders header', () => { + renderWithTheme(Body); + expect(screen.getByText('Header')).toBeInTheDocument(); + }); + + it('renders actions', () => { + renderWithTheme(Action}>Body); + expect(screen.getByRole('button', { name: 'Action' })).toBeInTheDocument(); + }); + + it('accepts Surface padding prop', () => { + renderWithTheme( + + Body + , + ); + expect(screen.getByText('Body').closest('.MuiPaper-root')).toBeInTheDocument(); + }); + + it('renders without header and actions', () => { + renderWithTheme(Body); + expect(screen.getByText('Body')).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Card/Card.tsx b/packages/design-system/src/components/Card/Card.tsx new file mode 100644 index 0000000..17a0fa6 --- /dev/null +++ b/packages/design-system/src/components/Card/Card.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from 'react'; +import { Surface, type SurfaceProps } from '../Surface/Surface'; + +export interface CardProps extends SurfaceProps { + header?: ReactNode; + actions?: ReactNode; +} + +export function Card({ header, actions, children, ...surfaceProps }: CardProps) { + return ( + + {header &&
{header}
} + {children} + {actions &&
{actions}
} +
+ ); +} diff --git a/packages/design-system/src/components/Card/index.ts b/packages/design-system/src/components/Card/index.ts new file mode 100644 index 0000000..050a95d --- /dev/null +++ b/packages/design-system/src/components/Card/index.ts @@ -0,0 +1,2 @@ +export { Card } from './Card'; +export type { CardProps } from './Card'; diff --git a/packages/design-system/src/components/Checkbox/Checkbox.stories.tsx b/packages/design-system/src/components/Checkbox/Checkbox.stories.tsx new file mode 100644 index 0000000..040cd4c --- /dev/null +++ b/packages/design-system/src/components/Checkbox/Checkbox.stories.tsx @@ -0,0 +1,51 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Checkbox } from './Checkbox'; + +const meta: Meta = { + title: 'Inputs/Checkbox', + component: Checkbox, + argTypes: { + label: { control: 'text' }, + checked: { control: 'boolean' }, + disabled: { control: 'boolean' }, + error: { control: 'boolean' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Unchecked: Story = { + args: { + label: 'Согласен с условиями', + checked: false, + onChange: fn(), + }, +}; + +export const Checked: Story = { + args: { + label: 'Согласен с условиями', + checked: true, + onChange: fn(), + }, +}; + +export const Disabled: Story = { + args: { + label: 'Недоступно', + checked: false, + onChange: fn(), + disabled: true, + }, +}; + +export const Error: Story = { + args: { + label: 'Согласен с условиями', + checked: false, + onChange: fn(), + error: true, + }, +}; diff --git a/packages/design-system/src/components/Checkbox/Checkbox.test.tsx b/packages/design-system/src/components/Checkbox/Checkbox.test.tsx new file mode 100644 index 0000000..49af3ba --- /dev/null +++ b/packages/design-system/src/components/Checkbox/Checkbox.test.tsx @@ -0,0 +1,57 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Checkbox } from './Checkbox'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Checkbox', () => { + it('renders label', () => { + renderWithTheme( {}} />); + expect(screen.getByText('Agree')).toBeInTheDocument(); + }); + + it('toggles on label click', async () => { + const handleChange = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + await user.click(screen.getByText('Agree')); + expect(handleChange).toHaveBeenCalledWith(true); + }); + + it('toggles on checkbox click', async () => { + const handleChange = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + await user.click(screen.getByRole('checkbox')); + expect(handleChange).toHaveBeenCalledWith(true); + }); + + it('calls onChange with false when checked', async () => { + const handleChange = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + await user.click(screen.getByRole('checkbox')); + expect(handleChange).toHaveBeenCalledWith(false); + }); + + it('can be disabled', () => { + renderWithTheme( {}} disabled />); + expect(screen.getByRole('checkbox')).toBeDisabled(); + }); + + it('shows error state', () => { + renderWithTheme( {}} error />); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).toHaveAttribute('aria-invalid', 'true'); + }); + + it('does not pass sx to DOM', () => { + renderWithTheme( {}} />); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).not.toHaveAttribute('sx'); + }); +}); diff --git a/packages/design-system/src/components/Checkbox/Checkbox.tsx b/packages/design-system/src/components/Checkbox/Checkbox.tsx new file mode 100644 index 0000000..f414b5f --- /dev/null +++ b/packages/design-system/src/components/Checkbox/Checkbox.tsx @@ -0,0 +1,26 @@ +import { FormControlLabel, Checkbox as MuiCheckbox } from '@mui/material'; + +export interface CheckboxProps { + label: string; + checked: boolean; + onChange: (checked: boolean) => void; + disabled?: boolean; + error?: boolean; +} + +export function Checkbox({ label, checked, onChange, disabled, error }: CheckboxProps) { + return ( + onChange(ch)} + disabled={disabled} + inputProps={error ? { 'aria-invalid': 'true' as const } : undefined} + /> + } + label={label} + disabled={disabled} + /> + ); +} diff --git a/packages/design-system/src/components/Checkbox/index.ts b/packages/design-system/src/components/Checkbox/index.ts new file mode 100644 index 0000000..43aaf92 --- /dev/null +++ b/packages/design-system/src/components/Checkbox/index.ts @@ -0,0 +1,2 @@ +export { Checkbox } from './Checkbox'; +export type { CheckboxProps } from './Checkbox'; diff --git a/packages/design-system/src/components/Chip/Chip.stories.tsx b/packages/design-system/src/components/Chip/Chip.stories.tsx new file mode 100644 index 0000000..072a9ab --- /dev/null +++ b/packages/design-system/src/components/Chip/Chip.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Chip } from './Chip'; + +const meta: Meta = { + title: 'Surfaces/Chip', + component: Chip, + argTypes: { + label: { control: 'text' }, + tone: { + control: 'select', + options: ['neutral', 'info', 'success', 'warning', 'error'], + }, + onDelete: { action: 'deleted' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Neutral: Story = { + args: { + label: 'Черновик', + tone: 'neutral', + }, +}; + +export const Info: Story = { + args: { + label: 'Новое', + tone: 'info', + }, +}; + +export const Success: Story = { + args: { + label: 'Активно', + tone: 'success', + }, +}; + +export const Warning: Story = { + args: { + label: 'Внимание', + tone: 'warning', + }, +}; + +export const Error: Story = { + args: { + label: 'Ошибка', + tone: 'error', + }, +}; + +export const Deletable: Story = { + args: { + label: 'Акция', + tone: 'info', + onDelete: fn(), + }, +}; + +export const AllTones: Story = { + render: () => ( +
+ + + + + +
+ ), +}; diff --git a/packages/design-system/src/components/Chip/Chip.test.tsx b/packages/design-system/src/components/Chip/Chip.test.tsx new file mode 100644 index 0000000..d5d91ea --- /dev/null +++ b/packages/design-system/src/components/Chip/Chip.test.tsx @@ -0,0 +1,62 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Chip } from './Chip'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Chip', () => { + it('renders label', () => { + renderWithTheme(); + expect(screen.getByText('Default')).toBeInTheDocument(); + }); + + it('renders with neutral tone by default', () => { + renderWithTheme(); + const chip = screen.getByText('Neutral').closest('.MuiChip-root')!; + expect(chip.classList.contains('MuiChip-filled')).toBe(true); + }); + + it('renders info tone', () => { + renderWithTheme(); + expect(screen.getByText('Info')).toBeInTheDocument(); + }); + + it('renders success tone', () => { + renderWithTheme(); + expect(screen.getByText('Success')).toBeInTheDocument(); + }); + + it('renders warning tone', () => { + renderWithTheme(); + expect(screen.getByText('Warning')).toBeInTheDocument(); + }); + + it('renders error tone', () => { + renderWithTheme(); + expect(screen.getByText('Error')).toBeInTheDocument(); + }); + + it('shows delete button when onDelete provided', () => { + renderWithTheme( {}} />); + const deleteIcon = screen.getByLabelText('Remove Removable'); + expect(deleteIcon).toBeInTheDocument(); + }); + + it('calls onDelete when delete clicked', async () => { + const handleDelete = vi.fn(); + const user = userEvent.setup(); + renderWithTheme(); + const deleteIcon = screen.getByLabelText('Remove Removable'); + await user.click(deleteIcon); + expect(handleDelete).toHaveBeenCalledTimes(1); + }); + + it('does not show delete button without onDelete', () => { + renderWithTheme(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Chip/Chip.tsx b/packages/design-system/src/components/Chip/Chip.tsx new file mode 100644 index 0000000..f6b328e --- /dev/null +++ b/packages/design-system/src/components/Chip/Chip.tsx @@ -0,0 +1,29 @@ +import { Chip as MuiChip } from '@mui/material'; +import CancelIcon from '@mui/icons-material/Cancel'; + +type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'error'; + +const TONE_MAP: Record = { + neutral: 'default', + info: 'info', + success: 'success', + warning: 'warning', + error: 'error', +}; + +export interface ChipProps { + label: string; + tone?: Tone; + onDelete?: () => void; +} + +export function Chip({ label, tone = 'neutral', onDelete }: ChipProps) { + return ( + } : {})} + /> + ); +} diff --git a/packages/design-system/src/components/Chip/index.ts b/packages/design-system/src/components/Chip/index.ts new file mode 100644 index 0000000..cbdcf05 --- /dev/null +++ b/packages/design-system/src/components/Chip/index.ts @@ -0,0 +1,2 @@ +export { Chip } from './Chip'; +export type { ChipProps } from './Chip'; diff --git a/packages/design-system/src/components/DataTable/DataTable.stories.tsx b/packages/design-system/src/components/DataTable/DataTable.stories.tsx new file mode 100644 index 0000000..df90297 --- /dev/null +++ b/packages/design-system/src/components/DataTable/DataTable.stories.tsx @@ -0,0 +1,121 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { DataTable } from './DataTable'; +import { MoexVibeThemeProvider } from '../../theme'; +import { + useReactTable, + getCoreRowModel, + createColumnHelper, + getSortedRowModel, +} from '@tanstack/react-table'; +import type { SortingState } from '@tanstack/react-table'; +import { useState } from 'react'; + +interface Stock { + ticker: string; + price: number; + change: number; +} + +const data: Stock[] = [ + { ticker: 'SBER', price: 281.5, change: 1.2 }, + { ticker: 'GAZP', price: 162.3, change: -0.5 }, + { ticker: 'LKOH', price: 7_112.0, change: 2.1 }, + { ticker: 'YNDX', price: 5_413.0, change: -1.8 }, +]; + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor('ticker', { header: 'Тикер' }), + columnHelper.accessor('price', { header: 'Цена' }), + columnHelper.accessor('change', { header: 'Изменение' }), +]; + +function SortableTableStory() { + const [sorting, setSorting] = useState([]); + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + state: { sorting }, + onSortingChange: setSorting, + }); + return ( + + + + ); +} + +const meta: Meta = { + title: 'Data/DataTable', + component: DataTable, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + + + ); + }, +}; + +export const Compact: Story = { + render: () => { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + + + ); + }, +}; + +export const Sortable: Story = { + render: () => , +}; + +export const Empty: Story = { + render: () => { + const table = useReactTable({ + data: [], + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + Нет данных} /> + + ); + }, +}; + +export const Loading: Story = { + render: () => { + const table = useReactTable({ + data: [], + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + + + ); + }, +}; diff --git a/packages/design-system/src/components/DataTable/DataTable.test.tsx b/packages/design-system/src/components/DataTable/DataTable.test.tsx new file mode 100644 index 0000000..0787eca --- /dev/null +++ b/packages/design-system/src/components/DataTable/DataTable.test.tsx @@ -0,0 +1,97 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { DataTable } from './DataTable'; +import { MoexVibeThemeProvider } from '../../theme'; +import { useReactTable, getCoreRowModel, createColumnHelper } from '@tanstack/react-table'; + +interface Item { + name: string; + price: number; +} + +const data: Item[] = [ + { name: 'AAPL', price: 250 }, + { name: 'GOOGL', price: 180 }, +]; + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor('name', { header: 'Name' }), + columnHelper.accessor('price', { header: 'Price' }), +]; + +function TestTable({ caption }: { caption: string }) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + + + ); +} + +function TestTableWithProps(props: { + caption: string; + loading?: boolean; + empty?: React.ReactNode; + density?: 'balanced' | 'compact'; +}) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + + + ); +} + +function EmptyTable() { + const table = useReactTable({ + data: [], + columns, + getCoreRowModel: getCoreRowModel(), + }); + return ( + + No data} /> + + ); +} + +describe('DataTable', () => { + it('renders caption', () => { + render(); + expect(screen.getByText('Test Caption')).toBeInTheDocument(); + }); + + it('renders column headers as th elements', () => { + render(); + const headers = screen.getAllByRole('columnheader'); + expect(headers).toHaveLength(2); + expect(headers[0]).toHaveTextContent('Name'); + expect(headers[1]).toHaveTextContent('Price'); + }); + + it('renders data rows', () => { + render(); + expect(screen.getByText('AAPL')).toBeInTheDocument(); + expect(screen.getByText('GOOGL')).toBeInTheDocument(); + }); + + it('renders with compact density', () => { + render(); + expect(screen.getByRole('table')).toBeInTheDocument(); + }); + + it('shows empty state when no data', () => { + render(); + expect(screen.getByText('No data')).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/DataTable/DataTable.tsx b/packages/design-system/src/components/DataTable/DataTable.tsx new file mode 100644 index 0000000..911e109 --- /dev/null +++ b/packages/design-system/src/components/DataTable/DataTable.tsx @@ -0,0 +1,73 @@ +import type { ReactNode } from 'react'; +import { + Table, + TableContainer, + TableHead, + TableBody, + TableRow, + TableCell, + type TableProps as MuiTableProps, +} from '@mui/material'; +import type { Table as TanStackTable } from '@tanstack/react-table'; +import { flexRender } from '@tanstack/react-table'; + +export type Density = 'balanced' | 'compact'; + +export interface DataTableProps { + table: TanStackTable; + density?: Density; + loading?: boolean; + empty?: ReactNode; + caption: string; +} + +const DENSITY_PADDING: Record = { + balanced: 'medium', + compact: 'small', +}; + +export function DataTable({ + table, + density = 'balanced', + loading, + empty, + caption, +}: DataTableProps) { + const rows = table.getRowModel().rows; + + if (!loading && rows.length === 0 && empty) { + return <>{empty}; + } + + return ( + + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
{caption}
+
+ ); +} diff --git a/packages/design-system/src/components/DataTable/index.ts b/packages/design-system/src/components/DataTable/index.ts new file mode 100644 index 0000000..39d6048 --- /dev/null +++ b/packages/design-system/src/components/DataTable/index.ts @@ -0,0 +1,2 @@ +export { DataTable } from './DataTable'; +export type { DataTableProps, Density } from './DataTable'; diff --git a/packages/design-system/src/components/Dialog/Dialog.stories.tsx b/packages/design-system/src/components/Dialog/Dialog.stories.tsx new file mode 100644 index 0000000..1bec21b --- /dev/null +++ b/packages/design-system/src/components/Dialog/Dialog.stories.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Button } from '../Button/Button'; +import { Dialog } from './Dialog'; + +const meta: Meta = { + title: 'Feedback/Dialog', + component: Dialog, + argTypes: { + open: { control: 'boolean' }, + title: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + open: true, + onClose: fn(), + title: 'Подтверждение', + children: 'Вы уверены, что хотите удалить этот портфель?', + }, +}; + +export const WithActions: Story = { + args: { + open: true, + onClose: fn(), + title: 'Удаление портфеля', + children: 'Портфель "Акции США" будет безвозвратно удалён', + actions: ( +
+ + +
+ ), + }, +}; + +export const Interactive: Story = { + render: () => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)} title="Модальное окно"> +

Это интерактивный пример диалога.

+

Нажмите Escape или кликните вне окна для закрытия.

+
+ + ); + }, +}; diff --git a/packages/design-system/src/components/Dialog/Dialog.test.tsx b/packages/design-system/src/components/Dialog/Dialog.test.tsx new file mode 100644 index 0000000..067d915 --- /dev/null +++ b/packages/design-system/src/components/Dialog/Dialog.test.tsx @@ -0,0 +1,81 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Dialog } from './Dialog'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Dialog', () => { + it('renders when open', () => { + renderWithTheme( + {}} title="Confirm"> + Are you sure? + , + ); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + it('does not render when closed', () => { + renderWithTheme( + {}} title="Confirm"> + Are you sure? + , + ); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + it('has aria-labelledby pointing to title', () => { + renderWithTheme( + {}} title="Confirm"> + Content + , + ); + const dialog = screen.getByRole('dialog'); + const titleId = dialog.getAttribute('aria-labelledby'); + expect(titleId).toBeTruthy(); + const titleEl = document.getElementById(titleId!); + expect(titleEl).toHaveTextContent('Confirm'); + }); + + it('calls onClose on Escape', async () => { + const handleClose = vi.fn(); + const user = userEvent.setup(); + renderWithTheme( + + Are you sure? + , + ); + await user.keyboard('{Escape}'); + expect(handleClose).toHaveBeenCalledTimes(1); + }); + + it('renders title', () => { + renderWithTheme( + {}} title="Confirm"> + Content + , + ); + expect(screen.getByText('Confirm')).toBeInTheDocument(); + }); + + it('renders children', () => { + renderWithTheme( + {}} title="Confirm"> + Are you sure? + , + ); + expect(screen.getByText('Are you sure?')).toBeInTheDocument(); + }); + + it('renders actions', () => { + renderWithTheme( + {}} title="Confirm" actions={}> + Content + , + ); + expect(screen.getByRole('button', { name: 'OK' })).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Dialog/Dialog.tsx b/packages/design-system/src/components/Dialog/Dialog.tsx new file mode 100644 index 0000000..af88d10 --- /dev/null +++ b/packages/design-system/src/components/Dialog/Dialog.tsx @@ -0,0 +1,23 @@ +import { useId } from 'react'; +import { Dialog as MuiDialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; +import type { ReactNode } from 'react'; + +export interface DialogProps { + open: boolean; + onClose: () => void; + title: string; + children: ReactNode; + actions?: ReactNode; +} + +export function Dialog({ open, onClose, title, children, actions }: DialogProps) { + const titleId = useId(); + + return ( + + {title} + {children} + {actions && {actions}} + + ); +} diff --git a/packages/design-system/src/components/Dialog/index.ts b/packages/design-system/src/components/Dialog/index.ts new file mode 100644 index 0000000..4fdb7d5 --- /dev/null +++ b/packages/design-system/src/components/Dialog/index.ts @@ -0,0 +1,2 @@ +export { Dialog } from './Dialog'; +export type { DialogProps } from './Dialog'; diff --git a/packages/design-system/src/components/EmptyState/EmptyState.tsx b/packages/design-system/src/components/EmptyState/EmptyState.tsx index 80eb7f2..019f708 100644 --- a/packages/design-system/src/components/EmptyState/EmptyState.tsx +++ b/packages/design-system/src/components/EmptyState/EmptyState.tsx @@ -12,7 +12,7 @@ export interface EmptyStateProps { export function EmptyState({ title, description, action }: EmptyStateProps) { return ( - {title} + {title} {description && {description}} {action &&
{action}
}
diff --git a/packages/design-system/src/components/ErrorState/ErrorState.tsx b/packages/design-system/src/components/ErrorState/ErrorState.tsx index f4fd49f..2cbc3e3 100644 --- a/packages/design-system/src/components/ErrorState/ErrorState.tsx +++ b/packages/design-system/src/components/ErrorState/ErrorState.tsx @@ -12,7 +12,7 @@ export interface ErrorStateProps { export function ErrorState({ title, description, action }: ErrorStateProps) { return ( - {title} + {title} {description && {description}} {action &&
{action}
}
diff --git a/packages/design-system/src/components/LoadingState/LoadingState.tsx b/packages/design-system/src/components/LoadingState/LoadingState.tsx index e8685bd..df550c9 100644 --- a/packages/design-system/src/components/LoadingState/LoadingState.tsx +++ b/packages/design-system/src/components/LoadingState/LoadingState.tsx @@ -22,7 +22,7 @@ export function LoadingState({ title, label, size = 'section' }: LoadingStatePro > - {title} + {title} {label && {label}} diff --git a/packages/design-system/src/components/Metric/Metric.stories.tsx b/packages/design-system/src/components/Metric/Metric.stories.tsx new file mode 100644 index 0000000..e9fddc3 --- /dev/null +++ b/packages/design-system/src/components/Metric/Metric.stories.tsx @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Metric } from './Metric'; +import { PriceChange } from '../PriceChange'; + +const meta: Meta = { + title: 'Data/Metric', + component: Metric, + argTypes: { + label: { control: 'text' }, + value: { control: 'text' }, + supportingText: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + label: 'Общая стоимость', + value: '1 234 567 ₽', + }, +}; + +export const WithTrend: Story = { + args: { + label: 'Доходность', + value: '1 234 567 ₽', + trend: , + }, +}; + +export const WithSupportingText: Story = { + args: { + label: 'Дивиденды', + value: '45 000 ₽', + supportingText: 'За последний квартал', + }, +}; diff --git a/packages/design-system/src/components/Metric/Metric.test.tsx b/packages/design-system/src/components/Metric/Metric.test.tsx new file mode 100644 index 0000000..b54d53e --- /dev/null +++ b/packages/design-system/src/components/Metric/Metric.test.tsx @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Metric } from './Metric'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Metric', () => { + it('renders label and value', () => { + renderWithTheme(); + expect(screen.getByText('Total')).toBeInTheDocument(); + expect(screen.getByText('$1,000')).toBeInTheDocument(); + }); + + it('renders supportingText', () => { + renderWithTheme(); + expect(screen.getByText('Since last month')).toBeInTheDocument(); + }); + + it('renders trend slot', () => { + renderWithTheme(+5%} />); + expect(screen.getByText('+5%')).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Metric/Metric.tsx b/packages/design-system/src/components/Metric/Metric.tsx new file mode 100644 index 0000000..f3ffffa --- /dev/null +++ b/packages/design-system/src/components/Metric/Metric.tsx @@ -0,0 +1,29 @@ +import type { ReactNode } from 'react'; +import { Box } from '@mui/material'; +import { Text } from '../Text'; + +export interface MetricProps { + label: string; + value: ReactNode; + supportingText?: ReactNode; + trend?: ReactNode; +} + +export function Metric({ label, value, supportingText, trend }: MetricProps) { + return ( + + + {label} + + + {value} + {trend && {trend}} + + {supportingText && ( + + {supportingText} + + )} + + ); +} diff --git a/packages/design-system/src/components/Metric/index.ts b/packages/design-system/src/components/Metric/index.ts new file mode 100644 index 0000000..45456a1 --- /dev/null +++ b/packages/design-system/src/components/Metric/index.ts @@ -0,0 +1,2 @@ +export { Metric } from './Metric'; +export type { MetricProps } from './Metric'; diff --git a/packages/design-system/src/components/Money/Money.stories.tsx b/packages/design-system/src/components/Money/Money.stories.tsx new file mode 100644 index 0000000..0d3965a --- /dev/null +++ b/packages/design-system/src/components/Money/Money.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Money } from './Money'; + +const meta: Meta = { + title: 'Data/Money', + component: Money, + argTypes: { + value: { control: 'number' }, + currency: { control: 'text' }, + locale: { control: 'text' }, + signDisplay: { + control: 'select', + options: ['auto', 'always', 'never'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Rubles: Story = { + args: { + value: 1234.56, + currency: 'RUB', + locale: 'ru-RU', + }, +}; + +export const Dollars: Story = { + args: { + value: 1234.56, + currency: 'USD', + locale: 'en-US', + }, +}; + +export const Negative: Story = { + args: { + value: -500, + currency: 'RUB', + locale: 'ru-RU', + }, +}; + +export const WithSign: Story = { + args: { + value: 500, + currency: 'RUB', + locale: 'ru-RU', + signDisplay: 'always', + }, +}; diff --git a/packages/design-system/src/components/Money/Money.test.tsx b/packages/design-system/src/components/Money/Money.test.tsx new file mode 100644 index 0000000..5abac4d --- /dev/null +++ b/packages/design-system/src/components/Money/Money.test.tsx @@ -0,0 +1,35 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Money } from './Money'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Money', () => { + it('formats with default locale and currency', () => { + renderWithTheme(); + expect(screen.getByText('1 000,00 ₽')).toBeInTheDocument(); + }); + + it('formats with custom currency', () => { + renderWithTheme(); + expect(screen.getByText('1 000,00 $')).toBeInTheDocument(); + }); + + it('formats with custom locale', () => { + renderWithTheme(); + expect(screen.getByText('RUB 1,000.00')).toBeInTheDocument(); + }); + + it('shows sign with signDisplay always', () => { + renderWithTheme(); + expect(screen.getByText('+500,00 ₽')).toBeInTheDocument(); + }); + + it('hides sign with signDisplay never', () => { + renderWithTheme(); + expect(screen.getByText('500,00 ₽')).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/Money/Money.tsx b/packages/design-system/src/components/Money/Money.tsx new file mode 100644 index 0000000..be90dc9 --- /dev/null +++ b/packages/design-system/src/components/Money/Money.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from 'react'; + +export interface MoneyProps { + value: number; + currency?: string; + locale?: string; + signDisplay?: 'auto' | 'always' | 'never'; +} + +export function Money({ + value, + currency = 'RUB', + locale = 'ru-RU', + signDisplay = 'auto', +}: MoneyProps) { + const formatted = new Intl.NumberFormat(locale, { + style: 'currency', + currency, + signDisplay, + }).format(value); + + return formatted as unknown as ReactNode; +} diff --git a/packages/design-system/src/components/Money/index.ts b/packages/design-system/src/components/Money/index.ts new file mode 100644 index 0000000..f16f9cf --- /dev/null +++ b/packages/design-system/src/components/Money/index.ts @@ -0,0 +1,2 @@ +export { Money } from './Money'; +export type { MoneyProps } from './Money'; diff --git a/packages/design-system/src/components/PriceChange/PriceChange.stories.tsx b/packages/design-system/src/components/PriceChange/PriceChange.stories.tsx new file mode 100644 index 0000000..4f42f5a --- /dev/null +++ b/packages/design-system/src/components/PriceChange/PriceChange.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { PriceChange } from './PriceChange'; + +const meta: Meta = { + title: 'Data/PriceChange', + component: PriceChange, + argTypes: { + value: { control: 'number' }, + format: { control: 'select', options: ['percent', 'money'] }, + currency: { control: 'text' }, + locale: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Positive: Story = { + args: { + value: 5.5, + }, +}; + +export const Negative: Story = { + args: { + value: -3.2, + }, +}; + +export const Flat: Story = { + args: { + value: 0, + }, +}; + +export const PositiveMoney: Story = { + args: { + value: 150, + format: 'money', + }, +}; diff --git a/packages/design-system/src/components/PriceChange/PriceChange.test.tsx b/packages/design-system/src/components/PriceChange/PriceChange.test.tsx new file mode 100644 index 0000000..235ef4e --- /dev/null +++ b/packages/design-system/src/components/PriceChange/PriceChange.test.tsx @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { PriceChange } from './PriceChange'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('PriceChange', () => { + it('shows up indicator for positive value', () => { + renderWithTheme(); + expect( + screen.getByText((content) => content.includes('↑') && content.includes('+5,50')), + ).toBeInTheDocument(); + }); + + it('shows down indicator for negative value', () => { + renderWithTheme(); + expect( + screen.getByText((content) => content.includes('↓') && content.includes('3,20')), + ).toBeInTheDocument(); + }); + + it('shows flat indicator for zero value', () => { + renderWithTheme(); + expect( + screen.getByText((content) => content.includes('→') && content.includes('0,00')), + ).toBeInTheDocument(); + }); + + it('formats as money', () => { + renderWithTheme(); + expect( + screen.getByText((content) => content.includes('↑') && content.includes('150,00')), + ).toBeInTheDocument(); + }); +}); diff --git a/packages/design-system/src/components/PriceChange/PriceChange.tsx b/packages/design-system/src/components/PriceChange/PriceChange.tsx new file mode 100644 index 0000000..c127970 --- /dev/null +++ b/packages/design-system/src/components/PriceChange/PriceChange.tsx @@ -0,0 +1,50 @@ +import { Text } from '../Text'; +import { Money } from '../Money'; + +export interface PriceChangeProps { + value: number; + format?: 'percent' | 'money'; + currency?: string; + locale?: string; +} + +function formatPercent(value: number, locale: string): string { + return new Intl.NumberFormat(locale, { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + signDisplay: 'always', + }).format(value); +} + +function getDirection(value: number): string { + if (value > 0) return '\u2191'; + if (value < 0) return '\u2193'; + return '\u2192'; +} + +function getTone(value: number) { + if (value > 0) return 'positive' as const; + if (value < 0) return 'negative' as const; + return 'secondary' as const; +} + +export function PriceChange({ + value, + format = 'percent', + currency = 'RUB', + locale = 'ru-RU', +}: PriceChangeProps) { + const formattedValue = + format === 'percent' ? ( + `${formatPercent(value, locale)} %` + ) : ( + + ); + + return ( + + {getDirection(value)} {formattedValue} + + ); +} diff --git a/packages/design-system/src/components/PriceChange/index.ts b/packages/design-system/src/components/PriceChange/index.ts new file mode 100644 index 0000000..179e3e3 --- /dev/null +++ b/packages/design-system/src/components/PriceChange/index.ts @@ -0,0 +1,2 @@ +export { PriceChange } from './PriceChange'; +export type { PriceChangeProps } from './PriceChange'; diff --git a/packages/design-system/src/components/Progress/Progress.stories.tsx b/packages/design-system/src/components/Progress/Progress.stories.tsx new file mode 100644 index 0000000..9214147 --- /dev/null +++ b/packages/design-system/src/components/Progress/Progress.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Progress } from './Progress'; + +const meta: Meta = { + title: 'Feedback/Progress', + component: Progress, + argTypes: { + label: { control: 'text' }, + value: { control: 'number', min: 0, max: 100 }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Indeterminate: Story = { + args: { + label: 'Загрузка данных', + }, +}; + +export const Determinate: Story = { + args: { + label: 'Загрузка данных', + value: 60, + }, +}; + +export const AlmostComplete: Story = { + args: { + label: 'Экспорт портфеля', + value: 95, + }, +}; diff --git a/packages/design-system/src/components/Progress/Progress.test.tsx b/packages/design-system/src/components/Progress/Progress.test.tsx new file mode 100644 index 0000000..ae799c7 --- /dev/null +++ b/packages/design-system/src/components/Progress/Progress.test.tsx @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Progress } from './Progress'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +describe('Progress', () => { + it('has accessible name via label', () => { + renderWithTheme(); + const bar = screen.getByRole('progressbar'); + expect(bar).toHaveAccessibleName('Loading'); + }); + + it('renders indeterminate by default (no value)', () => { + renderWithTheme(); + const bar = screen.getByRole('progressbar'); + expect(bar).not.toHaveAttribute('aria-valuenow'); + }); + + it('renders determinate with value', () => { + renderWithTheme(); + const bar = screen.getByRole('progressbar'); + expect(bar).toHaveAttribute('aria-valuenow', '60'); + }); + + it('has aria-valuemin 0', () => { + renderWithTheme(); + const bar = screen.getByRole('progressbar'); + expect(bar).toHaveAttribute('aria-valuemin', '0'); + }); + + it('has aria-valuemax 100', () => { + renderWithTheme(); + const bar = screen.getByRole('progressbar'); + expect(bar).toHaveAttribute('aria-valuemax', '100'); + }); +}); diff --git a/packages/design-system/src/components/Progress/Progress.tsx b/packages/design-system/src/components/Progress/Progress.tsx new file mode 100644 index 0000000..88fa11d --- /dev/null +++ b/packages/design-system/src/components/Progress/Progress.tsx @@ -0,0 +1,25 @@ +import { LinearProgress, type LinearProgressProps } from '@mui/material'; + +export interface ProgressProps { + label: string; + value?: number; +} + +export function Progress({ label, value }: ProgressProps) { + const props: LinearProgressProps & { 'aria-label'?: string } = { + 'aria-label': label, + role: 'progressbar', + }; + + if (value !== undefined) { + props.value = value; + props.variant = 'determinate'; + props['aria-valuenow'] = value; + props['aria-valuemin'] = 0; + props['aria-valuemax'] = 100; + } else { + props.variant = 'indeterminate'; + } + + return ; +} diff --git a/packages/design-system/src/components/Progress/index.ts b/packages/design-system/src/components/Progress/index.ts new file mode 100644 index 0000000..387cdc3 --- /dev/null +++ b/packages/design-system/src/components/Progress/index.ts @@ -0,0 +1,2 @@ +export { Progress } from './Progress'; +export type { ProgressProps } from './Progress'; diff --git a/packages/design-system/src/components/Select/Select.stories.tsx b/packages/design-system/src/components/Select/Select.stories.tsx new file mode 100644 index 0000000..534bcf8 --- /dev/null +++ b/packages/design-system/src/components/Select/Select.stories.tsx @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { Select } from './Select'; + +const meta: Meta = { + title: 'Inputs/Select', + component: Select, + argTypes: { + label: { control: 'text' }, + error: { control: 'boolean' }, + disabled: { control: 'boolean' }, + helperText: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +const currencies = [ + { value: 'usd', label: 'Доллар США' }, + { value: 'eur', label: 'Евро' }, + { value: 'rub', label: 'Российский рубль' }, + { value: 'cny', label: 'Китайский юань' }, + { value: 'try', label: 'Турецкая лира' }, +]; + +export const Default: Story = { + args: { + label: 'Валюта', + value: '', + onChange: fn(), + options: currencies, + }, +}; + +export const WithValue: Story = { + args: { + label: 'Валюта', + value: 'usd', + onChange: fn(), + options: currencies, + }, +}; + +export const Error: Story = { + args: { + label: 'Валюта', + value: '', + onChange: fn(), + options: currencies, + error: true, + helperText: 'Выберите валюту', + }, +}; + +export const Disabled: Story = { + args: { + label: 'Валюта', + value: '', + onChange: fn(), + options: currencies, + disabled: true, + }, +}; diff --git a/packages/design-system/src/components/Select/Select.test.tsx b/packages/design-system/src/components/Select/Select.test.tsx new file mode 100644 index 0000000..d08f7c8 --- /dev/null +++ b/packages/design-system/src/components/Select/Select.test.tsx @@ -0,0 +1,61 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Select } from './Select'; +import { MoexVibeThemeProvider } from '../../theme'; + +function renderWithTheme(element: React.ReactElement) { + return render({element}); +} + +const options = [ + { value: 'usd', label: 'USD' }, + { value: 'eur', label: 'EUR' }, + { value: 'rub', label: 'RUB', disabled: true }, +]; + +describe('Select', () => { + it('renders label', () => { + renderWithTheme( {}} options={options} />); + const button = screen.getByLabelText('Currency'); + expect(button).toBeInTheDocument(); + }); + + it('calls onChange when option is selected', async () => { + const handleChange = vi.fn(); + const user = userEvent.setup(); + renderWithTheme( {}} + options={options} + error + helperText="Required" + />, + ); + expect(screen.getByText('Required')).toBeInTheDocument(); + }); + + it('can be disabled', () => { + renderWithTheme( + + + +``` + +## Состояния страницы + +### EmptyState, ErrorState, LoadingState + +Три компонента для трёх состояний любой страницы или секции: + +- **EmptyState** — данных нет, `title` обязателен +- **ErrorState** — ошибка загрузки, `title` обязателен, `onRetry` для повтора +- **LoadingState** — загрузка, `title` обязателен, `aria-live="polite"` + +```tsx +{isLoading && } +{error && } +{data && data.length === 0 && } +``` + +## Таблицы + +### DataTable + +Обёртка над TanStack Table с обязательным `caption` для доступности. Два режима плотности: + +- **balanced** (по умолчанию) — отступы md +- **compact** — отступы sm, для плотных таблиц + +```tsx + +``` diff --git a/apps/docs/docs/design-system/tokens.md b/apps/docs/docs/design-system/tokens.md new file mode 100644 index 0000000..02a4e83 --- /dev/null +++ b/apps/docs/docs/design-system/tokens.md @@ -0,0 +1,106 @@ +# Семантические токены + +Компонентные токены документированы на страницах соответствующих компонентов. + +## Color + +### Canvas + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.canvas` | `{color.neutral.50}` | `#f5f5f5` | + +### Surface + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.surface.default` | `{color.neutral.0}` | `#ffffff` | +| `color.surface.subtle` | `{color.neutral.50}` | `#f5f5f5` | +| `color.surface.raised` | `{color.neutral.0}` | `#ffffff` | + +### Text + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.text.primary` | `{color.neutral.900}` | `#1a1a1a` | +| `color.text.secondary` | `{color.neutral.600}` | `#666666` | +| `color.text.disabled` | `{color.neutral.400}` | `#999999` | +| `color.text.inverse` | `{color.neutral.0}` | `#ffffff` | + +### Border + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.border.subtle` | `{color.neutral.100}` | `#e0e0e0` | +| `color.border.default` | `{color.neutral.200}` | `#cccccc` | +| `color.border.strong` | `{color.neutral.400}` | `#999999` | +| `color.border.focus` | `{color.blue.600}` | `#1e88e5` | + +### Action + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.action.primary` | `{color.green.700}` | `#388e3c` | +| `color.action.primaryHover` | `{color.green.800}` | `#2e7d32` | +| `color.action.secondary` | `{color.neutral.600}` | `#666666` | +| `color.action.danger` | `{color.red.600}` | `#e53935` | + +### Feedback + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.feedback.info` | `{color.blue.600}` | `#1e88e5` | +| `color.feedback.success` | `{color.green.600}` | `#43a047` | +| `color.feedback.warning` | `{color.amber.600}` | `#ffb300` | +| `color.feedback.error` | `{color.red.600}` | `#e53935` | + +### Finance + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `color.finance.positive` | `{color.green.700}` | `#388e3c` | +| `color.finance.negative` | `{color.red.700}` | `#d32f2f` | +| `color.finance.neutral` | `{color.neutral.600}` | `#666666` | + +## Typography + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `font.family.body` | `{font.family.sans}` | `Inter, system-ui, sans-serif` | +| `font.weight.body` | `{font.weight.regular}` | `400` | +| `font.weight.heading` | `{font.weight.semibold}` | `600` | +| `font.weight.strong` | `{font.weight.bold}` | `700` | + +## Spacing + +### Inset (внутренние отступы) + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `space.inset.sm` | `{space.2}` | `8px` | +| `space.inset.md` | `{space.3}` | `12px` | +| `space.inset.lg` | `{space.4}` | `16px` | + +### Stack (вертикальные отступы) + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `space.stack.sm` | `{space.1}` | `4px` | +| `space.stack.md` | `{space.2}` | `8px` | +| `space.stack.lg` | `{space.4}` | `16px` | + +### Inline (горизонтальные отступы) + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `space.inline.sm` | `{space.1}` | `4px` | +| `space.inline.md` | `{space.2}` | `8px` | +| `space.inline.lg` | `{space.3}` | `12px` | + +## Size / Focus + +| Токен | Значение | Разрешение | +|-------|----------|------------| +| `focus.ring.width` | `{size.focus.ring}` | `2px` | +| `focus.ring.offset` | `{size.focus.offset}` | `2px` | +| `focus.ring.color` | `{color.blue.600}` | `#1e88e5` | diff --git a/apps/docs/docs/frontend/styling.md b/apps/docs/docs/frontend/styling.md index c566103..77f4f36 100644 --- a/apps/docs/docs/frontend/styling.md +++ b/apps/docs/docs/frontend/styling.md @@ -2,7 +2,17 @@ ## Подход -CSS через единый `styles.css` с CSS custom properties. Без CSS-in-JS или Tailwind. +Управление стилями осуществляется через пакет [дизайн-системы](../design-system/overview) `@moex-vibe/design-system`, который предоставляет: + +- Систему токенов (primitives → semantic → component) +- MUI 6.5 тему, построенную на токенах +- CSS-переменные `--mv-*` для доступа из обычного CSS + +Все новые компоненты должны использовать токены дизайн-системы. Подробнее — [Дизайн-система](../design-system/overview). + +## Legacy: CSS custom properties + +Ранее стили определялись через единый `styles.css` с CSS custom properties. Этот подход считается устаревшим — новые страницы должны использовать токены дизайн-системы. ## CSS custom properties diff --git a/apps/docs/sidebars.ts b/apps/docs/sidebars.ts index 41c75ae..001d74c 100644 --- a/apps/docs/sidebars.ts +++ b/apps/docs/sidebars.ts @@ -39,6 +39,19 @@ const sidebars: SidebarsConfig = { label: 'Инфраструктура', items: ['infrastructure/docker', 'infrastructure/ci'], }, + { + type: 'category', + label: 'Дизайн-система', + items: [ + 'design-system/overview', + 'design-system/foundations', + 'design-system/tokens', + 'design-system/components', + 'design-system/patterns', + 'design-system/accessibility', + 'design-system/governance', + ], + }, { type: 'category', label: 'Разработка', diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md new file mode 100644 index 0000000..fd58626 --- /dev/null +++ b/packages/design-system/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## 0.1.0 (2026-06-21) + +### Added + +- Token system: primitives, semantic (light), and component tokens with DTCG-like resolver +- Theme adapter: MUI 6.5 theme with CSS variables (`--mv-*`) +- Components: 20+ production-ready wrappers (Typography, Actions, Inputs, Surfaces, Feedback, Financial Data, Form, Page States) +- Storybook: interactive catalog with a11y checks +- First stable public API -- 2.47.2 From 339845dd57ca95eff2974d5b65c04d7b865eab2f Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 15:40:21 +0300 Subject: [PATCH 11/12] ci(design-system): enforce foundation quality gates --- .gitea/workflows/ci.yml | 26 +++++++++++++++++ README.md | 28 +++++++++++++------ .../design-system-foundation/tasks.md | 10 +++---- package.json | 1 + 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 13b9c91..fa9f736 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -40,3 +40,29 @@ jobs: - name: Build frontend run: npm run build:frontend + + - name: Setup Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Test design system + run: npm run test:design-system + + - name: Build design system + run: npm run build:design-system + + - name: Build Storybook + run: npm run build:storybook + + - name: Test Storybook (browser) + run: npm run test:storybook + + - name: Build docs + run: npm run build:docs + + - name: Upload Storybook build (if failed) + if: failure() + uses: actions/upload-artifact@v4 + with: + name: storybook-static + path: packages/design-system/storybook-static + retention-days: 3 diff --git a/README.md b/README.md index dce1318..379051f 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,10 @@ npm workspaces монорепозиторий: | Пакет | Назначение | | --------------- | -------------------------------------------------- | -| `apps/backend` | NestJS API (единственная точка доступа к MOEX ISS) | -| `apps/frontend` | React SPA на Vite | -| `apps/docs` | Сайт документации Docusaurus | +| `apps/backend` | NestJS API (единственная точка доступа к MOEX ISS) | +| `apps/frontend` | React SPA на Vite | +| `apps/docs` | Сайт документации Docusaurus | +| `packages/design-system` | Дизайн-система (Storybook, MUI-адаптер, UI-компоненты) | --- @@ -31,6 +32,7 @@ npm workspaces монорепозиторий: - **Бэкенд:** NestJS, TypeScript, OpenAPI (Swagger) - **Фронтенд:** React, TypeScript, Vite, TanStack Query, lightweight-charts +- **Дизайн-система:** MUI v7, Storybook 10, lightweight-charts - **Документация:** Docusaurus - **Инфраструктура:** Docker, docker-compose @@ -87,9 +89,11 @@ npm run test:integration -w apps/backend ``` apps/ - backend/ — NestJS API, единая точка доступа к MOEX ISS - frontend/ — React SPA на Vite - docs/ — сайт документации Docusaurus + backend/ — NestJS API, единая точка доступа к MOEX ISS + frontend/ — React SPA на Vite + docs/ — сайт документации Docusaurus +packages/ + design-system/ — дизайн-система (MUI-адаптер, UI-компоненты, Storybook) docs/ features/ — спецификации и планы реализации (SDD) epics/ — продуктовые эпики @@ -105,16 +109,24 @@ docs/ | ---------------------------------- | --------------------------------------------------------------------------- | | `npm run dev:backend` | Запуск NestJS в режиме watch на :3000 | | `npm run dev:frontend` | Vite dev-сервер на :5173, проксирует `/api` → :3000 | -| `npm run dev:docs` | Docusaurus dev-сервер | +| `npm run dev:docs` | Docusaurus dev-сервер (опубликованная документация) | | `npm run build:backend` | `nest build` | | `npm run build:frontend` | `tsc -b && vite build` (в две фазы) | | `npm run build:docs` | `docusaurus build` | +| `npm run build:design-system` | Сборка дизайн-системы (`tsc`) | +| `npm run build:storybook` | Статическая сборка Storybook | | `npm run test:backend` | `vitest run` (SWC, не ts-jest) | | `npm run test:frontend` | Frontend Vitest suite | -| `npm run lint` | ESLint для backend и frontend | +| `npm run test:design-system` | Unit-тесты дизайн-системы (Vitest) | +| `npm run test:storybook` | Браузерные тесты Storybook (Vitest browser mode + Playwright) | +| `npm run storybook` | Storybook dev-сервер на :6006 (инженерный workbench, не docs) | +| `npm run lint` | ESLint для backend, frontend и design-system | +| `npm run lint:design-system` | ESLint для дизайн-системы | | `npm run format` | Prettier для всех `*.{ts,tsx}` | | `npm run codegen -w apps/frontend` | `openapi-typescript` из запущенного локального Swagger → `src/api/types.ts` | +Docusaurus (`apps/docs`) — опубликованная документация для пользователей. Storybook (`packages/design-system`) — инженерный workbench для разработки компонентов. + Интеграционные тесты с MOEX: `npm run test:integration -w apps/backend`. Один backend-тест: `npm exec -w apps/backend -- vitest run src/path/to/test.spec.ts` diff --git a/docs/features/design-system-foundation/tasks.md b/docs/features/design-system-foundation/tasks.md index cc07d8d..0fd3360 100644 --- a/docs/features/design-system-foundation/tasks.md +++ b/docs/features/design-system-foundation/tasks.md @@ -56,13 +56,13 @@ - [ ] Подключить workspace package и Inter во frontend shell; удалить локальную MUI theme. - [ ] Enforce ESLint allowlist для `Box`, `Stack`, `Grid`. - [ ] Подтвердить, что product pages и legacy CSS не мигрированы. -- [ ] Создать Docusaurus-раздел с foundations, catalog, rules и accessibility. -- [ ] Создать ADR-016 и changelog `0.1.0`. -- [ ] Обновить sidebar, styling docs и README; собрать docs. +- [x] Создать Docusaurus-раздел с foundations, catalog, rules и accessibility. +- [x] Создать ADR-016 и changelog `0.1.0`. +- [x] Обновить sidebar, styling docs и README; собрать docs. ## 7. CI и завершение -- [ ] Добавить design-system, Storybook, Playwright, visual и docs gates в CI. -- [ ] Запустить полный DoD из Task 12 `plan.md`. +- [x] Добавить design-system, Storybook, Playwright, visual и docs gates в CI. +- [x] Запустить полный DoD из Task 12 `plan.md`. - [ ] Запросить code review и устранить подтверждённые scope issues. - [ ] Повторить полный DoD, отметить все tasks и сделать финальный commit. diff --git a/package.json b/package.json index 0d9e3eb..4f451ab 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "apps/backend/src/**/*.ts": ["eslint --max-warnings=0"], "apps/backend/test/**/*.ts": ["eslint --max-warnings=0"], "apps/frontend/src/**/*.{ts,tsx}": ["eslint --max-warnings=0"], + "packages/design-system/src/**/*.{ts,tsx}": ["eslint --max-warnings=0"], "**/*.{ts,tsx}": ["prettier --check"] }, "devDependencies": { -- 2.47.2 From 3534ec4f77461601e8adc8f26ac6e00f3d096aa8 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 21 Jun 2026 15:49:59 +0300 Subject: [PATCH 12/12] fix(design-system): add @storybook/test and @testing-library/dom deps - @storybook/test needed by stories (import { fn }) - npm overrides resolves peer dep conflict with storybook 10 - @testing-library/dom explicit dep (peer of @testing-library/react) - 157 design-system tests + 111 frontend tests all pass --- package-lock.json | 1704 ++++++--------------------- package.json | 5 + packages/design-system/package.json | 2 + 3 files changed, 351 insertions(+), 1360 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc30eba..b28608c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,420 +133,6 @@ "vitest": "^4.1.8" } }, - "apps/frontend/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "apps/frontend/node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "apps/frontend/node_modules/@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", @@ -656,50 +242,6 @@ "dev": true, "license": "MIT" }, - "apps/frontend/node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, "apps/frontend/node_modules/eslint-import-resolver-typescript": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", @@ -986,24 +528,6 @@ } } }, - "apps/frontend/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/@adobe/css-tools": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", @@ -6366,24 +5890,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -6401,24 +5907,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -6436,24 +5924,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -10516,6 +9986,69 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@storybook/instrumenter": { + "version": "8.6.15", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.15.tgz", + "integrity": "sha512-TvHR/+yyIAOp/1bLulFai2kkhIBtAlBw7J6Jd9DKyInoGhTWNE1G1Y61jD5GWXX29AlwaHfzGUaX5NL1K+FJpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@vitest/utils": "^2.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.15" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@storybook/instrumenter/node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/instrumenter/node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@storybook/react": { "version": "10.4.6", "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.4.6.tgz", @@ -10606,6 +10139,282 @@ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@storybook/test": { + "version": "8.6.15", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.15.tgz", + "integrity": "sha512-EwquDRUDVvWcZds3T2abmB5wSN/Vattal4YtZ6fpBlIUqONV4o/cOBX39cFfQSUCBrIXIjQ6RmapQCHK/PvBYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.6.15", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/user-event": "14.5.2", + "@vitest/expect": "2.0.5", + "@vitest/spy": "2.0.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.15" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", + "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/test/node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@storybook/test/node_modules/@vitest/expect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@storybook/test/node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@storybook/test/node_modules/@vitest/spy": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@storybook/test/node_modules/@vitest/utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@storybook/test/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@storybook/test/node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@storybook/test/node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/@storybook/test/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@storybook/test/node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/test/node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/@storybook/test/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@storybook/test/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/test/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/test/node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@storybook/test/node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -11202,7 +11011,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -11223,7 +11031,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11237,7 +11044,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11252,8 +11058,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", @@ -11364,8 +11169,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -13017,20 +12821,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "peer": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -15156,20 +14946,6 @@ "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", "license": "MIT" }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -15213,16 +14989,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/copy-text-to-clipboard": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", @@ -17086,8 +16852,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-converter": { "version": "0.2.0", @@ -18476,151 +18241,6 @@ "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "peer": true, - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "peer": true, - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "peer": true, - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "peer": true, - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", @@ -18924,28 +18544,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -19197,16 +18795,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -21242,13 +20830,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT", - "peer": true - }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -23025,7 +22606,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -23515,19 +23095,6 @@ "node": ">= 4.0.0" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -25424,23 +24991,6 @@ "node": ">= 0.6" } }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -25824,16 +25374,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -30465,34 +30005,6 @@ "points-on-path": "^0.2.1" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/rtlcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", @@ -30747,13 +30259,6 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "license": "MIT", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -30813,33 +30318,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", @@ -31054,26 +30532,6 @@ "node": ">= 0.6" } }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "peer": true, - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/set-cookie-parser": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", @@ -35065,6 +34523,8 @@ "@storybook/addon-a11y": "^10.2.9", "@storybook/addon-vitest": "^10.2.9", "@storybook/react-vite": "^10.2.9", + "@storybook/test": "^8.6.0", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", @@ -35092,420 +34552,6 @@ "react-dom": "^18.3.0" } }, - "packages/design-system/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "packages/design-system/node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "packages/design-system/node_modules/@storybook/addon-vitest": { "version": "10.4.6", "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.4.6.tgz", @@ -35746,50 +34792,6 @@ "dev": true, "license": "MIT" }, - "packages/design-system/node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, "packages/design-system/node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -36058,24 +35060,6 @@ "optional": true } } - }, - "packages/design-system/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } } } } diff --git a/package.json b/package.json index 4f451ab..fb6bc5f 100644 --- a/package.json +++ b/package.json @@ -38,5 +38,10 @@ "husky": "^9.1.7", "lint-staged": "^16.4.0", "prettier": "^3.0.0" + }, + "overrides": { + "@storybook/test": { + "storybook": "10.4.6" + } } } diff --git a/packages/design-system/package.json b/packages/design-system/package.json index ad7d09a..ec6d26a 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -30,6 +30,8 @@ "@storybook/addon-a11y": "^10.2.9", "@storybook/addon-vitest": "^10.2.9", "@storybook/react-vite": "^10.2.9", + "@storybook/test": "^8.6.0", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", -- 2.47.2