From 9916ef5aaf2516b601e84bc1fb42f25e0be0320f Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Thu, 9 Apr 2026 00:11:55 +0300 Subject: [PATCH] feat(client): add UI kit, data layer, app shell, and Table component - design token system (Atlantis, Kimberly, Powder Ash palette) - Button, Badge, Input, Select, Card, SidePanel, Breadcrumbs components - generic typed Table component with loading/empty states - API data layer: typed fetch client for environments, keys, sessions, scenarios - Vite dev proxy targeting server on port 13000 - App shell with SidePanel nav and four entity pages (Environments, Keys, Sessions, Scenarios) - Storybook config with dark/light theme toggle and a11y addon --- .gitignore | 4 +- client/.storybook/main.ts | 26 + client/.storybook/preview.tsx | 45 + client/package.json | 15 +- client/src/App.module.css | 57 + client/src/App.tsx | 48 +- client/src/api/client.ts | 109 ++ client/src/api/index.ts | 2 + client/src/api/types.ts | 79 + client/src/declarations.d.ts | 8 + client/src/main.tsx | 1 + client/src/pages/EnvironmentsPage.tsx | 53 + client/src/pages/KeysPage.tsx | 38 + client/src/pages/Page.module.css | 15 + client/src/pages/ScenariosPage.tsx | 67 + client/src/pages/SessionsPage.tsx | 72 + client/src/ui/Badge/Badge.module.css | 27 + client/src/ui/Badge/Badge.stories.tsx | 25 + client/src/ui/Badge/Badge.tsx | 19 + .../src/ui/Breadcrumbs/Breadcrumbs.module.css | 54 + .../ui/Breadcrumbs/Breadcrumbs.stories.tsx | 48 + client/src/ui/Breadcrumbs/Breadcrumbs.tsx | 44 + client/src/ui/Button/Button.module.css | 89 + client/src/ui/Button/Button.stories.tsx | 26 + client/src/ui/Button/Button.tsx | 29 + client/src/ui/Card/Card.module.css | 11 + client/src/ui/Card/Card.stories.tsx | 46 + client/src/ui/Card/Card.tsx | 16 + client/src/ui/Colors.stories.tsx | 101 + client/src/ui/Input/Input.module.css | 60 + client/src/ui/Input/Input.stories.tsx | 40 + client/src/ui/Input/Input.tsx | 37 + client/src/ui/Select/Select.module.css | 79 + client/src/ui/Select/Select.stories.tsx | 75 + client/src/ui/Select/Select.tsx | 63 + client/src/ui/SidePanel/SidePanel.module.css | 88 + client/src/ui/SidePanel/SidePanel.stories.tsx | 83 + client/src/ui/SidePanel/SidePanel.tsx | 68 + client/src/ui/Table/Table.module.css | 56 + client/src/ui/Table/Table.stories.tsx | 71 + client/src/ui/Table/Table.tsx | 80 + client/src/ui/index.ts | 23 + client/src/ui/tokens.css | 134 ++ client/tsconfig.json | 5 +- client/vite.config.ts | 44 +- client/vitest.shims.d.ts | 1 + package-lock.json | 1739 ++++++++++++++++- 47 files changed, 3910 insertions(+), 10 deletions(-) create mode 100644 client/.storybook/main.ts create mode 100644 client/.storybook/preview.tsx create mode 100644 client/src/App.module.css create mode 100644 client/src/api/client.ts create mode 100644 client/src/api/index.ts create mode 100644 client/src/api/types.ts create mode 100644 client/src/declarations.d.ts create mode 100644 client/src/pages/EnvironmentsPage.tsx create mode 100644 client/src/pages/KeysPage.tsx create mode 100644 client/src/pages/Page.module.css create mode 100644 client/src/pages/ScenariosPage.tsx create mode 100644 client/src/pages/SessionsPage.tsx create mode 100644 client/src/ui/Badge/Badge.module.css create mode 100644 client/src/ui/Badge/Badge.stories.tsx create mode 100644 client/src/ui/Badge/Badge.tsx create mode 100644 client/src/ui/Breadcrumbs/Breadcrumbs.module.css create mode 100644 client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx create mode 100644 client/src/ui/Breadcrumbs/Breadcrumbs.tsx create mode 100644 client/src/ui/Button/Button.module.css create mode 100644 client/src/ui/Button/Button.stories.tsx create mode 100644 client/src/ui/Button/Button.tsx create mode 100644 client/src/ui/Card/Card.module.css create mode 100644 client/src/ui/Card/Card.stories.tsx create mode 100644 client/src/ui/Card/Card.tsx create mode 100644 client/src/ui/Colors.stories.tsx create mode 100644 client/src/ui/Input/Input.module.css create mode 100644 client/src/ui/Input/Input.stories.tsx create mode 100644 client/src/ui/Input/Input.tsx create mode 100644 client/src/ui/Select/Select.module.css create mode 100644 client/src/ui/Select/Select.stories.tsx create mode 100644 client/src/ui/Select/Select.tsx create mode 100644 client/src/ui/SidePanel/SidePanel.module.css create mode 100644 client/src/ui/SidePanel/SidePanel.stories.tsx create mode 100644 client/src/ui/SidePanel/SidePanel.tsx create mode 100644 client/src/ui/Table/Table.module.css create mode 100644 client/src/ui/Table/Table.stories.tsx create mode 100644 client/src/ui/Table/Table.tsx create mode 100644 client/src/ui/index.ts create mode 100644 client/src/ui/tokens.css create mode 100644 client/vitest.shims.d.ts diff --git a/.gitignore b/.gitignore index b1d724f..bbb234f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ node_modules/ # build outputs /dist server/dist -client/dist \ No newline at end of file +client/dist +*storybook.log +storybook-static diff --git a/client/.storybook/main.ts b/client/.storybook/main.ts new file mode 100644 index 0000000..82a430d --- /dev/null +++ b/client/.storybook/main.ts @@ -0,0 +1,26 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +import { dirname } from "path" + +import { fileURLToPath } from "url" + +/** +* This function is used to resolve the absolute path of a package. +* It is needed in projects that use Yarn PnP or are set up within a monorepo. +*/ +function getAbsolutePath(value: string) { + return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`))) +} +const config: StorybookConfig = { + "stories": [ + "../src/**/*.mdx", + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" + ], + "addons": [ + getAbsolutePath('@storybook/addon-vitest'), + getAbsolutePath('@storybook/addon-a11y'), + getAbsolutePath('@storybook/addon-docs') + ], + "framework": getAbsolutePath('@storybook/react-vite') +}; +export default config; \ No newline at end of file diff --git a/client/.storybook/preview.tsx b/client/.storybook/preview.tsx new file mode 100644 index 0000000..57d66d6 --- /dev/null +++ b/client/.storybook/preview.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import type { Preview } from '@storybook/react-vite' +import '../src/ui/tokens.css' + +const preview: Preview = { + globalTypes: { + theme: { + name: 'Theme', + description: 'Color theme', + defaultValue: 'light', + toolbar: { + icon: 'circlehollow', + items: [ + { value: 'light', icon: 'sun', title: 'Light' }, + { value: 'dark', icon: 'moon', title: 'Dark' }, + ], + dynamicTitle: true, + }, + }, + }, + decorators: [ + (Story, context) => { + const theme = (context.globals['theme'] as string) ?? 'light' + document.documentElement.setAttribute('data-theme', theme) + return ( +
+ +
+ ) + }, + ], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + a11y: { + test: 'todo', + }, + }, +} + +export default preview \ No newline at end of file diff --git a/client/package.json b/client/package.json index 0d03c0a..d05d64b 100644 --- a/client/package.json +++ b/client/package.json @@ -6,17 +6,28 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "dependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" }, "devDependencies": { + "@storybook/addon-a11y": "^10.3.5", + "@storybook/addon-docs": "^10.3.5", + "@storybook/addon-vitest": "^10.3.5", + "@storybook/react-vite": "^10.3.5", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", + "@vitest/browser-playwright": "^4.1.3", + "@vitest/coverage-v8": "^4.1.3", + "playwright": "^1.59.1", + "storybook": "^10.3.5", "typescript": "~5.8.3", - "vite": "^6.3.1" + "vite": "^6.3.1", + "vitest": "^4.1.3" } } diff --git a/client/src/App.module.css b/client/src/App.module.css new file mode 100644 index 0000000..7387948 --- /dev/null +++ b/client/src/App.module.css @@ -0,0 +1,57 @@ +.shell { + display: flex; + height: 100vh; + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-family); + overflow: hidden; +} + +.brand { + font-weight: 700; + font-size: 1rem; + color: var(--color-primary); + letter-spacing: 0.02em; +} + +.nav { + display: flex; + flex-direction: column; + gap: 2px; + padding: var(--space-2) 0; +} + +.navItem { + display: block; + width: 100%; + padding: var(--space-2) var(--space-3); + background: none; + border: none; + border-radius: var(--radius-md); + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-family); + text-align: left; + cursor: pointer; + transition: background 120ms ease, color 120ms ease; +} + +.navItem:hover { + background: var(--color-bg-subtle); +} + +.navItemActive { + background: var(--color-secondary); + color: var(--color-secondary-fg); + font-weight: 600; +} + +.navItemActive:hover { + background: var(--color-secondary-hover); +} + +.main { + flex: 1; + overflow-y: auto; + padding: var(--space-6); +} diff --git a/client/src/App.tsx b/client/src/App.tsx index 9f8fa7d..892b958 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,3 +1,49 @@ +import styles from './App.module.css'; +import { SidePanel } from './ui'; +import { EnvironmentsPage } from './pages/EnvironmentsPage'; +import { KeysPage } from './pages/KeysPage'; +import { SessionsPage } from './pages/SessionsPage'; +import { ScenariosPage } from './pages/ScenariosPage'; +import { useState } from 'react'; + +type Section = 'environments' | 'keys' | 'sessions' | 'scenarios'; + +const NAV: { id: Section; label: string }[] = [ + { id: 'environments', label: 'Environments' }, + { id: 'keys', label: 'Keys' }, + { id: 'sessions', label: 'Sessions' }, + { id: 'scenarios', label: 'Scenarios' }, +]; + export default function App() { - return

Hello World

+ const [active, setActive] = useState
('scenarios'); + + return ( +
+ QA Bot} + width={220} + > + + + +
+ {active === 'environments' && } + {active === 'keys' && } + {active === 'sessions' && } + {active === 'scenarios' && } +
+
+ ); } + diff --git a/client/src/api/client.ts b/client/src/api/client.ts new file mode 100644 index 0000000..120c67c --- /dev/null +++ b/client/src/api/client.ts @@ -0,0 +1,109 @@ +import type { + PaginatedResponse, + Environment, + KeysResponse, + Session, + Scenario, + ScenarioRun, + ScenarioStep, +} from './types'; + +// In dev, Vite proxies /environments /sessions /scenarios /keys to localhost:3000. +// In production (or when VITE_API_URL is set) we hit the configured origin directly. +const BASE_URL: string = (import.meta.env.VITE_API_URL as string | undefined) ?? ''; + +async function request(path: string, init?: RequestInit): Promise { + const res = await fetch(`${BASE_URL}${path}`, { + headers: { 'Content-Type': 'application/json', ...init?.headers }, + ...init, + }); + if (!res.ok) { + const text = await res.text().catch(() => res.statusText); + throw new Error(`${res.status} ${text}`); + } + if (res.status === 204) return undefined as T; + return res.json() as Promise; +} + +// ── Environments ────────────────────────────────────────────────────────────── + +export const environments = { + list(page = 1, limit = 50): Promise> { + return request(`/environments?page=${page}&limit=${limit}`); + }, + get(id: number): Promise { + return request(`/environments/${id}`); + }, + create(name: string, urls: Environment['urls']): Promise { + return request('/environments', { + method: 'POST', + body: JSON.stringify({ name, urls }), + }); + }, + update(id: number, patch: Partial>): Promise { + return request(`/environments/${id}`, { + method: 'PATCH', + body: JSON.stringify(patch), + }); + }, + remove(id: number): Promise { + return request(`/environments/${id}`, { method: 'DELETE' }); + }, +}; + +// ── Keys ────────────────────────────────────────────────────────────────────── + +export const keys = { + list(): Promise { + return request('/keys'); + }, +}; + +// ── Sessions ────────────────────────────────────────────────────────────────── + +export const sessions = { + list(page = 1, limit = 50): Promise> { + return request(`/sessions?page=${page}&limit=${limit}`); + }, + remove(id: number): Promise { + return request(`/sessions/${id}`, { method: 'DELETE' }); + }, +}; + +// ── Scenarios ───────────────────────────────────────────────────────────────── + +export const scenarios = { + list(page = 1, limit = 50): Promise> { + return request(`/scenarios?page=${page}&limit=${limit}`); + }, + get(id: number): Promise { + return request(`/scenarios/${id}`); + }, + create(name: string): Promise { + return request('/scenarios', { + method: 'POST', + body: JSON.stringify({ name }), + }); + }, + update(id: number, patch: Partial>): Promise { + return request(`/scenarios/${id}`, { + method: 'PATCH', + body: JSON.stringify(patch), + }); + }, + remove(id: number): Promise { + return request(`/scenarios/${id}`, { method: 'DELETE' }); + }, + run(id: number): Promise { + return request(`/scenarios/${id}/run`, { method: 'POST' }); + }, + getRun(scenarioId: number, runId: number): Promise { + return request(`/scenarios/${scenarioId}/run/${runId}`); + }, + waitForRun(scenarioId: number, runId: number): Promise { + return request(`/scenarios/${scenarioId}/run/${runId}/wait`, { method: 'POST' }); + }, + listRuns(scenarioId: number, page = 1, limit = 20): Promise> { + return request(`/scenarios/${scenarioId}/runs?page=${page}&limit=${limit}`); + }, +}; diff --git a/client/src/api/index.ts b/client/src/api/index.ts new file mode 100644 index 0000000..d860d06 --- /dev/null +++ b/client/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './client'; +export * from './types'; diff --git a/client/src/api/types.ts b/client/src/api/types.ts new file mode 100644 index 0000000..758411b --- /dev/null +++ b/client/src/api/types.ts @@ -0,0 +1,79 @@ +// ── Shared ──────────────────────────────────────────────────────────────────── + +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + limit: number; +} + +// ── Environments ────────────────────────────────────────────────────────────── + +export interface EnvironmentUrls { + id_url?: string; + cabinet_url?: string; + admin_url?: string; + [key: string]: string | undefined; +} + +export interface Environment { + id: number; + name: string; + urls: EnvironmentUrls; + createdAt: string; + updatedAt: string; +} + +// ── Keys ────────────────────────────────────────────────────────────────────── + +export interface KeysResponse { + keys: string[]; +} + +// ── Sessions ────────────────────────────────────────────────────────────────── + +export type SessionStatus = 'open' | 'closed'; + +export interface Session { + id: number; + sessionName: string; + token: string; + status: SessionStatus; + lastUsedAt: string | null; + createdAt: string; + updatedAt: string; +} + +// ── Scenarios ───────────────────────────────────────────────────────────────── + +export type StepType = 'login' | 'exec' | 'sign'; + +export interface ScenarioStep { + id: number; + scenarioId: number; + order: number; + type: StepType; + sessionName: string; + execCode: string | null; + validateCode: string | null; + createdAt: string; + updatedAt: string; +} + +export interface Scenario { + id: number; + name: string; + steps?: ScenarioStep[]; + createdAt: string; + updatedAt: string; +} + +export type ScenarioRunStatus = 'pending' | 'running' | 'pass' | 'fail'; + +export interface ScenarioRun { + id: number; + scenarioId: number; + status: ScenarioRunStatus; + createdAt: string; + updatedAt: string; +} diff --git a/client/src/declarations.d.ts b/client/src/declarations.d.ts new file mode 100644 index 0000000..65bd2fc --- /dev/null +++ b/client/src/declarations.d.ts @@ -0,0 +1,8 @@ +declare module '*.css' { + const styles: Record; + export default styles; +} + +interface ImportMetaEnv { + readonly VITE_API_URL?: string; +} diff --git a/client/src/main.tsx b/client/src/main.tsx index feac8ee..19d49f8 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,5 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import './ui/tokens.css' import App from './App' createRoot(document.getElementById('root')!).render( diff --git a/client/src/pages/EnvironmentsPage.tsx b/client/src/pages/EnvironmentsPage.tsx new file mode 100644 index 0000000..303d85e --- /dev/null +++ b/client/src/pages/EnvironmentsPage.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { environments } from '../api'; +import type { Environment } from '../api'; +import { Table, type TableColumn } from '../ui'; +import styles from './Page.module.css'; + +const columns: TableColumn[] = [ + { key: 'id', header: 'ID', render: (e) => e.id, width: 60 }, + { key: 'name', header: 'Name', render: (e) => e.name }, + { + key: 'urls', + header: 'URLs', + render: (e) => + Object.entries(e.urls) + .filter(([, v]) => v) + .map(([k, v]) => `${k}: ${v}`) + .join(' · ') || '—', + }, + { + key: 'updated', + header: 'Updated', + width: 120, + render: (e) => new Date(e.updatedAt).toLocaleDateString(), + }, +]; + +export function EnvironmentsPage() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + setLoading(true); + environments.list() + .then((res) => setItems(res.data)) + .catch((err: Error) => setError(err.message)) + .finally(() => setLoading(false)); + }, []); + + return ( +
+

Environments

+ {error &&

{error}

} + e.id} + loading={loading} + emptyMessage="No environments yet." + /> + + ); +} diff --git a/client/src/pages/KeysPage.tsx b/client/src/pages/KeysPage.tsx new file mode 100644 index 0000000..caab7fe --- /dev/null +++ b/client/src/pages/KeysPage.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react'; +import { keys } from '../api'; +import { Table, type TableColumn } from '../ui'; +import styles from './Page.module.css'; + +interface KeyRow { name: string } + +const columns: TableColumn[] = [ + { key: 'name', header: 'Key name', render: (k) => k.name }, +]; + +export function KeysPage() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + setLoading(true); + keys.list() + .then((res) => setItems(res.keys.map((name) => ({ name })))) + .catch((err: Error) => setError(err.message)) + .finally(() => setLoading(false)); + }, []); + + return ( +
+

Keys

+ {error &&

{error}

} +
k.name} + loading={loading} + emptyMessage="No key files found." + /> + + ); +} diff --git a/client/src/pages/Page.module.css b/client/src/pages/Page.module.css new file mode 100644 index 0000000..f9d8126 --- /dev/null +++ b/client/src/pages/Page.module.css @@ -0,0 +1,15 @@ +.heading { + margin: 0 0 var(--space-4); + font-size: var(--font-size-lg); + font-weight: 700; + color: var(--color-text); +} + +.error { + margin-bottom: var(--space-4); + padding: var(--space-3) var(--space-4); + background: var(--color-error-bg); + color: var(--color-error-fg); + border-radius: var(--radius-md); + font-size: var(--font-size-sm); +} diff --git a/client/src/pages/ScenariosPage.tsx b/client/src/pages/ScenariosPage.tsx new file mode 100644 index 0000000..658fe2a --- /dev/null +++ b/client/src/pages/ScenariosPage.tsx @@ -0,0 +1,67 @@ +import { useCallback, useEffect, useState } from 'react'; +import { scenarios } from '../api'; +import type { Scenario } from '../api'; +import { Button, Table, type TableColumn } from '../ui'; +import styles from './Page.module.css'; + +export function ScenariosPage() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const load = useCallback(() => { + setLoading(true); + scenarios.list() + .then((res) => setItems(res.data)) + .catch((err: Error) => setError(err.message)) + .finally(() => setLoading(false)); + }, []); + + useEffect(load, [load]); + + const handleRun = async (id: number) => { + await scenarios.run(id); + }; + + const handleDelete = async (id: number) => { + await scenarios.remove(id); + load(); + }; + + const columns: TableColumn[] = [ + { key: 'id', header: 'ID', render: (s) => s.id, width: 60 }, + { key: 'name', header: 'Name', render: (s) => s.name }, + { + key: 'updated', + header: 'Updated', + width: 120, + render: (s) => new Date(s.updatedAt).toLocaleDateString(), + }, + { + key: 'actions', + header: '', + width: 140, + align: 'right', + render: (s) => ( + + + + + ), + }, + ]; + + return ( +
+

Scenarios

+ {error &&

{error}

} +
s.id} + loading={loading} + emptyMessage="No scenarios yet." + /> + + ); +} diff --git a/client/src/pages/SessionsPage.tsx b/client/src/pages/SessionsPage.tsx new file mode 100644 index 0000000..00d84dd --- /dev/null +++ b/client/src/pages/SessionsPage.tsx @@ -0,0 +1,72 @@ +import { useCallback, useEffect, useState } from 'react'; +import { sessions } from '../api'; +import type { Session } from '../api'; +import { Badge, Button, Table, type TableColumn } from '../ui'; +import styles from './Page.module.css'; + +export function SessionsPage() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const load = useCallback(() => { + setLoading(true); + sessions.list() + .then((res) => setItems(res.data)) + .catch((err: Error) => setError(err.message)) + .finally(() => setLoading(false)); + }, []); + + useEffect(load, [load]); + + const handleDelete = async (id: number) => { + await sessions.remove(id); + load(); + }; + + const columns: TableColumn[] = [ + { key: 'id', header: 'ID', render: (s) => s.id, width: 60 }, + { key: 'name', header: 'Name', render: (s) => s.sessionName }, + { + key: 'status', + header: 'Status', + width: 100, + render: (s) => ( + + {s.status} + + ), + }, + { + key: 'lastUsed', + header: 'Last Used', + width: 160, + render: (s) => s.lastUsedAt ? new Date(s.lastUsedAt).toLocaleString() : '—', + }, + { + key: 'actions', + header: '', + width: 80, + align: 'right', + render: (s) => ( + + ), + }, + ]; + + return ( +
+

Sessions

+ {error &&

{error}

} +
s.id} + loading={loading} + emptyMessage="No sessions yet." + /> + + ); +} diff --git a/client/src/ui/Badge/Badge.module.css b/client/src/ui/Badge/Badge.module.css new file mode 100644 index 0000000..75a2777 --- /dev/null +++ b/client/src/ui/Badge/Badge.module.css @@ -0,0 +1,27 @@ +.badge { + display: inline-flex; + align-items: center; + gap: 5px; + font-family: var(--font-family); + font-size: var(--font-size-xs); + font-weight: 500; + padding: 2px var(--space-2); + border-radius: var(--radius-full); + white-space: nowrap; + line-height: 1.6; +} + +.dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + flex-shrink: 0; +} + +.success { background: var(--color-success-bg); color: var(--color-success-fg); } +.error { background: var(--color-error-bg); color: var(--color-error-fg); } +.warning { background: var(--color-warning-bg); color: var(--color-warning-fg); } +.info { background: var(--color-info-bg); color: var(--color-info-fg); } +.neutral { background: var(--color-neutral-bg); color: var(--color-neutral-fg); } +.running { background: var(--color-running-bg); color: var(--color-running-fg); } diff --git a/client/src/ui/Badge/Badge.stories.tsx b/client/src/ui/Badge/Badge.stories.tsx new file mode 100644 index 0000000..959872b --- /dev/null +++ b/client/src/ui/Badge/Badge.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Badge } from './Badge'; + +const meta: Meta = { + title: 'UI/Badge', + component: Badge, + parameters: { layout: 'centered' }, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['success', 'error', 'warning', 'info', 'neutral', 'running'], + }, + dot: { control: 'boolean' }, + }, +}; +export default meta; +type Story = StoryObj; + +export const Success: Story = { args: { children: 'Passed', variant: 'success', dot: true } }; +export const Error: Story = { args: { children: 'Failed', variant: 'error', dot: true } }; +export const Warning: Story = { args: { children: 'Warning', variant: 'warning' } }; +export const Info: Story = { args: { children: 'Info', variant: 'info' } }; +export const Neutral: Story = { args: { children: 'Pending', variant: 'neutral' } }; +export const Running: Story = { args: { children: 'Running', variant: 'running', dot: true } }; diff --git a/client/src/ui/Badge/Badge.tsx b/client/src/ui/Badge/Badge.tsx new file mode 100644 index 0000000..4a36a44 --- /dev/null +++ b/client/src/ui/Badge/Badge.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import styles from './Badge.module.css'; + +export type BadgeVariant = 'success' | 'error' | 'warning' | 'info' | 'neutral' | 'running'; + +export interface BadgeProps { + variant?: BadgeVariant; + dot?: boolean; + children: React.ReactNode; +} + +export function Badge({ variant = 'neutral', dot = false, children }: BadgeProps) { + return ( + + {dot && + ); +} diff --git a/client/src/ui/Breadcrumbs/Breadcrumbs.module.css b/client/src/ui/Breadcrumbs/Breadcrumbs.module.css new file mode 100644 index 0000000..8d04ee2 --- /dev/null +++ b/client/src/ui/Breadcrumbs/Breadcrumbs.module.css @@ -0,0 +1,54 @@ +.nav { + font-family: var(--font-family); +} + +.list { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-1); + list-style: none; + margin: 0; + padding: 0; +} + +.item { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.link { + font-size: var(--font-size-sm); + color: var(--color-text-muted); + text-decoration: none; + background: none; + border: none; + padding: 0; + cursor: pointer; + font-family: inherit; + border-radius: var(--radius-sm); + transition: color var(--transition); +} + +.link:hover { + color: var(--color-text); + text-decoration: underline; +} + +.link:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +.current { + font-size: var(--font-size-sm); + font-weight: 600; + color: var(--color-text); +} + +.separator { + display: flex; + align-items: center; + color: var(--color-border); +} diff --git a/client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx b/client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx new file mode 100644 index 0000000..6c34c7c --- /dev/null +++ b/client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx @@ -0,0 +1,48 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Breadcrumbs } from './Breadcrumbs'; + +const meta: Meta = { + title: 'UI/Breadcrumbs', + component: Breadcrumbs, + parameters: { layout: 'centered' }, + tags: ['autodocs'], +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + items: [ + { label: 'Scenarios', href: '#' }, + { label: 'Login flow' }, + ], + }, +}; + +export const Deep: Story = { + args: { + items: [ + { label: 'Home', href: '#' }, + { label: 'Scenarios', href: '#' }, + { label: 'Login flow', href: '#' }, + { label: 'Step 3' }, + ], + }, +}; + +export const SingleItem: Story = { + args: { + items: [{ label: 'Dashboard' }], + }, +}; + +export const CustomSeparator: Story = { + args: { + separator: '/', + items: [ + { label: 'Environments', href: '#' }, + { label: 'Staging', href: '#' }, + { label: 'Config' }, + ], + }, +}; diff --git a/client/src/ui/Breadcrumbs/Breadcrumbs.tsx b/client/src/ui/Breadcrumbs/Breadcrumbs.tsx new file mode 100644 index 0000000..83a77e9 --- /dev/null +++ b/client/src/ui/Breadcrumbs/Breadcrumbs.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import styles from './Breadcrumbs.module.css'; + +export interface BreadcrumbItem { + label: string; + href?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export interface BreadcrumbsProps { + items: BreadcrumbItem[]; + separator?: React.ReactNode; + className?: string; +} + +export function Breadcrumbs({ items, separator, className }: BreadcrumbsProps) { + const sep = separator ?? ( + + ); + + return ( + + ); +} diff --git a/client/src/ui/Button/Button.module.css b/client/src/ui/Button/Button.module.css new file mode 100644 index 0000000..3b973f7 --- /dev/null +++ b/client/src/ui/Button/Button.module.css @@ -0,0 +1,89 @@ +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + font-family: var(--font-family); + font-weight: 700; + border: var(--border-width) solid transparent; + border-radius: var(--radius-md); + cursor: pointer; + white-space: nowrap; + outline: none; + text-decoration: none; + transition: + background var(--transition), + border-color var(--transition), + color var(--transition), + opacity var(--transition); +} + +.button:focus-visible { + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.35); +} + +.button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Sizes */ +.sm { + font-size: var(--font-size-sm); + padding: var(--space-1) var(--space-3); + height: 28px; +} +.md { + font-size: var(--font-size-md); + padding: var(--space-2) var(--space-4); + height: 36px; +} +.lg { + font-size: var(--font-size-lg); + padding: var(--space-3) var(--space-5); + height: 44px; +} + +/* Variants */ +.primary { + background: var(--color-primary); + color: var(--color-primary-fg); +} +.primary:hover:not(:disabled) { background: var(--color-primary-hover); } +.primary:active:not(:disabled) { background: var(--color-primary-active); } + +.secondary { + background: var(--color-secondary); + color: var(--color-secondary-fg); + border-color: var(--color-secondary-border); +} +.secondary:hover:not(:disabled) { background: var(--color-secondary-hover); } +.secondary:active:not(:disabled) { background: var(--color-secondary-active); } + +.ghost { + background: transparent; + color: var(--color-text); +} +.ghost:hover:not(:disabled) { background: var(--color-secondary); } +.ghost:active:not(:disabled) { background: var(--color-secondary-hover); } + +.danger { + background: var(--color-danger); + color: var(--color-danger-fg); +} +.danger:hover:not(:disabled) { background: var(--color-danger-hover); } + +/* Loading spinner */ +.spinner { + width: 12px; + height: 12px; + border: 2px solid currentColor; + border-top-color: transparent; + border-radius: 50%; + animation: spin 0.6s linear infinite; + flex-shrink: 0; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} diff --git a/client/src/ui/Button/Button.stories.tsx b/client/src/ui/Button/Button.stories.tsx new file mode 100644 index 0000000..d5b5160 --- /dev/null +++ b/client/src/ui/Button/Button.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Button } from './Button'; + +const meta: Meta = { + title: 'UI/Button', + component: Button, + parameters: { layout: 'centered' }, + tags: ['autodocs'], + argTypes: { + variant: { control: 'select', options: ['primary', 'secondary', 'ghost', 'danger'] }, + size: { control: 'select', options: ['sm', 'md', 'lg'] }, + loading: { control: 'boolean' }, + disabled: { control: 'boolean' }, + }, +}; +export default meta; +type Story = StoryObj; + +export const Primary: Story = { args: { children: 'Button', variant: 'primary' } }; +export const Secondary: Story = { args: { children: 'Button', variant: 'secondary' } }; +export const Ghost: Story = { args: { children: 'Button', variant: 'ghost' } }; +export const Danger: Story = { args: { children: 'Delete', variant: 'danger' } }; +export const Small: Story = { args: { children: 'Small', size: 'sm' } }; +export const Large: Story = { args: { children: 'Large', size: 'lg' } }; +export const Loading: Story = { args: { children: 'Saving…', loading: true } }; +export const Disabled: Story = { args: { children: 'Button', disabled: true } }; diff --git a/client/src/ui/Button/Button.tsx b/client/src/ui/Button/Button.tsx new file mode 100644 index 0000000..62136ab --- /dev/null +++ b/client/src/ui/Button/Button.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import styles from './Button.module.css'; + +export interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + loading?: boolean; +} + +export function Button({ + variant = 'primary', + size = 'md', + loading = false, + disabled, + children, + className, + ...props +}: ButtonProps) { + return ( + + ); +} diff --git a/client/src/ui/Card/Card.module.css b/client/src/ui/Card/Card.module.css new file mode 100644 index 0000000..27a40b6 --- /dev/null +++ b/client/src/ui/Card/Card.module.css @@ -0,0 +1,11 @@ +.card { + background: var(--color-bg-subtle); + border: var(--border-width) solid var(--color-border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); + font-family: var(--font-family); +} + +.sm { padding: var(--space-3) var(--space-4); } +.md { padding: var(--space-4) var(--space-6); } +.lg { padding: var(--space-6) var(--space-8); } diff --git a/client/src/ui/Card/Card.stories.tsx b/client/src/ui/Card/Card.stories.tsx new file mode 100644 index 0000000..fc4ca3d --- /dev/null +++ b/client/src/ui/Card/Card.stories.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Card } from './Card'; +import { Badge } from '../Badge/Badge'; +import { Button } from '../Button/Button'; + +const meta: Meta = { + title: 'UI/Card', + component: Card, + parameters: { layout: 'centered' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + padding: { control: 'select', options: ['sm', 'md', 'lg'] }, + }, +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { children: 'Card content goes here.', padding: 'md' }, +}; + +export const ScenarioCard: Story = { + render: () => ( + +
+ Login flow + Passed +
+

+ Last run 2 minutes ago · 4 steps +

+
+ + +
+
+ ), +}; diff --git a/client/src/ui/Card/Card.tsx b/client/src/ui/Card/Card.tsx new file mode 100644 index 0000000..99c9f93 --- /dev/null +++ b/client/src/ui/Card/Card.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './Card.module.css'; + +export interface CardProps { + children: React.ReactNode; + className?: string; + padding?: 'sm' | 'md' | 'lg'; +} + +export function Card({ children, className, padding = 'md' }: CardProps) { + return ( +
+ {children} +
+ ); +} diff --git a/client/src/ui/Colors.stories.tsx b/client/src/ui/Colors.stories.tsx new file mode 100644 index 0000000..0aec90c --- /dev/null +++ b/client/src/ui/Colors.stories.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; + +const palette = [ + { name: 'Atlantis', token: '--color-primary', hex: '#9DD341', role: 'Primary / Action' }, + { name: 'Outer Space', token: '--color-text', hex: '#2D3339', role: 'Text / Surface' }, + { name: 'Kimberly', token: '--color-text-muted', hex: '#6650C0', role: 'Muted text / Info' }, + { name: 'Powder Ash', token: '--color-border', hex: '#AFBFB9', role: 'Border / Neutral' }, +]; + +const semanticColors = [ + { name: 'Success bg', token: '--color-success-bg', hex: '#EEF8D6' }, + { name: 'Success fg', token: '--color-success-fg', hex: '#3E6010' }, + { name: 'Error bg', token: '--color-error-bg', hex: '#FAEAEA' }, + { name: 'Error fg', token: '--color-error-fg', hex: '#8B2020' }, + { name: 'Warning bg', token: '--color-warning-bg', hex: '#FEF5E0' }, + { name: 'Warning fg', token: '--color-warning-fg', hex: '#7A4E00' }, + { name: 'Info bg', token: '--color-info-bg', hex: '#EEEDF6' }, + { name: 'Info fg', token: '--color-info-fg', hex: '#4A4270' }, + { name: 'Neutral bg', token: '--color-neutral-bg', hex: '#EDF1EF' }, + { name: 'Neutral fg', token: '--color-neutral-fg', hex: '#4D6059' }, + { name: 'Running bg', token: '--color-running-bg', hex: '#FEF9C3' }, + { name: 'Running fg', token: '--color-running-fg', hex: '#6B4A00' }, +]; + +const surfaceColors = [ + { name: 'Background', token: '--color-bg', hex: '#E6EDEA' }, + { name: 'Background subtle', token: '--color-bg-subtle', hex: '#D8E2DE' }, + { name: 'Secondary', token: '--color-secondary', hex: '#EDF1EF' }, + { name: 'Secondary hover', token: '--color-secondary-hover', hex: '#DFE6E3' }, + { name: 'Danger', token: '--color-danger', hex: '#D94F4F' }, + { name: 'Primary fg', token: '--color-primary-fg', hex: '#2D3339' }, +]; + +function Swatch({ name, token, hex, role }: { name: string; token: string; hex: string; role?: string }) {; + return ( +
+
+
+
{name}
+ {role &&
{role}
} +
{hex}
+
{token}
+
+
+ ); +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

+ {title} +

+
+ {children} +
+
+ ); +} + +function ColorPalette() { + return ( +
+

Color Palette

+

+ All colors are exposed as CSS custom properties on :root via tokens.css. +

+
+ {palette.map(c => )} +
+
+ {semanticColors.map(c => )} +
+
+ {surfaceColors.map(c => )} +
+
+ ); +} + +const meta: Meta = { + title: 'Design Tokens/Colors', + parameters: { layout: 'fullscreen' }, +}; +export default meta; + +type Story = StoryObj; +export const All: Story = { render: () => }; diff --git a/client/src/ui/Input/Input.module.css b/client/src/ui/Input/Input.module.css new file mode 100644 index 0000000..c7cd76e --- /dev/null +++ b/client/src/ui/Input/Input.module.css @@ -0,0 +1,60 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: var(--space-1); + font-family: var(--font-family); + width: 100%; +} + +.label { + font-size: var(--font-size-sm); + font-weight: 500; + color: var(--color-text); +} + +.input { + font-family: var(--font-family); + font-size: var(--font-size-md); + color: var(--color-text); + background: var(--color-bg); + border: var(--border-width) solid var(--color-border); + border-radius: var(--radius-md); + padding: var(--space-2) var(--space-3); + height: 36px; + width: 100%; + outline: none; + transition: border-color var(--transition), box-shadow var(--transition); +} + +.input::placeholder { + color: var(--color-text-muted); +} + +.input:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +.input:disabled { + opacity: 0.5; + cursor: not-allowed; + background: var(--color-bg-subtle); +} + +.hasError { + border-color: var(--color-error-fg); +} +.hasError:focus { + border-color: var(--color-error-fg); + box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2); +} + +.hint { + font-size: var(--font-size-xs); + color: var(--color-text-muted); +} + +.error { + font-size: var(--font-size-xs); + color: var(--color-error-fg); +} diff --git a/client/src/ui/Input/Input.stories.tsx b/client/src/ui/Input/Input.stories.tsx new file mode 100644 index 0000000..931eed9 --- /dev/null +++ b/client/src/ui/Input/Input.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Input } from './Input'; + +const meta: Meta = { + title: 'UI/Input', + component: Input, + parameters: { layout: 'centered' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { label: 'Label', placeholder: 'Placeholder' }, +}; +export const WithHint: Story = { + args: { + label: 'Environment name', + placeholder: 'e.g. staging', + hint: 'Used to identify this environment in scenarios.', + }, +}; +export const WithError: Story = { + args: { + label: 'URL', + placeholder: 'https://...', + defaultValue: 'not a url', + error: 'Must be a valid URL.', + }, +}; +export const Disabled: Story = { + args: { label: 'Label', value: 'Some value', disabled: true, readOnly: true }, +}; diff --git a/client/src/ui/Input/Input.tsx b/client/src/ui/Input/Input.tsx new file mode 100644 index 0000000..4bd496e --- /dev/null +++ b/client/src/ui/Input/Input.tsx @@ -0,0 +1,37 @@ +import React, { useId } from 'react'; +import styles from './Input.module.css'; + +export interface InputProps extends Omit, 'id'> { + label?: string; + hint?: string; + error?: string; +} + +export function Input({ label, hint, error, className, ...props }: InputProps) { + const id = useId(); + return ( +
+ {label && ( + + )} + + {error ? ( + + {error} + + ) : hint ? ( + + {hint} + + ) : null} +
+ ); +} diff --git a/client/src/ui/Select/Select.module.css b/client/src/ui/Select/Select.module.css new file mode 100644 index 0000000..16ee202 --- /dev/null +++ b/client/src/ui/Select/Select.module.css @@ -0,0 +1,79 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: var(--space-1); + font-family: var(--font-family); + width: 100%; +} + +.label { + font-size: var(--font-size-sm); + font-weight: 500; + color: var(--color-text); +} + +.control { + position: relative; + display: flex; + align-items: center; +} + +.select { + font-family: var(--font-family); + font-size: var(--font-size-md); + color: var(--color-text); + background: var(--color-bg); + border: var(--border-width) solid var(--color-border); + border-radius: var(--radius-md); + padding: var(--space-2) var(--space-3); + padding-right: calc(var(--space-3) + 20px); + line-height: 1.5; + width: 100%; + outline: none; + appearance: none; + cursor: pointer; + transition: border-color var(--transition), box-shadow var(--transition); +} + +.select:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 25%, transparent); +} + +.select:disabled { + opacity: 0.5; + cursor: not-allowed; + background: var(--color-bg-subtle); +} + +.hasError { + border-color: var(--color-error-fg); +} + +.hasError:focus { + border-color: var(--color-error-fg); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-error-fg) 20%, transparent); +} + +.chevron { + position: absolute; + right: var(--space-3); + pointer-events: none; + color: var(--color-text-muted); + display: flex; + align-items: center; +} + +.select:disabled ~ .chevron { + opacity: 0.5; +} + +.hint { + font-size: var(--font-size-xs); + color: var(--color-text-muted); +} + +.error { + font-size: var(--font-size-xs); + color: var(--color-error-fg); +} diff --git a/client/src/ui/Select/Select.stories.tsx b/client/src/ui/Select/Select.stories.tsx new file mode 100644 index 0000000..868c235 --- /dev/null +++ b/client/src/ui/Select/Select.stories.tsx @@ -0,0 +1,75 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Select } from './Select'; + +const ENVIRONMENTS = [ + { value: 'dev', label: 'Development' }, + { value: 'staging', label: 'Staging' }, + { value: 'prod', label: 'Production' }, +]; + +const SCENARIO_TYPES = [ + { value: 'smoke', label: 'Smoke test' }, + { value: 'regression', label: 'Regression' }, + { value: 'e2e', label: 'End-to-end' }, + { value: 'perf', label: 'Performance', disabled: true }, +]; + +const meta: Meta = { + title: 'UI/Select', + component: Select, + parameters: { layout: 'centered' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Environment', + options: ENVIRONMENTS, + placeholder: 'Select an environment…', + }, +}; + +export const WithValue: Story = { + args: { + label: 'Environment', + options: ENVIRONMENTS, + defaultValue: 'staging', + }, +}; + +export const WithHint: Story = { + args: { + label: 'Test type', + options: SCENARIO_TYPES, + placeholder: 'Choose a type…', + hint: 'Performance tests require a dedicated runner.', + }, +}; + +export const WithError: Story = { + args: { + label: 'Environment', + options: ENVIRONMENTS, + defaultValue: '', + placeholder: 'Select an environment…', + error: 'Please select an environment.', + }, +}; + +export const Disabled: Story = { + args: { + label: 'Environment', + options: ENVIRONMENTS, + defaultValue: 'prod', + disabled: true, + }, +}; diff --git a/client/src/ui/Select/Select.tsx b/client/src/ui/Select/Select.tsx new file mode 100644 index 0000000..f517433 --- /dev/null +++ b/client/src/ui/Select/Select.tsx @@ -0,0 +1,63 @@ +import React, { useId } from 'react'; +import styles from './Select.module.css'; + +export interface SelectOption { + value: string; + label: string; + disabled?: boolean; +} + +export interface SelectProps extends Omit, 'id'> { + label?: string; + hint?: string; + error?: string; + options: SelectOption[]; + placeholder?: string; +} + +export function Select({ label, hint, error, options, placeholder, className, ...props }: SelectProps) { + const id = useId(); + return ( +
+ {label && ( + + )} +
+ + +
+ {error ? ( + + {error} + + ) : hint ? ( + + {hint} + + ) : null} +
+ ); +} diff --git a/client/src/ui/SidePanel/SidePanel.module.css b/client/src/ui/SidePanel/SidePanel.module.css new file mode 100644 index 0000000..8dc1b6a --- /dev/null +++ b/client/src/ui/SidePanel/SidePanel.module.css @@ -0,0 +1,88 @@ +.panel { + position: relative; + display: flex; + flex-shrink: 0; + width: var(--side-panel-width, 240px); + background: var(--color-bg-subtle); + border-right: var(--border-width) solid var(--color-border); + font-family: var(--font-family); + transition: width 220ms ease; + overflow: visible; +} + +.panel.collapsed { + width: 0; +} + +.inner { + width: var(--side-panel-width, 240px); + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; + flex: 1; + transition: opacity 180ms ease; +} + +.collapsed .inner { + opacity: 0; + pointer-events: none; +} + +.header { + padding: var(--space-4) var(--space-4) var(--space-3); + border-bottom: var(--border-width) solid var(--color-border); + font-weight: 600; + font-size: var(--font-size-sm); + color: var(--color-text); + flex-shrink: 0; +} + +.content { + flex: 1; + overflow-y: auto; + padding: var(--space-3) var(--space-2); +} + +/* ── Toggle button ───────────────────────────────────────── */ +.toggle { + position: absolute; + top: var(--space-5); + right: -14px; + z-index: 10; + width: 28px; + height: 28px; + border-radius: var(--radius-full); + background: var(--color-bg); + border: var(--border-width) solid var(--color-border); + color: var(--color-text-muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-sm); + transition: background var(--transition), color var(--transition); + flex-shrink: 0; +} + +.toggle:hover { + background: var(--color-secondary); + color: var(--color-secondary-fg); + border-color: var(--color-secondary-border); +} + +.toggle:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +/* ── Chevron rotation ────────────────────────────────────── */ +.chevron { + transition: transform 220ms ease; + /* Points right (→) by default = collapse direction */ +} + +.collapsed .chevron { + transform: rotate(180deg); + /* Points left (←) = expand direction */ +} diff --git a/client/src/ui/SidePanel/SidePanel.stories.tsx b/client/src/ui/SidePanel/SidePanel.stories.tsx new file mode 100644 index 0000000..bd84d90 --- /dev/null +++ b/client/src/ui/SidePanel/SidePanel.stories.tsx @@ -0,0 +1,83 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { SidePanel } from './SidePanel'; + +const NAV_ITEMS = ['Dashboard', 'Scenarios', 'Environments', 'Reports', 'Settings']; + +function NavList() { + return ( +
    + {NAV_ITEMS.map((item) => ( +
  • + +
  • + ))} +
+ ); +} + +const meta: Meta = { + title: 'UI/SidePanel', + component: SidePanel, + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ Main content area +
+
+ ), + ], +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + header: 'Navigation', + children: , + }, +}; + +export const DefaultCollapsed: Story = { + args: { + header: 'Navigation', + children: , + defaultCollapsed: true, + }, +}; + +export const NarrowWidth: Story = { + args: { + header: 'Filters', + width: 180, + children: , + }, +}; + +export const WideWidth: Story = { + args: { + header: 'Scenarios', + width: 320, + children: , + }, +}; diff --git a/client/src/ui/SidePanel/SidePanel.tsx b/client/src/ui/SidePanel/SidePanel.tsx new file mode 100644 index 0000000..d87d7b9 --- /dev/null +++ b/client/src/ui/SidePanel/SidePanel.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import styles from './SidePanel.module.css'; + +export interface SidePanelProps { + children: React.ReactNode; + header?: React.ReactNode; + width?: number; + defaultCollapsed?: boolean; + collapsed?: boolean; + onCollapsedChange?: (collapsed: boolean) => void; + className?: string; +} + +export function SidePanel({ + children, + header, + width = 240, + defaultCollapsed = false, + collapsed: controlledCollapsed, + onCollapsedChange, + className, +}: SidePanelProps) { + const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed); + const isControlled = controlledCollapsed !== undefined; + const collapsed = isControlled ? controlledCollapsed : internalCollapsed; + + function toggle() { + const next = !collapsed; + if (!isControlled) setInternalCollapsed(next); + onCollapsedChange?.(next); + } + + return ( + + ); +} diff --git a/client/src/ui/Table/Table.module.css b/client/src/ui/Table/Table.module.css new file mode 100644 index 0000000..6d8af87 --- /dev/null +++ b/client/src/ui/Table/Table.module.css @@ -0,0 +1,56 @@ +.wrapper { + width: 100%; + overflow-x: auto; + border: var(--border-width) solid var(--color-border); + border-radius: var(--radius-lg); + background: var(--color-bg-subtle); + box-shadow: var(--shadow-sm); +} + +.table { + width: 100%; + border-collapse: collapse; + font-family: var(--font-family); + font-size: var(--font-size-sm); + color: var(--color-text); +} + +.th { + padding: var(--space-3) var(--space-4); + font-weight: 700; + font-size: var(--font-size-xs); + color: var(--color-text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + border-bottom: var(--border-width) solid var(--color-border); + white-space: nowrap; + background: var(--color-bg-subtle); +} + +/* Rounded top corners on first/last header cells */ +.th:first-child { border-radius: var(--radius-lg) 0 0 0; } +.th:last-child { border-radius: 0 var(--radius-lg) 0 0; } + +.tr { + transition: background var(--transition); +} + +.tr:hover { + background: var(--color-bg); +} + +.tr:not(:last-child) .td { + border-bottom: var(--border-width) solid var(--color-border); +} + +.td { + padding: var(--space-3) var(--space-4); + vertical-align: middle; +} + +.empty { + padding: var(--space-6) var(--space-4); + text-align: center; + color: var(--color-text-muted); + font-size: var(--font-size-sm); +} diff --git a/client/src/ui/Table/Table.stories.tsx b/client/src/ui/Table/Table.stories.tsx new file mode 100644 index 0000000..ae00bcd --- /dev/null +++ b/client/src/ui/Table/Table.stories.tsx @@ -0,0 +1,71 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Table } from './Table'; +import { Badge } from '../Badge/Badge'; + +const meta: Meta = { + title: 'UI/Table', + component: Table, + tags: ['autodocs'], + parameters: { layout: 'padded' }, +}; +export default meta; + +// ── Simple string rows ──────────────────────────────────────────────────────── + +interface User { + id: number; + name: string; + email: string; + status: 'active' | 'inactive'; +} + +const users: User[] = [ + { id: 1, name: 'Alice Müller', email: 'alice@example.com', status: 'active' }, + { id: 2, name: 'Bob Nguyen', email: 'bob@example.com', status: 'inactive' }, + { id: 3, name: 'Carol Santos', email: 'carol@example.com', status: 'active' }, +]; + +export const Default: StoryObj> = { + args: { + data: users, + rowKey: (u) => u.id, + emptyMessage: 'No users found.', + columns: [ + { key: 'id', header: 'ID', render: (u) => u.id, width: 60 }, + { key: 'name', header: 'Name', render: (u) => u.name }, + { key: 'email', header: 'Email', render: (u) => u.email }, + { + key: 'status', + header: 'Status', + width: 110, + render: (u) => ( + + {u.status} + + ), + }, + ], + }, +}; + +export const Loading: StoryObj> = { + args: { + ...Default.args, + loading: true, + }, +}; + +export const Empty: StoryObj> = { + args: { + ...Default.args, + data: [], + emptyMessage: 'No users found.', + }, +}; + +export const SingleRow: StoryObj> = { + args: { + ...Default.args, + data: [users[0]], + }, +}; diff --git a/client/src/ui/Table/Table.tsx b/client/src/ui/Table/Table.tsx new file mode 100644 index 0000000..e34e05f --- /dev/null +++ b/client/src/ui/Table/Table.tsx @@ -0,0 +1,80 @@ +import type { ReactNode } from 'react'; +import styles from './Table.module.css'; + +export interface TableColumn { + key: string; + header: ReactNode; + render: (row: T) => ReactNode; + width?: string | number; + align?: 'left' | 'center' | 'right'; +} + +export interface TableProps { + columns: TableColumn[]; + data: T[]; + rowKey: (row: T) => string | number; + loading?: boolean; + emptyMessage?: string; + className?: string; +} + +export function Table({ + columns, + data, + rowKey, + loading = false, + emptyMessage = 'No data.', + className, +}: TableProps) { + return ( +
+
+ + + {columns.map((col) => ( + + ))} + + + + {loading ? ( + + + + ) : data.length === 0 ? ( + + + + ) : ( + data.map((row) => ( + + {columns.map((col) => ( + + ))} + + )) + )} + +
+ {col.header} +
+ Loading… +
+ {emptyMessage} +
+ {col.render(row)} +
+
+ ); +} diff --git a/client/src/ui/index.ts b/client/src/ui/index.ts new file mode 100644 index 0000000..d3f4ea9 --- /dev/null +++ b/client/src/ui/index.ts @@ -0,0 +1,23 @@ +export { Button } from './Button/Button'; +export type { ButtonProps } from './Button/Button'; + +export { Badge } from './Badge/Badge'; +export type { BadgeProps, BadgeVariant } from './Badge/Badge'; + +export { Input } from './Input/Input'; +export type { InputProps } from './Input/Input'; + +export { Select } from './Select/Select'; +export type { SelectProps, SelectOption } from './Select/Select'; + +export { Card } from './Card/Card'; +export type { CardProps } from './Card/Card'; + +export { SidePanel } from './SidePanel/SidePanel'; +export type { SidePanelProps } from './SidePanel/SidePanel'; + +export { Breadcrumbs } from './Breadcrumbs/Breadcrumbs'; +export type { BreadcrumbsProps, BreadcrumbItem } from './Breadcrumbs/Breadcrumbs'; + +export { Table } from './Table/Table'; +export type { TableProps, TableColumn } from './Table/Table'; diff --git a/client/src/ui/tokens.css b/client/src/ui/tokens.css new file mode 100644 index 0000000..2bfa8ea --- /dev/null +++ b/client/src/ui/tokens.css @@ -0,0 +1,134 @@ +:root { + /* ── Palette ────────────────────────────────────────────────────────────── + * Atlantis #9DD341 — primary / action + * Outer Space #2D3339 — text / surfaces + * Kimberly #6650C0 — accent / info / muted text + * Powder Ash #AFBFB9 — borders / neutral + * ──────────────────────────────────────────────────────────────────────── */ + + /* Primary — Atlantis */ + --color-primary: #9DD341; + --color-primary-hover: #8ABD2E; + --color-primary-active: #77A320; + --color-primary-fg: #2D3339; /* Outer Space on bright green */ + + /* Secondary — Kimberly tint */ + --color-secondary: #EDEAF8; + --color-secondary-hover: #DDD8F2; + --color-secondary-active: #CCC5EB; + --color-secondary-fg: #6650C0; /* Kimberly */ + --color-secondary-border: #B8B0E0; + + /* Danger */ + --color-danger: #D94F4F; + --color-danger-hover: #C03A3A; + --color-danger-fg: #ffffff; + + /* Semantic */ + --color-success-bg: #EEF8D6; /* Atlantis tint */ + --color-success-fg: #3E6010; + --color-error-bg: #FAEAEA; + --color-error-fg: #8B2020; + --color-warning-bg: #FEF5E0; + --color-warning-fg: #7A4E00; + --color-info-bg: #EEEDF6; /* Kimberly tint */ + --color-info-fg: #4A4270; + --color-neutral-bg: #EDF1EF; /* Powder Ash tint */ + --color-neutral-fg: #4D6059; + --color-running-bg: #FEF9C3; + --color-running-fg: #6B4A00; + + /* Text & Surface — Outer Space */ + --color-text: #2D3339; + --color-text-muted: #6650C0; /* Kimberly */ + --color-border: #AFBFB9; /* Powder Ash */ + --color-bg: #E6EDEA; /* Powder Ash tint */ + --color-bg-subtle: #D8E2DE; /* Powder Ash tint, 1 step darker */ + + /* Spacing */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + + /* Border radius */ + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-full: 9999px; + + /* Typography */ + --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-size-xs: 13px; + --font-size-sm: 15px; + --font-size-md: 17px; + --font-size-lg: 20px; + + /* Borders */ + --border-width: 1.5px; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -1px rgba(0, 0, 0, 0.04); + + /* Transitions */ + --transition: 150ms ease; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + font-family: var(--font-family); + color: var(--color-text); + background: var(--color-bg); + margin: 0; +} + +/* ── Dark theme ─────────────────────────────────────────────────────────── */ +[data-theme="dark"] { + /* Primary — Atlantis unchanged, pops well on dark */ + --color-primary: #9DD341; + --color-primary-hover: #AEDE55; + --color-primary-active: #8ABD2E; + --color-primary-fg: #1A1F24; + + /* Secondary — dark Kimberly tint */ + --color-secondary: #2A2548; + --color-secondary-hover: #352F58; + --color-secondary-active: #403868; + --color-secondary-fg: #A9A3C9; /* lightened Kimberly */ + --color-secondary-border: #5A538A; + + /* Danger */ + --color-danger: #E06060; + --color-danger-hover: #CC4A4A; + --color-danger-fg: #1A1F24; + + /* Semantic */ + --color-success-bg: #1A3410; + --color-success-fg: #9DD341; + --color-error-bg: #3A1212; + --color-error-fg: #F08888; + --color-warning-bg: #3A2800; + --color-warning-fg: #F0C860; + --color-info-bg: #1E1A38; + --color-info-fg: #B0AAD6; /* lightened Kimberly */ + --color-neutral-bg: #2D3339; + --color-neutral-fg: #AFBFB9; + --color-running-bg: #382E00; + --color-running-fg: #F0D060; + + /* Text & Surface */ + --color-text: #E2EAE6; + --color-text-muted: #A9A3C9; /* lightened Kimberly */ + --color-border: #3D4850; + --color-bg: #1A1F24; /* deeper than Outer Space */ + --color-bg-subtle: #2D3339; /* Outer Space */ +} diff --git a/client/tsconfig.json b/client/tsconfig.json index 4c77361..0477b97 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -11,7 +11,8 @@ "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", - "strict": true + "strict": true, + "types": ["vite/client"] }, - "include": ["src"] + "include": ["src", ".storybook"] } diff --git a/client/vite.config.ts b/client/vite.config.ts index 9ffcc67..8c57bb9 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,6 +1,44 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +import { playwright } from '@vitest/browser-playwright'; +const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); +// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon export default defineConfig({ plugins: [react()], -}) + server: { + proxy: { + '/environments': 'http://localhost:13000', + '/sessions': 'http://localhost:13000', + '/scenarios': 'http://localhost:13000', + '/keys': 'http://localhost:13000', + '/login': 'http://localhost:13000', + }, + }, + test: { + projects: [{ + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook') + })], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: playwright({}), + instances: [{ + browser: 'chromium' + }] + } + } + }] + } +}); \ No newline at end of file diff --git a/client/vitest.shims.d.ts b/client/vitest.shims.d.ts new file mode 100644 index 0000000..7782f28 --- /dev/null +++ b/client/vitest.shims.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index eb7d11e..b3c89a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,20 @@ "react-dom": "^19.1.0" }, "devDependencies": { + "@storybook/addon-a11y": "^10.3.5", + "@storybook/addon-docs": "^10.3.5", + "@storybook/addon-vitest": "^10.3.5", + "@storybook/react-vite": "^10.3.5", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", + "@vitest/browser-playwright": "^4.1.3", + "@vitest/coverage-v8": "^4.1.3", + "playwright": "^1.59.1", + "storybook": "^10.3.5", "typescript": "~5.8.3", - "vite": "^6.3.1" + "vite": "^6.3.1", + "vitest": "^4.1.3" } }, "client/node_modules/typescript": { @@ -41,6 +50,13 @@ "node": ">=14.17" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/core": { "version": "19.2.22", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", @@ -700,6 +716,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -755,6 +782,13 @@ "dev": true, "license": "MIT" }, + "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", @@ -2543,6 +2577,26 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2613,6 +2667,24 @@ "node": ">=8" } }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, "node_modules/@microsoft/tsdoc": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", @@ -3101,6 +3173,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -3108,6 +3187,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", @@ -3498,6 +3600,334 @@ "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/addon-a11y": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.3.5.tgz", + "integrity": "sha512-5k6lpgfIeLxvNhE8v3wEzdiu73ONKjF4gmH1AHvfqYd8kIVzQJai0KCDxgvqNncXHQhIWkaf1fg6+9hKaYJyaw==", + "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.3.5" + } + }, + "node_modules/@storybook/addon-docs": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.3.5.tgz", + "integrity": "sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mdx-js/react": "^3.0.0", + "@storybook/csf-plugin": "10.3.5", + "@storybook/icons": "^2.0.1", + "@storybook/react-dom-shim": "10.3.5", + "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", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.3.5" + } + }, + "node_modules/@storybook/addon-vitest": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.3.5.tgz", + "integrity": "sha512-PQDeeMwoF55kvzlhFqVKOryBJskkVk71AbDh7F0y8PdRRxlGbTvIUkKXktHZWBdESo0dV6BkeVxGQ4ZpiFxirg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.1" + }, + "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.3.5", + "vitest": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/runner": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-vite": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.3.5.tgz", + "integrity": "sha512-i4KwCOKbhtlbQIbhm53+Kk7bMnxa0cwTn1pxmtA/x5wm1Qu7FrrBQV0V0DNjkUqzcSKo1CjspASJV/HlY0zYlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/csf-plugin": "10.3.5", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.3.5", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.3.5.tgz", + "integrity": "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "unplugin": "^2.3.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "esbuild": "*", + "rollup": "*", + "storybook": "^10.3.5", + "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.1", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.0.1.tgz", + "integrity": "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==", + "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.3.5", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.3.5.tgz", + "integrity": "sha512-tpLTLaVGoA6fLK3ReyGzZUricq7lyPaV2hLPpj5wqdXLV/LpRtAHClUpNoPDYSBjlnSjL81hMZijbkGC3mA+gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/react-dom-shim": "10.3.5", + "react-docgen": "^8.0.2", + "react-docgen-typescript": "^2.2.2" + }, + "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.3.5", + "typescript": ">= 4.9.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.3.5.tgz", + "integrity": "sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g==", + "dev": true, + "license": "MIT", + "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.3.5" + } + }, + "node_modules/@storybook/react-vite": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.3.5.tgz", + "integrity": "sha512-UB5sJHeh26bfd8sNMx2YPGYRYmErIdTRaLOT28m4bykQIa1l9IgVktsYg/geW7KsJU0lXd3oTbnUjLD+enpi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", + "@rollup/pluginutils": "^5.0.2", + "@storybook/builder-vite": "10.3.5", + "@storybook/react": "10.3.5", + "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.3.5", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/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", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/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", + "peer": true, + "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/@testing-library/dom/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", + "peer": true + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "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/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", @@ -3531,6 +3961,14 @@ "@types/estree": "*" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "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 + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3597,6 +4035,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -3624,6 +4073,20 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "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", @@ -3776,6 +4239,13 @@ "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", "license": "MIT" }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -3841,6 +4311,13 @@ "@types/react": "^19.2.0" } }, + "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/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -4248,6 +4725,297 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/browser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.3.tgz", + "integrity": "sha512-CS9KjO2vijuBlbwz0JIgC0YuoI1BuqWI5ziD3Nll6jkpNYtWdjPMVgGynQ9vZovjsECeUqEeNjWrypP414d0CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.3", + "@vitest/utils": "4.1.3", + "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.3" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.3.tgz", + "integrity": "sha512-D3Q+YozvSpiFaLPgd6/OMbyqEIZeucSe6AHJJ7VnNJKQhIyBE60TlBZlxzwM8bvjzQE9ZnYWQCPeCw5pnhbiNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.3", + "@vitest/mocker": "4.1.3", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.3" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/browser/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" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.3.tgz", + "integrity": "sha512-/MBdrkA8t6hbdCWFKs09dPik774xvs4Z6L4bycdCxYNLHM8oZuRyosumQMG19LUlBsB6GeVpL1q4kFFazvyKGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.3", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.3", + "vitest": "4.1.3" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/@vitest/expect/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/@vitest/expect/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/@vitest/expect/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/@vitest/expect/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/@vitest/mocker": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.3.tgz", + "integrity": "sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.3", + "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 + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/mocker/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" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.3.tgz", + "integrity": "sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.3.tgz", + "integrity": "sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.3", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.3.tgz", + "integrity": "sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.3", + "@vitest/utils": "4.1.3", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/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" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.3.tgz", + "integrity": "sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.3", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -4409,6 +5177,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", @@ -4624,6 +5399,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", @@ -4638,6 +5423,58 @@ "dev": true, "license": "MIT" }, + "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/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/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4660,6 +5497,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axe-core": { + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.2.tgz", + "integrity": "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/babel-jest": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", @@ -4963,6 +5810,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", @@ -5071,6 +5934,23 @@ ], "license": "CC-BY-4.0" }, + "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/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5105,6 +5985,16 @@ "dev": true, "license": "MIT" }, + "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/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -5526,6 +6416,13 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5604,6 +6501,16 @@ } } }, + "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/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -5630,6 +6537,36 @@ "node": ">=0.10.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/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -5660,6 +6597,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5679,6 +6629,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -5709,6 +6669,27 @@ "wrappy": "1" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dotenv": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", @@ -5800,6 +6781,16 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -6315,6 +7306,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6433,6 +7431,16 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -7276,6 +8284,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7337,6 +8355,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7379,6 +8429,25 @@ "node": ">=0.10.0" } }, + "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-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -7442,6 +8511,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -8697,6 +9782,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/lru-cache": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", @@ -8715,6 +9807,17 @@ "node": ">=12" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -8725,6 +9828,18 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -8884,6 +9999,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -8921,6 +10046,16 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9150,6 +10285,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -9187,6 +10333,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -9372,6 +10537,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -9409,6 +10581,23 @@ "node": ">=8" } }, + "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" + }, + "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/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -9515,6 +10704,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/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -9746,6 +10945,38 @@ "node": ">=0.10.0" } }, + "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-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", @@ -9803,6 +11034,60 @@ "url": "https://paulmillr.com/funding/" } }, + "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/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -9827,6 +11112,27 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -9942,6 +11248,19 @@ "node": ">= 18" } }, + "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/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -10241,6 +11560,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -10298,6 +11624,21 @@ "simple-concat": "^1.0.0" } }, + "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" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -10384,6 +11725,13 @@ "node": ">=10" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -10393,6 +11741,63 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/storybook": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.3.5.tgz", + "integrity": "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.1", + "@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", + "open": "^10.2.0", + "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": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "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/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -10498,6 +11903,19 @@ "node": ">=6" } }, + "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/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -10572,6 +11990,19 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/swagger-ui-dist": { "version": "5.31.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", @@ -10805,6 +12236,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -10835,6 +12290,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/tldts": { "version": "7.0.28", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", @@ -10901,6 +12376,16 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -10938,6 +12423,16 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-jest": { "version": "29.4.9", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", @@ -11439,6 +12934,35 @@ "node": ">= 0.8" } }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin/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" + } + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -11515,6 +13039,16 @@ "punycode": "^2.1.0" } }, + "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==", + "dev": 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", @@ -11642,6 +13176,147 @@ } } }, + "node_modules/vitest": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.3.tgz", + "integrity": "sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.3", + "@vitest/mocker": "4.1.3", + "@vitest/pretty-format": "4.1.3", + "@vitest/runner": "4.1.3", + "@vitest/snapshot": "4.1.3", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", + "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.3", + "@vitest/browser-preview": "4.1.3", + "@vitest/browser-webdriverio": "4.1.3", + "@vitest/coverage-istanbul": "4.1.3", + "@vitest/coverage-v8": "4.1.3", + "@vitest/ui": "4.1.3", + "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 + } + } + }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.3.tgz", + "integrity": "sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/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" + } + }, + "node_modules/vitest/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" + } + }, + "node_modules/vitest/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" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -11766,6 +13441,13 @@ "node": ">=10.13.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack/node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -11886,6 +13568,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -11956,6 +13655,44 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "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/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/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",