diff --git a/.storybook/main.ts b/.storybook/main.ts
deleted file mode 100644
index 956134e..0000000
--- a/.storybook/main.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { StorybookConfig } from "@storybook/react-vite";
-import { resolve, dirname } from "path";
-import { fileURLToPath } from "url";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const config: StorybookConfig = {
- stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
- addons: [
- "@chromatic-com/storybook",
- "@storybook/addon-vitest",
- "@storybook/addon-a11y",
- "@storybook/addon-docs",
- "@storybook/addon-onboarding",
- ],
- framework: "@storybook/react-vite",
- async viteFinal(config) {
- return {
- ...config,
- resolve: {
- ...config.resolve,
- alias: {
- ...config.resolve?.alias,
- "@": resolve(__dirname, "../src"),
- },
- },
- };
- },
-};
-export default config;
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
deleted file mode 100644
index 6fc6d42..0000000
--- a/.storybook/preview.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { Preview } from '@storybook/react-vite';
-import React from 'react';
-import { ThemeProvider } from '../src/theme';
-import '../src/theme/index.css';
-
-const preview: Preview = {
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
- },
- },
-
- a11y: {
- // 'todo' - show a11y violations in the test UI only
- // 'error' - fail CI on a11y violations
- // 'off' - skip a11y checks entirely
- test: 'todo'
- }
- },
- decorators: [
- (Story) => (
- React.createElement(ThemeProvider, { children: React.createElement(Story) })
- ),
- ],
-};
-
-export default preview;
\ No newline at end of file
diff --git a/.storybook/vitest.setup.ts b/.storybook/vitest.setup.ts
deleted file mode 100644
index 44922d5..0000000
--- a/.storybook/vitest.setup.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
-import { setProjectAnnotations } from '@storybook/react-vite';
-import * as projectAnnotations from './preview';
-
-// This is an important step to apply the right configuration when testing your stories.
-// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
-setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
index b6884de..848a9e8 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,6 +1,3 @@
-// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
-import storybook from "eslint-plugin-storybook";
-
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
@@ -34,4 +31,4 @@ export default tseslint.config({ ignores: ['dist', 'node_modules', 'storybook-st
"@typescript-eslint/no-explicit-any": ["off"]
},
-}, storybook.configs["flat/recommended"]);
+});
diff --git a/package-lock.json b/package-lock.json
index 313e588..f6da95a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,15 +12,9 @@
"react-dom": "^19.2.0"
},
"devDependencies": {
- "@chromatic-com/storybook": "^5.0.1",
"@commitlint/cli": "^20.4.1",
"@commitlint/config-conventional": "^20.4.1",
"@eslint/js": "^9.39.1",
- "@storybook/addon-a11y": "^10.2.8",
- "@storybook/addon-docs": "^10.2.8",
- "@storybook/addon-onboarding": "^10.2.8",
- "@storybook/addon-vitest": "^10.2.8",
- "@storybook/react-vite": "^10.2.8",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
@@ -37,7 +31,6 @@
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
- "eslint-plugin-storybook": "^10.2.8",
"globals": "^16.5.0",
"husky": "^9.1.7",
"jsdom": "^28.0.0",
@@ -45,7 +38,6 @@
"playwright": "^1.58.2",
"postcss": "^8.4.49",
"prettier": "^3.8.1",
- "storybook": "^10.2.8",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^3.4.17",
"typescript": "~5.7.0",
@@ -472,27 +464,6 @@
"specificity": "bin/cli.js"
}
},
- "node_modules/@chromatic-com/storybook": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.0.1.tgz",
- "integrity": "sha512-v80QBwVd8W6acH5NtDgFlUevIBaMZAh1pYpBiB40tuNzS242NTHeQHBDGYwIAbWKDnt1qfjJpcpL6pj5kAr4LA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@neoconfetti/react": "^1.0.0",
- "chromatic": "^13.3.4",
- "filesize": "^10.0.12",
- "jsonfile": "^6.1.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=20.0.0",
- "yarn": ">=1.22.18"
- },
- "peerDependencies": {
- "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0"
- }
- },
"node_modules/@commitlint/cli": {
"version": "20.4.1",
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.4.1.tgz",
@@ -805,522 +776,80 @@
},
{
"type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@csstools/color-helpers": "^6.0.1",
- "@csstools/css-calc": "^3.0.0"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-parser-algorithms": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
- "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.0.27",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz",
- "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT-0"
- },
- "node_modules/@csstools/css-tokenizer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
- "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
- "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
- "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
- "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
- "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
- "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
- "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
- "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
- "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
- "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
- "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
- "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
- "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
- "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
- "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
- "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
- "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
- "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
- "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
- "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
- "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
- "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^6.0.1",
+ "@csstools/css-calc": "^3.0.0"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
- "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
- "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.27",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz",
+ "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
- "engines": {
- "node": ">=18"
- }
+ "license": "MIT-0"
},
- "node_modules/@esbuild/win32-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
- "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -1597,36 +1126,6 @@
"node": "20 || >=22"
}
},
- "node_modules/@isaacs/cliui": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
- "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.4.tgz",
- "integrity": "sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA==",
- "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"
- },
- "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",
@@ -1677,24 +1176,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "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/api-extractor": {
"version": "7.56.3",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.56.3.tgz",
@@ -1836,13 +1317,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/@neoconfetti/react": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz",
- "integrity": "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2415,263 +1889,29 @@
"dev": true,
"license": "MIT",
"dependencies": {
- "@rushstack/terminal": "0.21.0",
- "@types/argparse": "1.0.38",
- "argparse": "~1.0.9",
- "string-argv": "~0.3.1"
- }
- },
- "node_modules/@rushstack/ts-command-line/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "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.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.2.8.tgz",
- "integrity": "sha512-EW5MzPKNzyPorvodd416U2Np+zEdMPe+BSyomjm0oCXoC/6rDurf05H1pa99rZsrTDRrpog+HCz8iVa4XSwN5Q==",
- "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.2.8"
- }
- },
- "node_modules/@storybook/addon-docs": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.8.tgz",
- "integrity": "sha512-cEoWqQrLzrxOwZFee5zrD4cYrdEWKV80POb7jUZO0r5vfl2DuslIr3n/+RfLT52runCV4aZcFEfOfP/IWHNPxg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@mdx-js/react": "^3.0.0",
- "@storybook/csf-plugin": "10.2.8",
- "@storybook/icons": "^2.0.1",
- "@storybook/react-dom-shim": "10.2.8",
- "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.2.8"
- }
- },
- "node_modules/@storybook/addon-onboarding": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/addon-onboarding/-/addon-onboarding-10.2.8.tgz",
- "integrity": "sha512-/+TD055ZDmM325RYrDKqle51P1iT3GiFyDrcCYNOGTUEp3lAu/qplgOC0xMZudiv2y4ExlNYD26lJoGSTNHfHg==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/storybook"
- },
- "peerDependencies": {
- "storybook": "^10.2.8"
- }
- },
- "node_modules/@storybook/addon-vitest": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.2.8.tgz",
- "integrity": "sha512-D+9QWBqURAlS9+js9fvBToe7RzhIM9/ep/q8lunpVvcTkbOxh5++cYWBD8PVqmzZ+U432w9h2UwPuxPsWD/loQ==",
- "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.2.8",
- "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.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.8.tgz",
- "integrity": "sha512-+6/Lwi7W0YIbzHDh798GPp0IHUYDwp0yv0Y1eVNK/StZD0tnv4/1C28NKyP+O7JOsFsuWI1qHiDhw8kNURugZw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@storybook/csf-plugin": "10.2.8",
- "ts-dedent": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/storybook"
- },
- "peerDependencies": {
- "storybook": "^10.2.8",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/@storybook/csf-plugin": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.8.tgz",
- "integrity": "sha512-kKkLYhRXb33YtIPdavD2DU25sb14sqPYdcQFpyqu4TaD9truPPqW8P5PLTUgERydt/eRvRlnhauPHavU1kjsnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "unplugin": "^2.3.5"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/storybook"
- },
- "peerDependencies": {
- "esbuild": "*",
- "rollup": "*",
- "storybook": "^10.2.8",
- "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.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.8.tgz",
- "integrity": "sha512-nMFqQFUXq6Zg2O5SeuomyWnrIx61QfpNQMrfor8eCEzHrWNnXrrvVsz2RnHIgXN8RVyaWGDPh1srAECu/kDHXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@storybook/global": "^5.0.0",
- "@storybook/react-dom-shim": "10.2.8",
- "react-docgen": "^8.0.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.2.8",
- "typescript": ">= 4.9.x"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@storybook/react-dom-shim": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.8.tgz",
- "integrity": "sha512-Xde9X3VszFV1pTXfc2ZFM89XOCGRxJD8MUIzDwkcT9xaki5a+8srs/fsXj75fMY6gMYfcL5lNRZvCqg37HOmcQ==",
- "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.2.8"
+ "@rushstack/terminal": "0.21.0",
+ "@types/argparse": "1.0.38",
+ "argparse": "~1.0.9",
+ "string-argv": "~0.3.1"
}
},
- "node_modules/@storybook/react-vite": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.8.tgz",
- "integrity": "sha512-x5kmw+TPhxkQV84n4e9X0q6/rA5T8V2QQFolMuN+U93q1HX1r+GZ6g/nXaaq9ox168PhHUJZQnn+LzSQKGCMBA==",
+ "node_modules/@rushstack/ts-command-line/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3",
- "@rollup/pluginutils": "^5.0.2",
- "@storybook/builder-vite": "10.2.8",
- "@storybook/react": "10.2.8",
- "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.2.8",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ "sprintf-js": "~1.0.2"
}
},
+ "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/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
@@ -2840,13 +2080,6 @@
"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/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -2861,13 +2094,6 @@
"dev": true,
"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/node": {
"version": "24.10.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
@@ -2898,13 +2124,6 @@
"@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/@typescript-eslint/eslint-plugin": {
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
@@ -3260,74 +2479,6 @@
}
}
},
- "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.0.18",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
@@ -3896,19 +3047,6 @@
"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-types-flow": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -4126,22 +3264,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "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/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -4233,23 +3355,6 @@
],
"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",
@@ -4267,16 +3372,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "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": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -4315,30 +3410,6 @@
"node": ">= 6"
}
},
- "node_modules/chromatic": {
- "version": "13.3.5",
- "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-13.3.5.tgz",
- "integrity": "sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "chroma": "dist/bin.js",
- "chromatic": "dist/bin.js",
- "chromatic-cli": "dist/bin.js"
- },
- "peerDependencies": {
- "@chromatic-com/cypress": "^0.*.* || ^1.0.0",
- "@chromatic-com/playwright": "^0.*.* || ^1.0.0"
- },
- "peerDependenciesMeta": {
- "@chromatic-com/cypress": {
- "optional": true
- },
- "@chromatic-com/playwright": {
- "optional": true
- }
- }
- },
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
@@ -4825,16 +3896,6 @@
"dev": true,
"license": "MIT"
},
- "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-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -4842,36 +3903,6 @@
"dev": true,
"license": "MIT"
},
- "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/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -4890,19 +3921,6 @@
"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/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -4955,19 +3973,6 @@
"dev": true,
"license": "MIT"
},
- "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",
@@ -5018,16 +4023,6 @@
"dev": true,
"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/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
@@ -5230,48 +4225,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/esbuild": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
- "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.3",
- "@esbuild/android-arm": "0.27.3",
- "@esbuild/android-arm64": "0.27.3",
- "@esbuild/android-x64": "0.27.3",
- "@esbuild/darwin-arm64": "0.27.3",
- "@esbuild/darwin-x64": "0.27.3",
- "@esbuild/freebsd-arm64": "0.27.3",
- "@esbuild/freebsd-x64": "0.27.3",
- "@esbuild/linux-arm": "0.27.3",
- "@esbuild/linux-arm64": "0.27.3",
- "@esbuild/linux-ia32": "0.27.3",
- "@esbuild/linux-loong64": "0.27.3",
- "@esbuild/linux-mips64el": "0.27.3",
- "@esbuild/linux-ppc64": "0.27.3",
- "@esbuild/linux-riscv64": "0.27.3",
- "@esbuild/linux-s390x": "0.27.3",
- "@esbuild/linux-x64": "0.27.3",
- "@esbuild/netbsd-arm64": "0.27.3",
- "@esbuild/netbsd-x64": "0.27.3",
- "@esbuild/openbsd-arm64": "0.27.3",
- "@esbuild/openbsd-x64": "0.27.3",
- "@esbuild/openharmony-arm64": "0.27.3",
- "@esbuild/sunos-x64": "0.27.3",
- "@esbuild/win32-arm64": "0.27.3",
- "@esbuild/win32-ia32": "0.27.3",
- "@esbuild/win32-x64": "0.27.3"
- }
- },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -5425,20 +4378,6 @@
"eslint": ">=8.40"
}
},
- "node_modules/eslint-plugin-storybook": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.2.8.tgz",
- "integrity": "sha512-BtysXrg1RoYT3DIrCc+svZ0+L3mbWsu7suxTLGrihBY5HfWHkJge+qjlBBR1Nm2ZMslfuFS5K0NUWbWCJRu6kg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/utils": "^8.48.0"
- },
- "peerDependencies": {
- "eslint": ">=8",
- "storybook": "^10.2.8"
- }
- },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -5511,20 +4450,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
@@ -5718,16 +4643,6 @@
"node": ">=16.0.0"
}
},
- "node_modules/filesize": {
- "version": "10.1.6",
- "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
- "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">= 10.4.0"
- }
- },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -5995,93 +4910,33 @@
"git-raw-commits": "cli.mjs"
},
"engines": {
- "node": ">=16"
- }
- },
- "node_modules/git-raw-commits/node_modules/meow": {
- "version": "12.1.1",
- "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
- "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=16.10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/glob": {
- "version": "13.0.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.3.tgz",
- "integrity": "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minimatch": "^10.2.0",
- "minipass": "^7.1.2",
- "path-scurry": "^2.0.0"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/glob/node_modules/balanced-match": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz",
- "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "jackspeak": "^4.2.3"
- },
- "engines": {
- "node": "20 || >=22"
+ "node": ">=16"
}
},
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
- "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
+ "node_modules/git-raw-commits/node_modules/meow": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
+ "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
"engines": {
- "node": "20 || >=22"
+ "node": ">=16.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/glob/node_modules/minimatch": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz",
- "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==",
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
- "license": "BlueOak-1.0.0",
+ "license": "ISC",
"dependencies": {
- "brace-expansion": "^5.0.2"
+ "is-glob": "^4.0.3"
},
"engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=10.13.0"
}
},
"node_modules/global-directory": {
@@ -6593,22 +5448,6 @@
"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",
@@ -6684,25 +5523,6 @@
"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-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
@@ -6931,22 +5751,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-wsl": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
- "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
- "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",
@@ -7000,22 +5804,6 @@
"node": ">=8"
}
},
- "node_modules/jackspeak": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
- "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^9.0.0"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@@ -7401,13 +6189,6 @@
"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": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -7580,16 +6361,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
@@ -7843,25 +6614,6 @@
"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",
@@ -8009,33 +6761,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/path-scurry": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
- "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -8043,16 +6768,6 @@
"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",
@@ -8459,38 +7174,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-docgen": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.2.tgz",
- "integrity": "sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==",
- "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",
@@ -8557,23 +7240,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "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/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -8776,19 +7442,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/run-applescript": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
- "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -9178,55 +7831,6 @@
"node": ">= 0.4"
}
},
- "node_modules/storybook": {
- "version": "10.2.8",
- "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.8.tgz",
- "integrity": "sha512-885uSIn8NQw2ZG7vy84K45lHCOSyz1DVsDV8pHiHQj3J0riCuWLNeO50lK9z98zE8kjhgTtxAAkMTy5nkmNRKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@storybook/global": "^5.0.0",
- "@storybook/icons": "^2.0.1",
- "@testing-library/jest-dom": "^6.6.3",
- "@testing-library/user-event": "^14.6.1",
- "@vitest/expect": "3.2.4",
- "@vitest/spy": "3.2.4",
- "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/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -9357,29 +7961,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "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": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -9541,13 +8122,6 @@
"node": ">=0.8"
}
},
- "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",
@@ -9592,16 +8166,6 @@
"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.23",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz",
@@ -9684,16 +8248,6 @@
"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-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -9701,28 +8255,6 @@
"dev": true,
"license": "Apache-2.0"
},
- "node_modules/tsconfig-paths": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
- "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json5": "^2.2.2",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
- "license": "0BSD"
- },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -9905,22 +8437,6 @@
"node": ">= 10.0.0"
}
},
- "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/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -9962,16 +8478,6 @@
"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",
@@ -10716,13 +9222,6 @@
"node": ">=20"
}
},
- "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/whatwg-mimetype": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
@@ -10958,22 +9457,6 @@
}
}
},
- "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",
diff --git a/package.json b/package.json
index e957bb3..61c8274 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,6 @@
"lint": "eslint .",
"preview": "vite preview",
"prepare": "husky",
- "storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build",
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:coverage": "vitest run --coverage"
@@ -20,15 +18,9 @@
"react-dom": "^19.2.0"
},
"devDependencies": {
- "@chromatic-com/storybook": "^5.0.1",
"@commitlint/cli": "^20.4.1",
"@commitlint/config-conventional": "^20.4.1",
"@eslint/js": "^9.39.1",
- "@storybook/addon-a11y": "^10.2.8",
- "@storybook/addon-docs": "^10.2.8",
- "@storybook/addon-onboarding": "^10.2.8",
- "@storybook/addon-vitest": "^10.2.8",
- "@storybook/react-vite": "^10.2.8",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
@@ -45,7 +37,6 @@
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
- "eslint-plugin-storybook": "^10.2.8",
"globals": "^16.5.0",
"husky": "^9.1.7",
"jsdom": "^28.0.0",
@@ -53,7 +44,6 @@
"playwright": "^1.58.2",
"postcss": "^8.4.49",
"prettier": "^3.8.1",
- "storybook": "^10.2.8",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^3.4.17",
"typescript": "~5.7.0",
diff --git a/src/Playground.tsx b/src/Playground.tsx
index 98db492..a83c306 100644
--- a/src/Playground.tsx
+++ b/src/Playground.tsx
@@ -7,6 +7,12 @@ import { Accordion } from "@/components/Accordion";
import { useTheme } from "@/theme";
import { Badge } from "./components/Badge";
import { Avatar } from "@/components/Avatar";
+import { Skeleton } from "@/components/Skeleton";
+import Checkbox from "@/components/Checkbox";
+import { ScrollArea, ScrollElement } from "@/components/ScrollArea";
+import RadioGroup from "@/components/RadioGroup";
+import reactLogo from "@/assets/react.svg";
+import type { ThemeOverride } from "@/theme";
function ThemeToggle() {
const { toggleColorMode, theme } = useTheme();
@@ -19,8 +25,120 @@ function ThemeToggle() {
);
}
+const presetThemes: {
+ name: string;
+ override: ThemeOverride | null;
+ color: string;
+}[] = [
+ { name: "Default (Pink)", override: null, color: "#ec4899" },
+ {
+ name: "Ocean",
+ color: "#3b82f6",
+ override: {
+ name: "ocean",
+ palette: {
+ primary: {
+ 50: "#eff6ff",
+ 100: "#dbeafe",
+ 200: "#bfdbfe",
+ 300: "#93c5fd",
+ 400: "#60a5fa",
+ 500: "#3b82f6",
+ 600: "#2563eb",
+ 700: "#1d4ed8",
+ 800: "#1e40af",
+ 900: "#1e3a8a",
+ },
+ },
+ tokens: {
+ ring: "#3b82f6",
+ accent: "#2563eb",
+ accentHover: "#1d4ed8",
+ },
+ },
+ },
+ {
+ name: "Forest",
+ color: "#10b981",
+ override: {
+ name: "forest",
+ palette: {
+ primary: {
+ 50: "#ecfdf5",
+ 100: "#d1fae5",
+ 200: "#a7f3d0",
+ 300: "#6ee7b7",
+ 400: "#34d399",
+ 500: "#10b981",
+ 600: "#059669",
+ 700: "#047857",
+ 800: "#065f46",
+ 900: "#064e3b",
+ },
+ },
+ tokens: {
+ ring: "#10b981",
+ accent: "#059669",
+ accentHover: "#047857",
+ },
+ },
+ },
+ {
+ name: "Sunset",
+ color: "#f97316",
+ override: {
+ name: "sunset",
+ palette: {
+ primary: {
+ 50: "#fff7ed",
+ 100: "#ffedd5",
+ 200: "#fed7aa",
+ 300: "#fdba74",
+ 400: "#fb923c",
+ 500: "#f97316",
+ 600: "#ea580c",
+ 700: "#c2410c",
+ 800: "#9a3412",
+ 900: "#7c2d12",
+ },
+ },
+ tokens: {
+ ring: "#f97316",
+ accent: "#ea580c",
+ accentHover: "#c2410c",
+ },
+ },
+ },
+ {
+ name: "Amethyst",
+ color: "#a855f7",
+ override: {
+ name: "amethyst",
+ palette: {
+ primary: {
+ 50: "#faf5ff",
+ 100: "#f3e8ff",
+ 200: "#e9d5ff",
+ 300: "#d8b4fe",
+ 400: "#c084fc",
+ 500: "#a855f7",
+ 600: "#9333ea",
+ 700: "#7e22ce",
+ 800: "#6b21a8",
+ 900: "#581c87",
+ },
+ },
+ tokens: {
+ ring: "#a855f7",
+ accent: "#9333ea",
+ accentHover: "#7e22ce",
+ },
+ },
+ },
+];
+
export default function Playground() {
- const { theme } = useTheme();
+ const { theme, overrideTheme, resetTheme } = useTheme();
const [isModalOpen, setIsModalOpen] = useState(false);
const [inputValue, setInputValue] = useState("");
const [email, setEmail] = useState("");
@@ -47,6 +165,32 @@ export default function Playground() {
"--typography-color": theme.tokens.foregroundMuted,
} as React.CSSProperties;
+ const [isSimulatingData, setIsSimulatingData] = useState(true);
+ const [radioValue, setRadioValue] = useState("option-1");
+
+ const [checkedItems, setCheckedItems] = useState({
+ item1: false,
+ item2: false,
+ item3: false,
+ });
+
+ const allChecked = Object.values(checkedItems).every(Boolean);
+ const isIndeterminate =
+ Object.values(checkedItems).some(Boolean) && !allChecked;
+
+ const handleToggleAll = (checked: boolean) => {
+ setCheckedItems({
+ item1: checked,
+ item2: checked,
+ item3: checked,
+ });
+ };
+
+ const handleToggleItem =
+ (item: keyof typeof checkedItems) => (checked: boolean) => {
+ setCheckedItems((prev) => ({ ...prev, [item]: checked }));
+ };
+
return (
+ {/* Theme Picker Section */}
+
+
+
+
+ Dynamic Brand Theming
+
+
+ Apply seamless theme overrides and instantly see UI scale
+ changes.
+
+
+
+
+
+ {presetThemes.map((preset) => (
+
{
+ if (preset.override) {
+ overrideTheme(preset.override);
+ } else {
+ resetTheme();
+ }
+ }}
+ className="group relative flex items-center justify-center p-[2px] rounded-full hover:scale-105 active:scale-95 transition-all duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ring-color)]"
+ title={preset.name}
+ >
+
+
+ {preset.name}
+
+
+ ))}
+
+
+
{/* Buttons Section */}
Secondary
+ Success
+ Warning
+ Danger
+ Info
Outline
Ghost
- Danger
@@ -203,7 +397,9 @@ export default function Playground() {
Variants
- Primary
+
+ Primary{" "}
+
Secondary
Success
Danger
@@ -211,6 +407,7 @@ export default function Playground() {
Info
Light
Dark
+ Outline
@@ -333,7 +530,7 @@ export default function Playground() {
Initials & Fallbacks
-
Display 1
+
+ Display 1
+
Display 2
Heading 1
Heading 2
@@ -640,7 +839,7 @@ export default function Playground() {
>
-
+
+ {/* Skeleton Section */}
+
+
+
+ Skeleton Placeholders
+
+
+ Simplified loading placeholders for various content shapes
+
+
+
+
+ {/* Shapes */}
+
+
+ {/* Animations */}
+
+
+ Animations
+
+
+
+
+ Wave (Default)
+
+
+
+
+
+ Pulse
+
+
+
+
+
+ None
+
+
+
+
+
+
+ {/* Custom Size */}
+
+
+ Typography Variants
+
+
+
+
+
+
+
+
+
+ {/* Real Data Simulation */}
+
+
+
+
+ Real Data simulation
+
+
+ Toggle to see how skeletons transition to real content
+
+
+
setIsSimulatingData(!isSimulatingData)}
+ variant={isSimulatingData ? "outline" : "primary"}
+ >
+ {isSimulatingData ? "Load Data" : "Show Skeletons"}
+
+
+
+
+ {[1, 2, 3].map((i) => (
+
+
+ {isSimulatingData ? (
+
+ ) : (
+
+ )}
+
+ {isSimulatingData ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+ {
+ ["Alice Freeman", "Bob Smith", "Charlie Davis"][
+ i - 1
+ ]
+ }
+
+
+ {
+ [
+ "Senior Designer",
+ "Software Engineer",
+ "Product Manager",
+ ][i - 1]
+ }
+
+ >
+ )}
+
+
+
+ {isSimulatingData ? (
+
+
+
+
+
+ ) : (
+
+ {
+ [
+ "Creative designer with a passion for building beautiful UI components.",
+ "Backend developer focused on scalable architecture and performance.",
+ "Strategic thinker driving product vision and execution.",
+ ][i - 1]
+ }
+
+ )}
+
+
+ ))}
+
+
+
+ {/* Checkbox Section */}
+
+
+
+ Checkboxes
+
+
+ Selection controls with support for labels, descriptions, and
+ interactive indeterminate states
+
+
+
+
+ {/* Interactive Playground */}
+
+
+ {/* Sizes & States */}
+
+
+
+
+ Available Sizes
+
+
+
+
+
+
+ Visual Feedback
+
+
+
+
+
+
+
+
+
+
+ Native States
+
+
+
+
+
+
+
+
+
+ Combine labels, descriptions, and icons to create clear and
+ actionable interfaces.
+
+
+
+
+
+
+
+ {/* RadioGroup Section */}
+
+
+
+ Radio Groups
+
+
+ Single selection controls with support for horizontal/vertical
+ layouts, descriptions, and validation.
+
+
+
+
+ {/* Basic & Orientations */}
+
+
+
+ Vertical Layout (Default)
+
+
+
+ Selected:{" "}
+
+ {radioValue}
+
+
+
+
+
+
+ Horizontal Layout
+
+
+
+
+
+ {/* Validation & States */}
+
+
+
+ Validation & States
+
+
+
+
+
+
+
+
+
+
+ {/* Card Variant */}
+
+
+
+ Card Variant (New)
+
+
+
+
+ React Pay
+
+
+ ),
+ },
+ ]}
+ />
+
+
+
+
+
+
+ {/* ScrollArea Section */}
+
+
+
+ Scroll Area
+
+
+ Customizable scrolling container with support for custom scrollbars
+ and visibility controls
+
+
+
+
+ {/* Vertical Scroll */}
+
+
+ Vertical Scrolling
+
+
+
+
+ {[...Array(12)].map((_, i) => (
+
+
+ Scroll Item {i + 1}
+
+
+ This is a sample item inside the vertical scroll area.
+ It demonstrates how the container handles overflowing
+ vertical content.
+
+
+ ))}
+
+
+
+
+
+ {/* Horizontal Scroll */}
+
+
+ Horizontal Scrolling
+
+
+
+
+ {[...Array(8)].map((_, i) => (
+
+
+ #{i + 1}
+
+
+ Horizontal Item {i + 1}
+
+
+ This card scrolls horizontally.
+
+
+ ))}
+
+
+
+
+
+
+
{/* Modal */}
+
setIsModalOpen(false)}
diff --git a/src/assets/Icons/CheckIcon.tsx b/src/assets/Icons/CheckIcon.tsx
new file mode 100644
index 0000000..4c7c107
--- /dev/null
+++ b/src/assets/Icons/CheckIcon.tsx
@@ -0,0 +1,20 @@
+import { cn } from "@/utils";
+
+export const CheckIcon = ({ size }: { size: string }) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/assets/Icons/InterminateIcon.tsx b/src/assets/Icons/InterminateIcon.tsx
new file mode 100644
index 0000000..90baad9
--- /dev/null
+++ b/src/assets/Icons/InterminateIcon.tsx
@@ -0,0 +1,20 @@
+import { cn } from "@/utils/cn";
+
+export const InterminateIcon = ({ size }: { size: string }) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Accordion/Accordion.stories.tsx b/src/components/Accordion/Accordion.stories.tsx
deleted file mode 100644
index 99501ed..0000000
--- a/src/components/Accordion/Accordion.stories.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Accordion } from "./Accordion";
-
-const meta: Meta = {
- title: "Components/Accordion",
- component: Accordion,
- parameters: {
- layout: "centered",
- },
- tags: ["autodocs"],
- argTypes: {
- isDefaultOpen: {
- control: "boolean",
- },
- isDisabled: {
- control: "boolean",
- },
- },
- decorators: [
- (Story) => (
-
-
-
- ),
- ],
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- args: {
- title: "What is a UI Library?",
- children:
- "A UI library is a collection of reusable components that help developers build consistent and polished user interfaces quickly.",
- },
-};
-
-export const WithSubtitle: Story = {
- args: {
- title: "Getting Started",
- subtitle: "Learn how to install and use the library",
- children:
- "Install the package using npm or yarn, then import the components you need into your project. Each component is fully typed and supports theming out of the box.",
- },
-};
-
-export const OpenByDefault: Story = {
- args: {
- title: "Already Expanded",
- subtitle: "This accordion starts open",
- isDefaultOpen: true,
- children:
- "This content is visible immediately because isDefaultOpen is set to true.",
- },
-};
-
-export const Disabled: Story = {
- args: {
- title: "Disabled Accordion",
- subtitle: "This cannot be toggled",
- isDisabled: true,
- children: "You should not be able to see this content.",
- },
-};
-
-export const WithRichContent: Story = {
- args: {
- title: "Shipping Information",
- subtitle: "Delivery options and timelines",
- isDefaultOpen: true,
- children: (
-
-
We offer the following shipping options:
-
-
- Standard — 5-7 business days
-
-
- Express — 2-3 business days
-
-
- Overnight — Next business day
-
-
-
Free shipping on orders over $50.
-
- ),
- },
-};
-
-export const AccordionGroup: Story = {
- render: () => (
-
-
- Welcome to our application. This section covers the basics you need to
- get started.
-
-
- Explore the powerful features built into every component, including
- theming, accessibility, and responsive design.
-
-
- Find answers to frequently asked questions about installation,
- configuration, and customization.
-
-
- ),
-};
-
-export const FAQ: Story = {
- render: () => (
-
-
- Run npm install @awesome-ui/library in your project
- directory.
-
-
- Yes! Wrap your app in a ThemeProvider and toggle between light and dark
- themes.
-
-
- All components follow WAI-ARIA guidelines and support keyboard
- navigation.
-
-
- Absolutely. Every component accepts a className prop and uses CSS
- variables for theming, making customization straightforward.
-
-
- ),
-};
diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx
index be4ac9a..ffc4bf1 100644
--- a/src/components/Accordion/Accordion.tsx
+++ b/src/components/Accordion/Accordion.tsx
@@ -14,7 +14,7 @@ const Accordion = forwardRef(
children,
isDefaultOpen = false,
isDisabled = false,
- variant = "borderless",
+ variant = "border",
},
ref,
) => {
@@ -57,30 +57,30 @@ const Accordion = forwardRef(
className={cn(
"flex cursor-pointer items-center justify-between gap-4",
"text-left select-none",
- "transition-all duration-200",
+ "transition-all duration-300 ease-out",
"text-[var(--accordion-text)]",
!isBorderless && [
"px-5 py-4",
- "hover:bg-[var(--accordion-bg-hover)] rounded-[var(--accordion-radius)]",
+ "hover:bg-[var(--accordion-bg-hover)] rounded-[var(--accordion-radius)] active:scale-[0.99]",
],
isBorderless && [
"px-0 py-3",
- "hover:bg-[var(--accordion-bg-hover)]/40",
+ "hover:opacity-80 active:scale-[0.99] origin-left",
],
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accordion-border)] focus-visible:ring-offset-2",
)}
>
-
+
{title && (
-
+
{title}
)}
{subtitle && (
-
+
{subtitle}
)}
@@ -98,11 +98,11 @@ const Accordion = forwardRef(
{children}
diff --git a/src/components/Accordion/Accordion.types.tsx b/src/components/Accordion/Accordion.types.tsx
index 5425215..e3241fc 100644
--- a/src/components/Accordion/Accordion.types.tsx
+++ b/src/components/Accordion/Accordion.types.tsx
@@ -1,10 +1,21 @@
+/**
+ * Props for the Accordion component.
+ */
export interface AccordionProps {
+ /** Unique identifier for the accordion. */
id?: string;
+ /** Custom CSS class for the root element. */
className?: string;
+ /** The primary content displayed in the accordion header. */
title?: React.ReactNode;
+ /** Optional secondary content displayed next to or below the title. */
subtitle?: React.ReactNode;
+ /** The content to be displayed when the accordion is expanded. */
children?: React.ReactNode;
+ /** Whether the accordion should be open by default (uncontrolled). */
isDefaultOpen?: boolean;
+ /** Whether the accordion is disabled and cannot be toggled. */
isDisabled?: boolean;
+ /** The visual variant of the accordion. @default "default" */
variant?: "default" | "borderless";
}
diff --git a/src/components/Avatar/Avatar.stories.tsx b/src/components/Avatar/Avatar.stories.tsx
deleted file mode 100644
index e1779d5..0000000
--- a/src/components/Avatar/Avatar.stories.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import Avatar from "./Avatar";
-import { Badge } from "../Badge/Badge";
-
-const meta: Meta
= {
- title: "Components/Avatar",
- component: Avatar,
- parameters: {
- layout: "centered",
- },
- tags: ["autodocs"],
- argTypes: {
- size: {
- control: "select",
- options: ["small", "medium", "large"],
- },
- shape: {
- control: "select",
- options: ["circle", "square"],
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Image: Story = {
- args: {
- src: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
- alt: "User Avatar",
- },
-};
-
-export const Initials: Story = {
- args: {
- displayName: "Abilash",
- size: "large",
- },
-};
-
-export const Square: Story = {
- args: {
- shape: "square",
- src: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80",
- },
-};
-
-export const Sizes: Story = {
- render: () => (
-
- ),
-};
-
-export const WithBadge: Story = {
- args: {
- src: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-1.2.1",
- badge: (
-
- {""}
-
- ),
- },
-};
-
-export const Fallback: Story = {
- args: {
- displayName: "John Doe",
- src: "https://invalid-url.com/image.jpg", // Force error
- },
-};
diff --git a/src/components/Avatar/Avatar.test.tsx b/src/components/Avatar/Avatar.test.tsx
index a34453d..c797ca4 100644
--- a/src/components/Avatar/Avatar.test.tsx
+++ b/src/components/Avatar/Avatar.test.tsx
@@ -75,4 +75,9 @@ describe("Avatar", () => {
expect(screen.getByText("EU")).toBeInTheDocument();
});
+
+ it("shows skeleton when loading", () => {
+ render( );
+ expect(screen.getByRole("status")).toBeInTheDocument();
+ });
});
diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx
index edd37ee..04bc103 100644
--- a/src/components/Avatar/Avatar.tsx
+++ b/src/components/Avatar/Avatar.tsx
@@ -2,6 +2,7 @@ import { forwardRef, useState } from "react";
import { cn } from "@/utils";
import { useTheme } from "@/theme";
import type { AvatarProps } from "./Avatar.types";
+import { Skeleton } from "../Skeleton";
const Avatar = forwardRef((props, ref) => {
const {
@@ -15,6 +16,7 @@ const Avatar = forwardRef((props, ref) => {
style,
badge,
onBadgeClick,
+ isLoading,
} = props;
const [hasError, setHasError] = useState(false);
const { theme } = useTheme();
@@ -55,36 +57,44 @@ const Avatar = forwardRef((props, ref) => {
)}
style={style}
>
-
- {src && !fallBack ? (
-
setHasError(true)}
- aria-label={displayName || "Avatar"}
- />
- ) : (
-
- {displayName ? getInitials(displayName) : "?"}
-
- )}
-
+ {isLoading ? (
+
+ ) : (
+
+ {src && !fallBack ? (
+
setHasError(true)}
+ aria-label={displayName || "Avatar"}
+ />
+ ) : (
+
+ {displayName ? getInitials(displayName) : "?"}
+
+ )}
+
+ )}
{badge && (
void;
- // showTooltip?: boolean;
- // tooltipContent?: string;
+ /** Whether the avatar is in a loading state. */
+ isLoading?: boolean;
}
diff --git a/src/components/Badge/Badge.stories.tsx b/src/components/Badge/Badge.stories.tsx
deleted file mode 100644
index b077806..0000000
--- a/src/components/Badge/Badge.stories.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Badge } from "./Badge";
-
-const meta: Meta
= {
- title: "Components/Badge",
- component: Badge,
- parameters: {
- layout: "centered",
- },
- tags: ["autodocs"],
- argTypes: {
- variant: {
- control: "select",
- options: [
- "primary",
- "secondary",
- "success",
- "danger",
- "warning",
- "info",
- "light",
- "dark",
- ],
- },
- size: {
- control: "select",
- options: ["small", "medium", "large"],
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Primary: Story = {
- args: {
- children: "Primary",
- variant: "primary",
- },
-};
-
-export const Secondary: Story = {
- args: {
- children: "Secondary",
- variant: "secondary",
- },
-};
-
-export const Success: Story = {
- args: {
- children: "Success",
- variant: "success",
- },
-};
-
-export const Danger: Story = {
- args: {
- children: "Danger",
- variant: "danger",
- },
-};
-
-export const Warning: Story = {
- args: {
- children: "Warning",
- variant: "warning",
- },
-};
-
-export const Info: Story = {
- args: {
- children: "Info",
- variant: "info",
- },
-};
-
-export const Light: Story = {
- args: {
- children: "Light",
- variant: "light",
- },
-};
-
-export const Dark: Story = {
- args: {
- children: "Dark",
- variant: "dark",
- },
-};
-
-export const Small: Story = {
- args: {
- children: "Small",
- size: "small",
- },
-};
-
-export const Medium: Story = {
- args: {
- children: "Medium",
- size: "medium",
- },
-};
-
-export const Large: Story = {
- args: {
- children: "Large",
- size: "large",
- },
-};
-
-export const AllVariants: Story = {
- render: () => (
-
-
- Primary
- Secondary
- Success
- Danger
- Warning
- Info
- Light
- Dark
-
-
- Small
- Medium
- Large
-
-
- ),
-};
diff --git a/src/components/Badge/Badge.test.tsx b/src/components/Badge/Badge.test.tsx
index eee711b..c094523 100644
--- a/src/components/Badge/Badge.test.tsx
+++ b/src/components/Badge/Badge.test.tsx
@@ -55,4 +55,9 @@ describe("Badge", () => {
const { container } = render(Style );
expect(container.firstChild).toHaveStyle({ color: "rgb(255, 0, 0)" });
});
+
+ it("shows skeleton when loading", () => {
+ render(Loading );
+ expect(screen.getByRole("status")).toBeInTheDocument();
+ });
});
diff --git a/src/components/Badge/Badge.tsx b/src/components/Badge/Badge.tsx
index 6be7bb0..0166157 100644
--- a/src/components/Badge/Badge.tsx
+++ b/src/components/Badge/Badge.tsx
@@ -2,10 +2,18 @@ import { forwardRef } from "react";
import { cn } from "@/utils";
import { useTheme } from "@/theme";
import type { BadgeProps } from "./Badge.types";
+import { Skeleton } from "../Skeleton";
export const Badge = forwardRef(
(
- { variant = "primary", size = "medium", children, className, style },
+ {
+ variant = "primary",
+ size = "medium",
+ children,
+ className,
+ style,
+ isLoading,
+ },
ref,
) => {
const { theme } = useTheme();
@@ -21,6 +29,8 @@ export const Badge = forwardRef(
info: "bg-[var(--badge-bg)] text-[var(--badge-text)]",
light: "bg-[var(--badge-bg)] text-[var(--badge-text)]",
dark: "bg-[var(--badge-bg)] text-[var(--badge-text)]",
+ outline:
+ "bg-[var(--badge-bg)] text-[var(--badge-text)] border border-[var(--badge-border)]",
};
const sizeStyles = {
@@ -34,14 +44,23 @@ export const Badge = forwardRef(
"--badge-text": theme.palette.primary[700],
};
+ const SKELETON_SIZE_MAP: Record<
+ NonNullable,
+ { height: number; width: number }
+ > = {
+ small: { height: 20, width: 48 },
+ medium: { height: 24, width: 56 },
+ large: { height: 28, width: 64 },
+ };
+
switch (variant) {
case "primary":
themeStyles["--badge-bg"] = theme.palette.primary[100];
themeStyles["--badge-text"] = theme.palette.primary[700];
break;
case "secondary":
- themeStyles["--badge-bg"] = theme.neutral[100];
- themeStyles["--badge-text"] = theme.neutral[700];
+ themeStyles["--badge-bg"] = theme.neutral[200];
+ themeStyles["--badge-text"] = theme.neutral[800];
break;
case "success":
themeStyles["--badge-bg"] = theme.palette.success[100];
@@ -67,6 +86,25 @@ export const Badge = forwardRef(
themeStyles["--badge-bg"] = theme.tokens.foreground;
themeStyles["--badge-text"] = theme.tokens.background;
break;
+ case "outline":
+ themeStyles["--badge-bg"] = "transparent";
+ themeStyles["--badge-text"] = theme.tokens.foreground;
+ themeStyles["--badge-border"] = theme.tokens.foreground;
+ break;
+ }
+
+ if (isLoading) {
+ const { height, width } = SKELETON_SIZE_MAP[size];
+
+ return (
+
+ );
}
return (
diff --git a/src/components/Badge/Badge.types.ts b/src/components/Badge/Badge.types.ts
index 44ef2ba..f532844 100644
--- a/src/components/Badge/Badge.types.ts
+++ b/src/components/Badge/Badge.types.ts
@@ -1,4 +1,8 @@
+/**
+ * Props for the Badge component.
+ */
export interface BadgeProps {
+ /** The visual variant of the badge. @default "primary" */
variant?:
| "primary"
| "secondary"
@@ -7,9 +11,17 @@ export interface BadgeProps {
| "warning"
| "info"
| "light"
- | "dark";
+ | "dark"
+ | "outline";
+
+ /** The visual size of the badge. @default "medium" */
size?: "small" | "medium" | "large";
+ /** The content to be displayed within the badge. */
children: React.ReactNode;
+ /** Custom CSS class for the root element. */
className?: string;
+ /** Inline styles for the root element. */
style?: React.CSSProperties;
+ /** Whether the badge is in a loading state. */
+ isLoading?: boolean;
}
diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx
deleted file mode 100644
index 6f3cebc..0000000
--- a/src/components/Button/Button.stories.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Button } from "./Button";
-import { DummyIcon } from "../../assets/Icons/Dummy";
-
-const meta: Meta = {
- title: "Components/Button",
- component: Button,
- parameters: {
- layout: "centered",
- },
- tags: ["autodocs"],
- argTypes: {
- variant: {
- control: "select",
- options: ["primary", "secondary", "outline", "ghost", "danger"],
- },
- size: {
- control: "select",
- options: ["sm", "md", "lg"],
- },
- isLoading: {
- control: "boolean",
- },
- disabled: {
- control: "boolean",
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Primary: Story = {
- args: {
- children: "Primary Button",
- variant: "primary",
- },
-};
-
-export const Secondary: Story = {
- args: {
- children: "Secondary Button",
- variant: "secondary",
- },
-};
-
-export const Outline: Story = {
- args: {
- children: "Outline Button",
- variant: "outline",
- },
-};
-
-export const Ghost: Story = {
- args: {
- children: "Ghost Button",
- variant: "ghost",
- },
-};
-
-export const Danger: Story = {
- args: {
- children: "Danger Button",
- variant: "danger",
- },
-};
-
-export const Small: Story = {
- args: {
- children: "Small Button",
- size: "sm",
- },
-};
-
-export const Medium: Story = {
- args: {
- children: "Medium Button",
- size: "md",
- },
-};
-
-export const Large: Story = {
- args: {
- children: "Large Button",
- size: "lg",
- },
-};
-
-export const Loading: Story = {
- args: {
- children: "Loading Button",
- isLoading: true,
- },
-};
-
-export const Disabled: Story = {
- args: {
- children: "Disabled Button",
- disabled: true,
- },
-};
-export const IconOnly: Story = {
- args: {
- children: ,
- size: "md",
- },
-};
-
-export const AllVariants: Story = {
- render: () => (
-
-
- Primary
- Secondary
- Outline
- Ghost
- Danger
-
-
- Small
- Medium
- Large
-
-
- ),
-};
diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx
index 0ab4178..2d4be8f 100644
--- a/src/components/Button/Button.test.tsx
+++ b/src/components/Button/Button.test.tsx
@@ -26,7 +26,7 @@ describe("Button", () => {
it("applies secondary variant correctly", () => {
render( );
const button = screen.getByRole("button");
- expect(button).toHaveClass("bg-[var(--btn-bg)]");
+ expect(button).toHaveClass("bg-[var(--btn-hover)]");
expect(button).toHaveClass("text-[var(--btn-text)]");
});
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index ea4501c..c503789 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -39,13 +39,18 @@ const Button = forwardRef(
primary:
"bg-[var(--btn-bg)] text-white hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)] shadow-sm",
secondary:
- "bg-[var(--btn-bg)] text-[var(--btn-text)] hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)]",
+ "bg-[var(--btn-bg)] text-[var(--btn-text)] bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)]",
outline:
"border-2 border-[var(--btn-border)] text-[var(--btn-text)] bg-transparent hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)]",
ghost:
- "text-[var(--btn-text)] hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)]",
+ "text-[var(--btn-text)] hover:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)]",
danger:
"bg-[var(--btn-bg)] text-white hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)] shadow-sm",
+ success:
+ "bg-[var(--btn-bg)] text-white hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)] shadow-sm",
+ warning:
+ "bg-[var(--btn-bg)] text-white hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)] shadow-sm",
+ info: "bg-[var(--btn-bg)] text-white hover:bg-[var(--btn-hover)] active:bg-[var(--btn-active)] focus-visible:ring-[var(--btn-ring)] shadow-sm",
};
const sizes = {
@@ -57,10 +62,10 @@ const Button = forwardRef(
const iconOnlyStyles =
!children && (prefixIcon || suffixIcon)
? {
- sm: "h-8 w-8 px-0 min-w-0",
- md: "h-10 w-10 px-0 min-w-0",
- lg: "h-12 w-12 px-0 min-w-0",
- }
+ sm: "h-8 w-8 px-0 min-w-0",
+ md: "h-10 w-10 px-0 min-w-0",
+ lg: "h-12 w-12 px-0 min-w-0",
+ }
: null;
const themeStyles: Record = {
@@ -111,6 +116,30 @@ const Button = forwardRef(
themeStyles["--btn-text"] = "#ffffff";
themeStyles["--btn-ring"] = theme.palette.error[500];
break;
+
+ case "success":
+ themeStyles["--btn-bg"] = theme.palette.success[600];
+ themeStyles["--btn-hover"] = theme.palette.success[700];
+ themeStyles["--btn-active"] = theme.palette.success[800];
+ themeStyles["--btn-text"] = "#ffffff";
+ themeStyles["--btn-ring"] = theme.palette.success[500];
+ break;
+
+ case "warning":
+ themeStyles["--btn-bg"] = theme.palette.warning[600];
+ themeStyles["--btn-hover"] = theme.palette.warning[700];
+ themeStyles["--btn-active"] = theme.palette.warning[800];
+ themeStyles["--btn-text"] = "#ffffff";
+ themeStyles["--btn-ring"] = theme.palette.warning[500];
+ break;
+
+ case "info":
+ themeStyles["--btn-bg"] = theme.palette.info[600];
+ themeStyles["--btn-hover"] = theme.palette.info[700];
+ themeStyles["--btn-active"] = theme.palette.info[800];
+ themeStyles["--btn-text"] = "#ffffff";
+ themeStyles["--btn-ring"] = theme.palette.info[500];
+ break;
}
const isIconOnly = !children && (prefixIcon || suffixIcon);
diff --git a/src/components/Button/Button.types.tsx b/src/components/Button/Button.types.tsx
index 8f1d33d..59384b6 100644
--- a/src/components/Button/Button.types.tsx
+++ b/src/components/Button/Button.types.tsx
@@ -1,34 +1,53 @@
import { type ButtonHTMLAttributes, type ReactNode } from "react";
+/**
+ * The visual variants available for the Button.
+ */
export type ButtonVariant =
| "primary"
| "secondary"
| "outline"
| "ghost"
- | "danger";
+ | "danger"
+ | "success"
+ | "warning"
+ | "info";
+
+/**
+ * The size variants available for the Button.
+ */
export type ButtonSize = "sm" | "md" | "lg";
+/**
+ * Props for the Button component.
+ * Extends all standard HTML button attributes.
+ */
export interface ButtonProps extends ButtonHTMLAttributes {
- // Content
+ /** The content to be displayed within the button. */
children?: ReactNode;
- // Visual variants
+ /** The visual variant of the button. @default "primary" */
variant?: ButtonVariant;
+ /** The visual size of the button. @default "md" */
size?: ButtonSize;
- // Icons
+ /** Optional icon to be displayed before the button text. */
prefixIcon?: ReactNode;
+ /** Optional icon to be displayed after the button text. */
suffixIcon?: ReactNode;
- // States
+ /** Whether the button is in a loading state. Mutually exclusive with `disabled` but provides visual feedback. */
isLoading?: boolean;
+ /** Whether the button is disabled and non-interactive. */
disabled?: boolean;
- // Interaction
+ /** Callback function triggered when the button is clicked. */
onClick?: (event: React.MouseEvent) => void;
- // Accessibility
+ /** Accessibility label for the button. Important for icon-only buttons. */
"aria-label"?: string;
+ /** ID of an element that describes this button. */
"aria-describedby"?: string;
+ /** Whether the button is in a pressed state (useful for toggle buttons). */
"aria-pressed"?: boolean;
}
diff --git a/src/components/Checkbox/Checkbox.constants.ts b/src/components/Checkbox/Checkbox.constants.ts
new file mode 100644
index 0000000..184a641
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.constants.ts
@@ -0,0 +1,33 @@
+import type { CheckboxProps } from "./Checkbox.types";
+
+export const labelSizeClasses = {
+ small: "text-[13px] font-medium leading-[1.4]",
+ medium: "text-[14px] font-medium leading-[1.4]",
+ large: "text-[16px] font-medium leading-[1.4]",
+};
+export const descriptionSizeClasses = {
+ small: "text-[12px] leading-[1.4]",
+ medium: "text-[13px] leading-[1.4]",
+ large: "text-[14px] leading-[1.4]",
+};
+
+export const sizeClasses = {
+ small: "h-4 w-4",
+ medium: "h-5 w-5",
+ large: "h-6 w-6",
+};
+
+export const iconSizeClasses = {
+ small: "w-2 h-2",
+ medium: "w-3 h-3",
+ large: "w-3.5 h-3.5",
+};
+
+export const SKELETON_SIZE_MAP: Record<
+ NonNullable,
+ { height: string; width: string }
+> = {
+ small: { height: "16px", width: "16px" },
+ medium: { height: "20px", width: "20px" },
+ large: { height: "24px", width: "24px" },
+};
diff --git a/src/components/Checkbox/Checkbox.test.tsx b/src/components/Checkbox/Checkbox.test.tsx
new file mode 100644
index 0000000..a1e79b7
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.test.tsx
@@ -0,0 +1,64 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { describe, it, expect, vi } from "vitest";
+import Checkbox from "./Checkbox";
+import { ThemeProvider } from "@/theme";
+
+const renderWithTheme = (ui: React.ReactElement) => {
+ return render({ui} );
+};
+
+describe("Checkbox", () => {
+ it("renders with a label", () => {
+ renderWithTheme( );
+ expect(screen.getByLabelText("Test Label")).toBeInTheDocument();
+ });
+
+ it("handles checked state change", () => {
+ const onChange = vi.fn();
+ renderWithTheme( );
+ const input = screen.getByRole("checkbox");
+
+ fireEvent.click(input);
+ expect(onChange).toHaveBeenCalledWith(true);
+
+ fireEvent.click(input);
+ expect(onChange).toHaveBeenCalledWith(false);
+ });
+
+ it("renders in indeterminate state", () => {
+ renderWithTheme( );
+ const input = screen.getByRole("checkbox") as HTMLInputElement;
+ expect(input.indeterminate).toBe(true);
+ });
+
+ it("is disabled when disabled prop is true", () => {
+ renderWithTheme( );
+ const input = screen.getByRole("checkbox");
+ expect(input).toBeDisabled();
+ });
+
+ it("shows error state", () => {
+ renderWithTheme(
+ ,
+ );
+ expect(screen.getByText("Error message")).toBeInTheDocument();
+ expect(screen.getByRole("checkbox")).toHaveAttribute(
+ "aria-invalid",
+ "true",
+ );
+ });
+
+ it("renders loading state", () => {
+ const { container } = renderWithTheme( );
+ expect(container.querySelector(".skeleton-root")).toBeInTheDocument();
+ });
+
+ it("associates description via aria-describedby", () => {
+ renderWithTheme(
+ ,
+ );
+ const input = screen.getByRole("checkbox");
+ const description = screen.getByText("This is a description");
+ expect(input).toHaveAttribute("aria-describedby", description.id);
+ });
+});
diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx
new file mode 100644
index 0000000..2233595
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.tsx
@@ -0,0 +1,166 @@
+import { forwardRef, useEffect, useRef, useImperativeHandle } from "react";
+import type { CheckboxProps } from "./Checkbox.types";
+import { useTheme } from "@/theme";
+import Skeleton from "../Skeleton";
+import { cn } from "@/utils/cn";
+import {
+ descriptionSizeClasses,
+ iconSizeClasses,
+ labelSizeClasses,
+ sizeClasses,
+ SKELETON_SIZE_MAP,
+} from "./Checkbox.constants";
+import { CheckIcon } from "@/assets/Icons/CheckIcon";
+import { InterminateIcon } from "@/assets/Icons/InterminateIcon";
+
+const Checkbox = forwardRef(
+ (
+ {
+ checked,
+ defaultChecked,
+ disabled,
+ required,
+ loading,
+ size = "medium",
+ className = "",
+ style,
+ onChange,
+ error,
+ description,
+ label,
+ id,
+ indeterminate,
+ ...props
+ },
+ ref,
+ ) => {
+ const { theme } = useTheme();
+ const internalRef = useRef(null);
+
+ useEffect(() => {
+ if (internalRef.current) {
+ internalRef.current.indeterminate = indeterminate ?? false;
+ }
+ }, [indeterminate]);
+
+ useImperativeHandle(ref, () => internalRef.current!);
+
+ if (loading) {
+ return (
+
+
+
+
+ {description && (
+
+ )}
+
+
+ );
+ }
+
+ const themeStyles = {
+ "--primary": theme.palette.primary[500],
+ "--error": theme.palette.error[500],
+ "--ring": theme.tokens.ring,
+ "--border-color": error ? theme.palette.error[500] : theme.tokens.border,
+ "--bg-color": theme.tokens.surface,
+ "--text-color": theme.tokens.foreground,
+ "--text-secondary": theme.tokens.foregroundMuted,
+ } as React.CSSProperties;
+
+ const describedBy = [description && `${id}-description`]
+ .filter(Boolean)
+ .join(" ");
+
+ const handleChange = (e: React.ChangeEvent) => {
+ onChange?.(e.target.checked);
+ };
+
+ return (
+
+
+
+
+ {/* Check Icon */}
+
+
+ {/* Indeterminate Icon */}
+
+
+
+ {/* Text Content */}
+ {(label || description) && (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ {description && (
+
+ {description}
+
+ )}
+
+ )}
+
+ );
+ },
+);
+
+Checkbox.displayName = "Checkbox";
+
+export default Checkbox;
diff --git a/src/components/Checkbox/Checkbox.types.ts b/src/components/Checkbox/Checkbox.types.ts
new file mode 100644
index 0000000..0a4b4b6
--- /dev/null
+++ b/src/components/Checkbox/Checkbox.types.ts
@@ -0,0 +1,25 @@
+import type { InputHTMLAttributes, ReactNode } from "react";
+
+/**
+ * Props for the Checkbox component.
+ * Extends all standard HTML input attributes while omitting conflicting ones.
+ */
+export interface CheckboxProps extends Omit<
+ InputHTMLAttributes,
+ "size" | "onChange"
+> {
+ /** The primary label displayed next to the checkbox. */
+ label?: ReactNode;
+ /** Optional detailed description text displayed below the label. */
+ description?: ReactNode;
+ /** Whether the checkbox should be displayed in an error state. */
+ error?: boolean;
+ /** Whether the checkbox is in a loading state. */
+ loading?: boolean;
+ /** The visual size of the checkbox. @default "medium" */
+ size?: "small" | "medium" | "large";
+ /** Whether the checkbox is in an indeterminate state (neither checked nor unchecked). */
+ indeterminate?: boolean;
+ /** Callback function triggered when the checked state changes. */
+ onChange?: (checked: boolean) => void;
+}
diff --git a/src/components/Checkbox/index.ts b/src/components/Checkbox/index.ts
new file mode 100644
index 0000000..dc638ad
--- /dev/null
+++ b/src/components/Checkbox/index.ts
@@ -0,0 +1,3 @@
+export { default } from "./Checkbox";
+export * from "./Checkbox.types";
+export * from "./Checkbox.constants";
diff --git a/src/components/Input/Input.stories.tsx b/src/components/Input/Input.stories.tsx
deleted file mode 100644
index 72e3b2b..0000000
--- a/src/components/Input/Input.stories.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-import { Input } from './Input';
-
-const meta: Meta = {
- title: 'Components/Input',
- component: Input,
- parameters: {
- layout: 'centered',
- },
- tags: ['autodocs'],
- argTypes: {
- inputSize: {
- control: 'select',
- options: ['sm', 'md', 'lg'],
- },
- disabled: {
- control: 'boolean',
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- args: {
- placeholder: 'Enter text...',
- },
-};
-
-export const WithLabel: Story = {
- args: {
- label: 'Username',
- placeholder: 'Enter your username',
- },
-};
-
-export const WithError: Story = {
- args: {
- label: 'Email',
- placeholder: 'your.email@example.com',
- error: 'Please enter a valid email address',
- },
-};
-
-export const WithHelperText: Story = {
- args: {
- label: 'Full Name',
- placeholder: 'John Doe',
- helperText: 'Enter your first and last name',
- },
-};
-
-export const Small: Story = {
- args: {
- label: 'Small Input',
- placeholder: 'Small size',
- inputSize: 'sm',
- },
-};
-
-export const Medium: Story = {
- args: {
- label: 'Medium Input',
- placeholder: 'Medium size',
- inputSize: 'md',
- },
-};
-
-export const Large: Story = {
- args: {
- label: 'Large Input',
- placeholder: 'Large size',
- inputSize: 'lg',
- },
-};
-
-export const Disabled: Story = {
- args: {
- label: 'Disabled Input',
- placeholder: 'Cannot edit',
- disabled: true,
- },
-};
-
-export const AllSizes: Story = {
- render: () => (
-
-
-
-
-
- ),
-};
-
-export const FormExample: Story = {
- render: () => (
-
-
-
-
-
- ),
-};
diff --git a/src/components/Input/Input.test.tsx b/src/components/Input/Input.test.tsx
index 8745465..e20c2fc 100644
--- a/src/components/Input/Input.test.tsx
+++ b/src/components/Input/Input.test.tsx
@@ -17,7 +17,9 @@ describe("Input", () => {
it("renders with label", () => {
render( );
- expect(screen.getByLabelText("Username")).toBeInTheDocument();
+ expect(
+ screen.getByRole("textbox", { name: /username/i }),
+ ).toBeInTheDocument();
});
it("displays error message", () => {
@@ -62,13 +64,13 @@ describe("Input", () => {
it("applies small size correctly", () => {
render( );
const input = screen.getByRole("textbox");
- expect(input).toHaveClass("h-9");
+ expect(input).toHaveClass("h-8");
});
it("applies medium size by default", () => {
render( );
const input = screen.getByRole("textbox");
- expect(input).toHaveClass("h-11");
+ expect(input).toHaveClass("h-10");
});
it("applies large size correctly", () => {
@@ -92,8 +94,13 @@ describe("Input", () => {
it("associates label with input using htmlFor", () => {
render( );
const label = screen.getByText("Email");
- const input = screen.getByLabelText("Email");
+ const input = screen.getByRole("textbox", { name: /email/i });
expect(label).toHaveAttribute("for", "email-input");
expect(input).toHaveAttribute("id", "email-input");
});
+
+ it("shows skeleton when loading", () => {
+ render( );
+ expect(screen.getByRole("status")).toBeInTheDocument();
+ });
});
diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx
index e9c42a8..f92357e 100644
--- a/src/components/Input/Input.tsx
+++ b/src/components/Input/Input.tsx
@@ -1,29 +1,23 @@
-import { forwardRef, useId, type InputHTMLAttributes, useMemo } from "react";
+import { forwardRef, useId, useMemo } from "react";
import { cn } from "@/utils";
import { useTheme } from "@/theme";
-
-export interface InputProps extends InputHTMLAttributes {
- label?: string;
- error?: string;
- helperText?: string;
- inputSize?: "sm" | "md" | "lg";
- leftIcon?: React.ReactNode;
- rightIcon?: React.ReactNode;
-}
+import { Skeleton } from "../Skeleton";
+import type { InputProps } from "./Input.types";
const Input = forwardRef(
(
{
+ id,
className,
label,
error,
helperText,
inputSize = "md",
- id,
disabled,
required,
leftIcon,
rightIcon,
+ isLoading,
...props
},
ref,
@@ -40,8 +34,8 @@ const Input = forwardRef(
}, [errorId, helperId]);
const sizes = {
- sm: "h-9 text-sm",
- md: "h-11 text-sm",
+ sm: "h-8 text-xs",
+ md: "h-10 text-sm",
lg: "h-12 text-base",
};
@@ -64,55 +58,74 @@ const Input = forwardRef(
{label}
{required && * }
)}
-
- {leftIcon && (
-
{leftIcon}
- )}
-
-
+ ) : (
+
+ >
+ {leftIcon && (
+
+ {leftIcon}
+
+ )}
+
+
- {rightIcon && (
-
{rightIcon}
- )}
-
+ {rightIcon && (
+
+ {rightIcon}
+
+ )}
+
+ )}
{error && (
-
+
{error}
)}
{helperText && !error && (
-
+
{helperText}
)}
diff --git a/src/components/Input/Input.types.ts b/src/components/Input/Input.types.ts
new file mode 100644
index 0000000..404f589
--- /dev/null
+++ b/src/components/Input/Input.types.ts
@@ -0,0 +1,24 @@
+import type { InputHTMLAttributes } from "react";
+
+/**
+ * Props for the Input component.
+ * Extends all standard HTML input attributes.
+ */
+export interface InputProps extends InputHTMLAttributes {
+ /** Unique identifier for the input element. */
+ id?: string;
+ /** The label displayed above the input field. */
+ label?: string;
+ /** Error message to display below the input (puts the input in an error state). */
+ error?: string;
+ /** Optional supporting text displayed below the input field. */
+ helperText?: string;
+ /** The visual size of the input. @default "md" */
+ inputSize?: "sm" | "md" | "lg";
+ /** Icon element to display inside the input on the left side. */
+ leftIcon?: React.ReactNode;
+ /** Icon element to display inside the input on the right side. */
+ rightIcon?: React.ReactNode;
+ /** Whether the input is in a loading state. */
+ isLoading?: boolean;
+}
diff --git a/src/components/Modal/Modal.stories.tsx b/src/components/Modal/Modal.stories.tsx
deleted file mode 100644
index 7f2ef22..0000000
--- a/src/components/Modal/Modal.stories.tsx
+++ /dev/null
@@ -1,308 +0,0 @@
-// src/components/Modal/Modal.stories.tsx
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { useState } from "react";
-import { Modal } from "./Modal";
-import { Button } from "../Button";
-
-const meta: Meta = {
- title: "Components/Modal",
- component: Modal,
- parameters: {
- layout: "centered",
- docs: {
- description: {
- component:
- "A fully accessible modal component with backdrop, keyboard controls, and focus management.",
- },
- },
- },
- tags: ["autodocs"],
- argTypes: {
- isOpen: {
- control: "boolean",
- description: "Controls modal visibility",
- },
- size: {
- control: "select",
- options: ["sm", "md", "lg", "xl"],
- description: "Modal size preset",
- },
- showCloseButton: {
- control: "boolean",
- description: "Show/hide close button",
- },
- preventBackdropClose: {
- control: "boolean",
- description: "Prevent closing on backdrop click",
- },
- preventEscapeClose: {
- control: "boolean",
- description: "Prevent closing on Escape key",
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-// Wrapper component for stories
-const ModalWrapper = (args: any) => {
- const [isOpen, setIsOpen] = useState(false);
-
- return (
- <>
- setIsOpen(true)}>Open Modal
- setIsOpen(false)} />
- >
- );
-};
-
-// Default modal
-export const Default: Story = {
- render: (args) => ,
- args: {
- title: "Modal Title",
- children: (
-
-
- This is a modal dialog. You can close it by clicking the close button,
- clicking outside, or pressing the Escape key.
-
-
Action Button
-
- ),
- },
-};
-
-// Different sizes
-export const Sizes: Story = {
- render: () => {
- const [activeModal, setActiveModal] = useState(null);
-
- return (
-
-
setActiveModal("sm")}>Small
-
setActiveModal("md")}>Medium
-
setActiveModal("lg")}>Large
-
setActiveModal("xl")}>Extra Large
-
-
setActiveModal(null)}
- size="sm"
- title="Small Modal"
- >
- This is a small modal (max-w-md)
-
-
-
setActiveModal(null)}
- size="md"
- title="Medium Modal"
- >
- This is a medium modal (max-w-lg) - the default size
-
-
-
setActiveModal(null)}
- size="lg"
- title="Large Modal"
- >
- This is a large modal (max-w-2xl)
-
-
-
setActiveModal(null)}
- size="xl"
- title="Extra Large Modal"
- >
- This is an extra large modal (max-w-4xl)
-
-
- );
- },
-};
-
-// Without close button
-export const NoCloseButton: Story = {
- render: (args) => ,
- args: {
- title: "No Close Button",
- showCloseButton: false,
- children: (
-
-
- This modal has no close button. You can still close it by clicking
- outside or pressing Escape.
-
-
- ),
- },
-};
-
-// Prevent backdrop close
-export const PreventBackdropClose: Story = {
- render: (args) => ,
- args: {
- title: "Cannot Close on Backdrop Click",
- preventBackdropClose: true,
- children: (
-
-
- You cannot close this modal by clicking outside. Use the close button
- or press Escape.
-
-
- ),
- },
-};
-
-// Prevent all closes except button
-export const ForceCloseButton: Story = {
- render: (args) => ,
- args: {
- title: "Must Use Close Button",
- preventBackdropClose: true,
- preventEscapeClose: true,
- children: (
-
-
- You must use the close button to close this modal. Backdrop click and
- Escape key are disabled.
-
-
- ),
- },
-};
-
-// Long content with scroll
-export const LongContent: Story = {
- render: (args) => ,
- args: {
- title: "Modal with Long Content",
- children: (
-
- {Array.from({ length: 20 }, (_, i) => (
-
- Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur
- adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua.
-
- ))}
-
- ),
- },
-};
-
-// Confirmation dialog example
-export const ConfirmationDialog: Story = {
- render: () => {
- const [isOpen, setIsOpen] = useState(false);
-
- const handleConfirm = () => {
- alert("Confirmed!");
- setIsOpen(false);
- };
-
- return (
- <>
- setIsOpen(true)}>
- Delete Item
-
- setIsOpen(false)}
- title="Confirm Deletion"
- size="sm"
- >
-
-
- Are you sure you want to delete this item? This action cannot be
- undone.
-
-
- setIsOpen(false)}>
- Cancel
-
-
- Delete
-
-
-
-
- >
- );
- },
-};
-
-// Form inside modal
-export const FormModal: Story = {
- render: () => {
- const [isOpen, setIsOpen] = useState(false);
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- alert("Form submitted!");
- setIsOpen(false);
- };
-
- return (
- <>
- setIsOpen(true)}>Add New Item
- setIsOpen(false)}
- title="Add New Item"
- size="md"
- >
-
-
- >
- );
- },
-};
-
-// Without title
-export const NoTitle: Story = {
- render: (args) => ,
- args: {
- children: (
-
-
This modal has no title.
-
- ),
- },
-};
diff --git a/src/components/Modal/Modal.types.tsx b/src/components/Modal/Modal.types.tsx
index 3139138..7d23df5 100644
--- a/src/components/Modal/Modal.types.tsx
+++ b/src/components/Modal/Modal.types.tsx
@@ -1,61 +1,43 @@
// src/components/Modal/Modal.types.ts
import type { ReactNode } from "react";
+/**
+ * The visual size variants available for the Modal.
+ */
export type ModalSize = "sm" | "md" | "lg" | "xl";
+/**
+ * Props for the Modal component.
+ */
export interface ModalProps {
- /**
- * Optional id for the modal
- */
+ /** Unique identifier for the modal. */
id?: string;
- /**
- * Controls whether the modal is visible
- */
+ /** Whether the modal is currently open and visible. */
isOpen: boolean;
- /**
- * Callback when the modal should close
- */
+ /** Callback function triggered when the modal requests to close (e.g., clicking the backdrop). */
onClose: () => void;
- /**
- * Optional title displayed at the top of the modal
- */
+ /** The title displayed in the modal header. */
title?: string;
- /**
- * Modal content
- */
+ /** The content to be rendered within the modal body. */
children: ReactNode;
- /**
- * Additional CSS classes
- */
+ /** Custom CSS class for the modal container. */
className?: string;
- /**
- * Modal size preset
- * @default 'md'
- */
+ /** The visual width of the modal. @default "md" */
size?: ModalSize;
- /**
- * Show/hide the close button
- * @default true
- */
+ /** Whether to show a close icon in the header. @default true */
showCloseButton?: boolean;
- /**
- * Prevent closing on backdrop click
- * @default false
- */
+ /** If true, clicking the backdrop will not trigger onClose. @default false */
preventBackdropClose?: boolean;
- /**
- * Prevent closing on Escape key
- * @default false
- */
+ /** If true, pressing the Escape key will not trigger onClose. @default false */
preventEscapeClose?: boolean;
}
diff --git a/src/components/RadioGroup/RadioGroup.constants.ts b/src/components/RadioGroup/RadioGroup.constants.ts
new file mode 100644
index 0000000..9415989
--- /dev/null
+++ b/src/components/RadioGroup/RadioGroup.constants.ts
@@ -0,0 +1,43 @@
+import type { RadioGroupProps } from "./RadioGroup.types";
+
+export const sizeClasses = {
+ small: "w-4 h-4",
+ medium: "w-5 h-5",
+ large: "w-6 h-6",
+};
+
+export const checkedBorderWidths = {
+ small: "border-[5px]",
+ medium: "border-[6px]",
+ large: "border-[7px]",
+};
+
+export const touchTargetClasses = {
+ small: "min-h-[40px]",
+ medium: "min-h-[44px]",
+ large: "min-h-[48px]",
+};
+
+export const cardClasses = {
+ small: "p-3 rounded-lg",
+ medium: "p-4 rounded-xl",
+ large: "p-5 rounded-2xl",
+};
+
+export const SKELETON_SIZE_MAP: Record<
+ NonNullable,
+ { height: string; width: string }
+> = {
+ small: { height: "16px", width: "16px" },
+ medium: { height: "20px", width: "20px" },
+ large: { height: "24px", width: "24px" },
+};
+
+export const SKELETON_CARD_SIZE_MAP: Record<
+ NonNullable,
+ { height: string; width: string }
+> = {
+ small: { height: "40px", width: "100%" },
+ medium: { height: "44px", width: "100%" },
+ large: { height: "48px", width: "100%" },
+};
diff --git a/src/components/RadioGroup/RadioGroup.test.tsx b/src/components/RadioGroup/RadioGroup.test.tsx
new file mode 100644
index 0000000..5677804
--- /dev/null
+++ b/src/components/RadioGroup/RadioGroup.test.tsx
@@ -0,0 +1,184 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { describe, it, expect, vi } from "vitest";
+import RadioGroup from "./RadioGroup";
+import { ThemeProvider } from "../../theme";
+
+const options = [
+ {
+ id: "opt-1",
+ value: "option-1",
+ label: "Option One",
+ description: "Description 1",
+ },
+ { id: "opt-2", value: "option-2", label: "Option Two" },
+ { id: "opt-3", value: "option-3", label: "Option Three", disabled: true },
+];
+
+const renderWithTheme = (ui: React.ReactElement) => {
+ return render({ui} );
+};
+
+describe("RadioGroup", () => {
+ it("renders correctly with all options", () => {
+ renderWithTheme(
+ ,
+ );
+
+ expect(screen.getByText("Group Label")).toBeInTheDocument();
+ expect(
+ screen.getByRole("radio", { name: /Option One/i }),
+ ).toBeInTheDocument();
+ expect(screen.getByText("Description 1")).toBeInTheDocument();
+ expect(
+ screen.getByRole("radio", { name: /Option Two/i }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole("radio", { name: /Option Three/i }),
+ ).toBeInTheDocument();
+ });
+
+ it("handles uncontrolled selection change", () => {
+ const onChange = vi.fn();
+ renderWithTheme(
+ ,
+ );
+
+ const option2 = screen.getByRole("radio", {
+ name: /Option Two/i,
+ }) as HTMLInputElement;
+ expect(screen.getByRole("radio", { name: /Option One/i })).toBeChecked();
+ expect(option2).not.toBeChecked();
+
+ fireEvent.click(option2);
+ expect(option2).toBeChecked();
+ expect(onChange).toHaveBeenCalledWith("option-2");
+ });
+
+ it("handles controlled state", () => {
+ const onChange = vi.fn();
+ const { rerender } = renderWithTheme(
+ ,
+ );
+
+ const option2 = screen.getByRole("radio", {
+ name: /Option Two/i,
+ }) as HTMLInputElement;
+
+ fireEvent.click(option2);
+ expect(onChange).toHaveBeenCalledWith("option-2");
+ // Should still be option-1 because it's controlled and we haven't updated the value prop
+ expect(option2).not.toBeChecked();
+
+ rerender(
+
+
+ ,
+ );
+ expect(option2).toBeChecked();
+ });
+
+ it("respects individual option disabled state", () => {
+ const onChange = vi.fn();
+ renderWithTheme(
+ ,
+ );
+
+ const disabledOption = screen.getByRole("radio", { name: /Option Three/i });
+ expect(disabledOption).toBeDisabled();
+
+ // Clicking a disabled input directly should not trigger onChange in JSDOM
+ fireEvent.click(disabledOption);
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it("respects group disabled state", () => {
+ renderWithTheme(
+ ,
+ );
+
+ options.forEach((option) => {
+ expect(
+ screen.getByRole("radio", {
+ name: new RegExp(option.label as string, "i"),
+ }),
+ ).toBeDisabled();
+ });
+ });
+
+ it("renders with card variant", () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+
+ // Check if the card styles are applied
+ const labels = container.querySelectorAll("label");
+ labels.forEach((label) => {
+ expect(label.className).toContain("border-2");
+ });
+ });
+
+ it("shows error and accessibility attributes", () => {
+ renderWithTheme(
+ ,
+ );
+
+ const errorMessage = screen.getByText("Field is required");
+ const fieldset = screen.getByRole("group", { name: /test group/i });
+ const inputs = screen.getAllByRole("radio");
+
+ expect(errorMessage).toBeInTheDocument();
+ expect(fieldset).toHaveAttribute("aria-describedby", "test-rg-error");
+ expect(fieldset).toHaveAttribute("aria-invalid", "true");
+
+ inputs.forEach((input) => {
+ expect(input).toHaveAttribute("required");
+ });
+ });
+
+ it("renders loading state", () => {
+ const { container } = renderWithTheme(
+ ,
+ );
+
+ // Skeletons should be visible
+ const skeletons = container.querySelectorAll(".skeleton-root");
+ expect(skeletons.length).toBeGreaterThan(0);
+ });
+
+ it("associates helper text via aria-describedby", () => {
+ renderWithTheme(
+ ,
+ );
+
+ const fieldset = screen.getByRole("group");
+ // We know the id should be test-rg-helper
+ expect(fieldset).toHaveAttribute("aria-describedby", "test-rg-helper");
+ });
+});
diff --git a/src/components/RadioGroup/RadioGroup.tsx b/src/components/RadioGroup/RadioGroup.tsx
new file mode 100644
index 0000000..97849e9
--- /dev/null
+++ b/src/components/RadioGroup/RadioGroup.tsx
@@ -0,0 +1,253 @@
+import { forwardRef, useState } from "react";
+import type { RadioGroupProps } from "./RadioGroup.types";
+import { useTheme } from "@/theme";
+import Skeleton from "../Skeleton";
+import { Typography } from "../Typography";
+import { cn } from "@/utils";
+import {
+ cardClasses,
+ checkedBorderWidths,
+ sizeClasses,
+ SKELETON_CARD_SIZE_MAP,
+ SKELETON_SIZE_MAP,
+ touchTargetClasses,
+} from "./RadioGroup.constants";
+
+const RadioGroup = forwardRef(
+ (props, ref) => {
+ const {
+ id,
+ options,
+ value: controlledValue,
+ defaultValue,
+ onChange,
+ isLoading,
+ name,
+ label,
+ helperText,
+ error,
+ required,
+ size = "medium",
+ variant = "default",
+ orientation = "vertical",
+ disabled,
+ className,
+ style,
+ } = props;
+ const { theme } = useTheme();
+
+ const [internalValue, setInternalValue] = useState(defaultValue);
+ const value =
+ controlledValue !== undefined ? controlledValue : internalValue;
+
+ const handleChange = (newValue: string, isOptionDisabled?: boolean) => {
+ if (disabled || isOptionDisabled) return;
+ if (controlledValue === undefined) {
+ setInternalValue(newValue);
+ }
+ onChange?.(newValue);
+ };
+
+ const helperTextId = id ? `${id}-helper` : undefined;
+ const errorId = id ? `${id}-error` : undefined;
+ const describedBy = error ? errorId : helperText ? helperTextId : undefined;
+
+ if (isLoading) {
+ return (
+
+
+
+
+ {helperText && (
+
+ )}
+
+
+ );
+ }
+
+ const themeStyles = {
+ "--primary": theme.palette.primary[500],
+ "--primary-muted":
+ theme.colorMode === "light"
+ ? theme.palette.primary[50]
+ : `${theme.palette.primary[900]}44`, // Muted background for card checked state
+ "--error": theme.palette.error[500],
+ "--ring": theme.tokens.ring,
+ "--border-color": error ? theme.palette.error[500] : theme.tokens.border,
+ "--bg-color": theme.tokens.surface,
+ "--text-color": theme.tokens.foreground,
+ "--text-secondary": theme.tokens.foregroundMuted,
+ } as React.CSSProperties;
+
+ return (
+
+ {label && (
+
+ {label}
+ {required && (
+
+ *
+
+ )}
+ {required && (required) }
+
+ )}
+
+ {label &&
}
+
+
+ {options.map((option) => {
+ const isOptionDisabled = disabled || option.disabled;
+ const isChecked = value === option.value;
+
+ return (
+
+ handleChange(option.value, isOptionDisabled)}
+ disabled={isOptionDisabled}
+ required={required}
+ className="peer sr-only"
+ aria-invalid={!!error}
+ aria-describedby={describedBy}
+ />
+
+
+
+
+
+ {option.label}
+
+
+ {option.description && (
+
+ {option.description}
+
+ )}
+
+
+ );
+ })}
+
+
+ {(error || helperText) && (
+
+
+ {error || helperText}
+
+
+ )}
+
+ );
+ },
+);
+
+RadioGroup.displayName = "RadioGroup";
+
+export default RadioGroup;
diff --git a/src/components/RadioGroup/RadioGroup.types.ts b/src/components/RadioGroup/RadioGroup.types.ts
new file mode 100644
index 0000000..5c55269
--- /dev/null
+++ b/src/components/RadioGroup/RadioGroup.types.ts
@@ -0,0 +1,55 @@
+/**
+ * Represents an individual option within a RadioGroup.
+ */
+export interface RadioOption {
+ /** Unique identifier for the option. */
+ id: string;
+ /** The content to be displayed for the option label. */
+ label: React.ReactNode;
+ /** The value associated with this option. */
+ value: string;
+ /** Whether the individual option is disabled. */
+ disabled?: boolean;
+ /** Optional detailed description displayed below the label. */
+ description?: React.ReactNode;
+}
+
+/**
+ * Props for the RadioGroup component.
+ */
+export interface RadioGroupProps {
+ /** Unique identifier for the RadioGroup. */
+ id?: string;
+ /** Array of options to be rendered within the group. */
+ options: RadioOption[];
+ /** Whether the component is in a loading state. */
+ isLoading?: boolean;
+ /** The controlled value of the selected radio button. */
+ value?: string;
+ /** The initial value for an uncontrolled component. */
+ defaultValue?: string;
+ /** Callback function triggered when the selection changes. */
+ onChange?: (value: string) => void;
+ /** The name used for grouping the radio inputs (important for native form submission). */
+ name?: string;
+ /** The label for the entire group. */
+ label?: React.ReactNode;
+ /** Supporting text displayed below the radio group. */
+ helperText?: React.ReactNode;
+ /** Error message to display, which also puts the group in an error state. */
+ error?: React.ReactNode;
+ /** Whether a selection is required. */
+ required?: boolean;
+ /** The visual size of the component. @default "medium" */
+ size?: "small" | "medium" | "large";
+ /** The visual variant of the radio buttons. @default "default" */
+ variant?: "default" | "card";
+ /** The layout direction of the options. @default "vertical" */
+ orientation?: "horizontal" | "vertical";
+ /** Whether the entire group is disabled. */
+ disabled?: boolean;
+ /** Custom CSS class for the root fieldset element. */
+ className?: string;
+ /** Inline styles for the root fieldset element. */
+ style?: React.CSSProperties;
+}
diff --git a/src/components/RadioGroup/index.ts b/src/components/RadioGroup/index.ts
new file mode 100644
index 0000000..ff6b007
--- /dev/null
+++ b/src/components/RadioGroup/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./RadioGroup";
+export * from "./RadioGroup.types";
diff --git a/src/components/ScrollArea/ScrollArea.tsx b/src/components/ScrollArea/ScrollArea.tsx
new file mode 100644
index 0000000..e233900
--- /dev/null
+++ b/src/components/ScrollArea/ScrollArea.tsx
@@ -0,0 +1,69 @@
+import { useTheme } from "@/theme";
+import type { ScrollAreaProps, ScrollAreaItemProps } from "./ScrollArea.types";
+import { useReveal } from "@/hooks/useReveal";
+
+const ScrollAreaComponent = ({
+ id,
+ ref,
+ children,
+ className = "",
+ style,
+ onScroll,
+}: ScrollAreaProps) => {
+ const { theme } = useTheme();
+
+ const themeStyles = {
+ "--scroll-thumb": theme.tokens.foregroundMuted,
+ "--scroll-thumb-hover": theme.tokens.foreground,
+ "--scroll-thumb-active": theme.palette.primary[500],
+ } as React.CSSProperties;
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const ScrollAreaItem = ({
+ children,
+ className = "",
+ style,
+ triggerOnce,
+ revealType = "scale",
+ threshold = 0.1,
+}: ScrollAreaItemProps) => {
+ const { elementRef, isVisible } = useReveal({ triggerOnce, threshold });
+
+ const ANIMATION_CLASS_MAP: Record<
+ NonNullable,
+ string
+ > = {
+ up: "reveal-up",
+ down: "reveal-down",
+ left: "reveal-left",
+ right: "reveal-right",
+ fade: "fade-in",
+ scale: "reveal-scale",
+ blur: "reveal-blur",
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export { ScrollAreaComponent as ScrollArea };
+export { ScrollAreaItem as ScrollElement };
diff --git a/src/components/ScrollArea/ScrollArea.types.ts b/src/components/ScrollArea/ScrollArea.types.ts
new file mode 100644
index 0000000..7ed02cd
--- /dev/null
+++ b/src/components/ScrollArea/ScrollArea.types.ts
@@ -0,0 +1,120 @@
+import type React from "react";
+
+/**
+ * Properties for the ScrollArea container component.
+ *
+ * The `ScrollArea` component acts as a highly customizable wrapper that coordinates
+ * reveal-on-scroll animations for its nested `ScrollAreaItem` descendants.
+ */
+export interface ScrollAreaProps {
+ /**
+ * Unique identifier applied to the scrollable container.
+ * Useful for accessibility (`aria-controls`) and testing scenarios.
+ */
+ id?: string;
+
+ /**
+ * Ref forwarded internally to the underlying scroll container element (``).
+ * Can be leveraged to programmatically control scroll position.
+ */
+ ref?: React.RefObject
;
+
+ /**
+ * Content rendered inside the scrollable container.
+ * This typically includes standard HTML elements and `ScrollAreaItem` components
+ * which handle individual entry animations.
+ */
+ children: React.ReactNode;
+
+ /**
+ * Optional custom CSS class names to apply to the scroll container.
+ */
+ className?: string;
+
+ /**
+ * Optional inline styles to apply directly to the scroll container viewport.
+ */
+ style?: React.CSSProperties;
+
+ /**
+ * Callback fired synchronously when the user scrolls the container's contents.
+ *
+ * @param event - The native UI event fired by the underlying `HTMLDivElement`.
+ */
+ onScroll?: (event: React.UIEvent) => void;
+
+ /**
+ * Defines the default reveal animation applied to all child `ScrollAreaItem` descendant
+ * items when they enter the viewport. Items can override this using their own `revealType`.
+ *
+ * @remarks
+ * - `"up"`, `"down"`, `"left"`, `"right"`: Directional slide-in translations.
+ * - `"fade"`: Simple opacity transition from 0% to 100%.
+ * - `"scale"`: Zooms in fluidly from a lower scale bound.
+ * - `"blur"`: Transitions from a gaussian blur down into sharp focus.
+ *
+ * @defaultValue `"scale"`
+ */
+ ScrollAnimation?:
+ | "up"
+ | "down"
+ | "left"
+ | "right"
+ | "fade"
+ | "scale"
+ | "blur";
+}
+
+/**
+ * Properties for an individual scroll-observable item.
+ *
+ * Specifically designed to be composed within a `ScrollArea` container. It leverages
+ * the intersection observer to trigger entrance and reveal animations.
+ */
+export interface ScrollAreaItemProps {
+ /**
+ * The content to be wrapped and naturally revealed when intersected by the viewport.
+ */
+ children: React.ReactNode;
+
+ /**
+ * Optional custom CSS class names applied to the wrapper executing the intersection observer.
+ */
+ className?: string;
+
+ /**
+ * Optional inline styles applied directly to the observer wrapper element.
+ */
+ style?: React.CSSProperties;
+
+ /**
+ * Determines whether the reveal animation occurs only a single time or repeatedly.
+ *
+ * - If `true`, the animation plays the first time the item is visible and remains unobserved mapping to a finalized state.
+ * - If `false`, the animation replays every single time the item crosses the threshold back into the viewport's context.
+ *
+ * @defaultValue `true`
+ */
+ triggerOnce?: boolean;
+
+ /**
+ * Set the threshold ratio indicating the percentage of the item's visibility required
+ * to trigger the reveal animation callbacks.
+ *
+ * - A value of `0.0` reveals instantly when any single pixel enters the viewport.
+ * - A value of `0.5` requires 50% visibility prior to executing the animation.
+ * - A value of `1.0` dictates the entire element must be thoroughly seen.
+ *
+ * @defaultValue 0.2
+ */
+ threshold?: number;
+
+ /**
+ * Disables the parent container's `ScrollAnimation` setting exclusively for this particular item.
+ * Highly useful for constructing complex cascades where certain hero items enter differently
+ * from the standard unified grid items.
+ *
+ * Inherits strictly from `ScrollAreaProps["ScrollAnimation"]` when left omitted.
+ */
+ revealType?: NonNullable;
+}
diff --git a/src/components/ScrollArea/index.ts b/src/components/ScrollArea/index.ts
new file mode 100644
index 0000000..755a2e0
--- /dev/null
+++ b/src/components/ScrollArea/index.ts
@@ -0,0 +1,2 @@
+export * from "./ScrollArea";
+export * from "./ScrollArea.types";
diff --git a/src/components/Skeleton/Skeleton.test.tsx b/src/components/Skeleton/Skeleton.test.tsx
new file mode 100644
index 0000000..ef908fc
--- /dev/null
+++ b/src/components/Skeleton/Skeleton.test.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import { describe, it, expect } from "vitest";
+import { render as rtlRender, screen } from "@testing-library/react";
+import { Skeleton } from "./Skeleton";
+import { ThemeProvider } from "../../theme";
+
+const render = (ui: React.ReactElement) => {
+ return rtlRender({ui} );
+};
+
+describe("Skeleton", () => {
+ it("renders correctly", () => {
+ render( );
+ expect(screen.getByRole("status")).toBeInTheDocument();
+ });
+
+ it("applies variant styles correctly", () => {
+ render( );
+ const span = screen.getByRole("status");
+ expect(span).toHaveStyle({ borderRadius: "50%" });
+ });
+
+ it("applies width and height correctly", () => {
+ render( );
+ const span = screen.getByRole("status");
+ expect(span).toHaveStyle({ width: "100px", height: "50px" });
+ });
+
+ it("applies animation class correctly", () => {
+ const { rerender } = render( );
+ let span = screen.getByRole("status");
+ expect(span).toHaveClass("skeleton-pulse");
+
+ rerender(
+
+
+ ,
+ );
+ span = screen.getByRole("status");
+ expect(span).toHaveClass("skeleton-wave");
+ });
+
+ it("applies custom className", () => {
+ render( );
+ expect(screen.getByRole("status")).toHaveClass("custom-class");
+ });
+
+ it("has accessible roles and labels", () => {
+ render( );
+ const span = screen.getByRole("status");
+ expect(span).toHaveAttribute("aria-label", "Loading…");
+ expect(span).toHaveAttribute("aria-busy", "true");
+ });
+});
diff --git a/src/components/Skeleton/Skeleton.tsx b/src/components/Skeleton/Skeleton.tsx
new file mode 100644
index 0000000..cd6802d
--- /dev/null
+++ b/src/components/Skeleton/Skeleton.tsx
@@ -0,0 +1,100 @@
+import { useTheme } from "@/theme";
+import type { CSSProperties } from "react";
+
+// ─── Types ────────────────────────────────────────────────────────────────────
+
+/**
+ * The shape of the skeleton placeholder.
+ */
+type SkeletonVariant = "rectangular" | "circular" | "text" | "rounded";
+
+/**
+ * The animation style to apply to the skeleton.
+ */
+type SkeletonAnimation = "pulse" | "wave" | "none";
+
+/**
+ * Props for the Skeleton component.
+ */
+export interface SkeletonProps {
+ /** The shape variant to use. @default "rectangular" */
+ variant?: SkeletonVariant;
+ /** The animation type to display. @default "wave" */
+ animation?: SkeletonAnimation;
+ /** Custom width for the skeleton placeholder. */
+ width?: number | string;
+ /** Custom height for the skeleton placeholder. */
+ height?: number | string;
+ /** Additional CSS class for the root element. */
+ className?: string;
+ /** Inline styles for the root element. */
+ style?: CSSProperties;
+}
+
+// ─── Helpers ──────────────────────────────────────────────────────────────────
+
+function px(v: number | string | undefined): string | undefined {
+ if (v === undefined) return undefined;
+ return typeof v === "number" ? `${v}px` : v;
+}
+
+function variantStyles(variant: SkeletonVariant): CSSProperties {
+ switch (variant) {
+ case "circular":
+ return { borderRadius: "50%" };
+ case "rounded":
+ return { borderRadius: "12px" };
+ case "text":
+ return {
+ borderRadius: "4px",
+ transform: "scale(1, 0.85)",
+ transformOrigin: "0 55%",
+ };
+ default:
+ return { borderRadius: "4px" };
+ }
+}
+
+export function Skeleton({
+ variant = "rectangular",
+ animation = "wave",
+ width,
+ height,
+ className = "",
+ style,
+}: SkeletonProps) {
+ const { theme } = useTheme();
+ const resolvedWidth = px(width);
+ const resolvedHeight =
+ px(height) ??
+ (variant === "text" ? "1em" : variant === "circular" ? px(width) : "100%");
+
+ const animationClass =
+ animation === "pulse"
+ ? "skeleton-pulse"
+ : animation === "wave"
+ ? "skeleton-wave"
+ : "";
+
+ return (
+
+ );
+}
+
+export default Skeleton;
diff --git a/src/components/Skeleton/index.ts b/src/components/Skeleton/index.ts
new file mode 100644
index 0000000..de1f29c
--- /dev/null
+++ b/src/components/Skeleton/index.ts
@@ -0,0 +1,2 @@
+export * from "./Skeleton";
+export { default } from "./Skeleton";
diff --git a/src/components/Typography/Typography.stories.tsx b/src/components/Typography/Typography.stories.tsx
deleted file mode 100644
index d9cec9c..0000000
--- a/src/components/Typography/Typography.stories.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-// src/components/Typography/Typography.stories.tsx
-import type { Meta, StoryObj } from "@storybook/react-vite";
-import { Typography } from "./Typography";
-
-const meta: Meta = {
- title: "Components/Typography",
- component: Typography,
- parameters: {
- layout: "padded",
- docs: {
- description: {
- component:
- "Typography component for consistent text styling across the application.",
- },
- },
- },
- tags: ["autodocs"],
- argTypes: {
- variant: {
- control: "select",
- options: [
- "display1",
- "display2",
- "h1",
- "h2",
- "h3",
- "h4",
- "h5",
- "h6",
- "subtitle1",
- "subtitle2",
- "body1",
- "body2",
- "caption",
- "overline",
- ],
- description: "Typography variant",
- },
- weight: {
- control: "select",
- options: [
- "thin",
- "extralight",
- "light",
- "normal",
- "medium",
- "semibold",
- "bold",
- "extrabold",
- "black",
- ],
- description: "Font weight",
- },
- children: {
- control: "text",
- description: "Content to display",
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-// Default story
-export const Default: Story = {
- args: {
- variant: "h1",
- children: "The quick brown fox jumps over the lazy dog",
- },
-};
-
-// All variants showcase
-export const AllVariants: Story = {
- render: () => (
-
-
- Display 1 - The quick brown fox
-
-
- Display 2 - The quick brown fox
-
- Heading 1 - The quick brown fox
- Heading 2 - The quick brown fox
- Heading 3 - The quick brown fox
- Heading 4 - The quick brown fox
- Heading 5 - The quick brown fox
- Heading 6 - The quick brown fox
-
- Subtitle 1 - The quick brown fox
-
-
- Subtitle 2 - The quick brown fox
-
-
- Body 1 - The quick brown fox jumps over the lazy dog
-
-
- Body 2 - The quick brown fox jumps over the lazy dog
-
- Caption - The quick brown fox
- Overline - The quick brown fox
-
- ),
-};
-
-// Font weights
-export const FontWeights: Story = {
- render: () => (
-
-
- Font Weight Thin (100)
-
-
- Font Weight Extra Light (200)
-
-
- Font Weight Light (300)
-
-
- Font Weight Normal (400)
-
-
- Font Weight Medium (500)
-
-
- Font Weight Semibold (600)
-
-
- Font Weight Bold (700)
-
-
- Font Weight Extra Bold (800)
-
-
- Font Weight Black (900)
-
-
- ),
-};
-
-// Custom styling with className
-export const CustomStyling: Story = {
- render: () => (
-
-
- Blue heading
-
-
- Red text
-
-
- Centered heading
-
-
- Right aligned text
-
-
- Uppercase text
-
-
- Italic text
-
-
- Underlined text
-
-
- Strikethrough text
-
-
- ),
-};
-
-// Realistic example
-export const RealisticExample: Story = {
- render: () => (
-
-
- Technology
-
-
- The Future of Web Development in 2026
-
-
- Exploring the latest trends and technologies shaping the industry
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
- tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
- veniam, quis nostrud exercitation ullamco laboris.
-
-
- Key Takeaways
-
-
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
- dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
- proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
-
- "Innovation distinguishes between a leader and a follower." - Steve Jobs
-
-
-
- Published on February 15, 2026 • 5 min read
-
-
- ),
-};
diff --git a/src/components/Typography/Typography.test.tsx b/src/components/Typography/Typography.test.tsx
index 0272958..309d831 100644
--- a/src/components/Typography/Typography.test.tsx
+++ b/src/components/Typography/Typography.test.tsx
@@ -308,4 +308,9 @@ describe("Typography", () => {
expect(heading).toHaveAttribute("id", "section-heading");
});
});
+
+ it("shows skeleton when loading", () => {
+ render(Loading );
+ expect(screen.getByRole("status")).toBeInTheDocument();
+ });
});
diff --git a/src/components/Typography/Typography.tsx b/src/components/Typography/Typography.tsx
index bc97dba..e915776 100644
--- a/src/components/Typography/Typography.tsx
+++ b/src/components/Typography/Typography.tsx
@@ -7,17 +7,8 @@ import type {
} from "./Typography.types";
import { VARIANT_ELEMENT_MAP, VARIANT_WEIGHT_MAP } from "./Typography.types";
import { useTheme } from "@/theme";
+import { Skeleton } from "../Skeleton";
-/**
- * Typography component for consistent text styling across the application
- *
- * @example
- * ```tsx
- * Main Heading
- * Body text
- * Custom element
- * ```
- */
export const Typography: React.FC = ({
variant = "body1",
as,
@@ -27,29 +18,36 @@ export const Typography: React.FC = ({
children,
id,
onClick,
+ isLoading,
...rest
}) => {
const { theme } = useTheme();
- // Determine the HTML element to render
+
+ if (isLoading) {
+ return (
+
+ );
+ }
const Element = as || VARIANT_ELEMENT_MAP[variant] || "p";
- // Determine font weight
const fontWeight = weight || VARIANT_WEIGHT_MAP[variant];
const color = theme.tokens.foreground;
- // Build class names
const classes = cn(
- // Base styles
"typography",
-
- // Variant styles
getVariantClasses(variant),
-
- // Font weight
getWeightClasses(fontWeight),
-
- // Custom classes
className,
);
diff --git a/src/components/Typography/Typography.types.tsx b/src/components/Typography/Typography.types.tsx
index 853d647..5450c15 100644
--- a/src/components/Typography/Typography.types.tsx
+++ b/src/components/Typography/Typography.types.tsx
@@ -1,6 +1,9 @@
import type React from "react";
import { type JSX } from "react";
+/**
+ * The visual variants available for the Typography component.
+ */
export type TypographyVariant =
| "h1"
| "h2"
@@ -17,6 +20,9 @@ export type TypographyVariant =
| "display1"
| "display2";
+/**
+ * The available font weights.
+ */
export type TypographyWeight =
| "thin" // 100
| "extralight" // 200
@@ -28,53 +34,39 @@ export type TypographyWeight =
| "extrabold" // 800
| "black"; // 900
+/**
+ * Props for the Typography component.
+ */
export interface TypographyProps {
- /**
- * The variant of the typography
- * @default 'h1'
- */
+ /** The semantic and visual variant to use. @default "body1" */
variant?: TypographyVariant;
- /**
- * The HTML element to render
- * If not provided, maps to variant default
- */
+ /** The HTML tag to render as. If omitted, maps to a default based on variant. */
as?: keyof JSX.IntrinsicElements;
- /**
- * Font weight
- * @default based on variant
- */
+ /** The font weight to apply. @default based on variant */
weight?: TypographyWeight;
- /**
- * Additional CSS classes
- */
+ /** Additional CSS classes for the text element. */
className?: string;
- /**
- * Inline styles
- */
+ /** Inline styles for the text element. */
style?: React.CSSProperties;
- /**
- * Content to render
- */
+ /** The text or elements to be rendered. */
children: React.ReactNode;
- /**
- * HTML id attribute
- */
+ /** Unique ID for the element. */
id?: string;
- /**
- * onClick handler
- */
+ /** Click event handler. */
onClick?: React.MouseEventHandler;
- /**
- * onClick handler
- */
+
+ /** ID of the element this label is for (useful when `as="label"`). */
htmlFor?: string;
+
+ /** Whether to show a skeleton loading placeholder instead of text. */
+ isLoading?: boolean;
}
/**
diff --git a/src/components/index.ts b/src/components/index.ts
index d55f9d1..e95ab28 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -5,6 +5,10 @@ export * from "./Modal";
export * from "./Typography";
export * from "./Badge";
export * from "./Avatar";
+export * from "./Skeleton";
+export * from "./Checkbox";
+export * from "./ScrollArea";
+export * from "./RadioGroup";
// ── Theme ──
export { ThemeProvider, useTheme, defaultTheme, darkTheme } from "../theme";
diff --git a/src/hooks/useReveal.ts b/src/hooks/useReveal.ts
new file mode 100644
index 0000000..a705d8e
--- /dev/null
+++ b/src/hooks/useReveal.ts
@@ -0,0 +1,46 @@
+import { useState, useEffect, useRef } from "react";
+
+interface UseRevealOptions {
+ threshold?: number | number[];
+ triggerOnce?: boolean;
+}
+
+export const useReveal = ({
+ threshold = 0,
+ triggerOnce = true,
+}: UseRevealOptions = {}) => {
+ const [isVisible, setIsVisible] = useState(false);
+ const elementRef = useRef(null);
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ const entry = entries[0];
+
+ if (entry && entry.isIntersecting) {
+ setIsVisible(true);
+ if (triggerOnce && elementRef.current) {
+ observer.unobserve(elementRef.current);
+ }
+ } else if (!triggerOnce) {
+ setIsVisible(false);
+ }
+ },
+ { threshold },
+ );
+
+ const currentElement = elementRef.current;
+
+ if (currentElement) {
+ observer.observe(currentElement);
+ }
+
+ return () => {
+ if (currentElement) {
+ observer.unobserve(currentElement);
+ }
+ };
+ }, [threshold, triggerOnce]);
+
+ return { elementRef, isVisible };
+};
diff --git a/src/theme/DefaultTheme.ts b/src/theme/DefaultTheme.ts
index c12130d..4ca59d9 100644
--- a/src/theme/DefaultTheme.ts
+++ b/src/theme/DefaultTheme.ts
@@ -8,16 +8,16 @@ import type { ThemeConfig } from "./Types";
const palette = {
primary: {
- 50: "#eff6ff",
- 100: "#dbeafe",
- 200: "#bfdbfe",
- 300: "#93c5fd",
- 400: "#60a5fa",
- 500: "#3b82f6",
- 600: "#2563eb",
- 700: "#1d4ed8",
- 800: "#1e40af",
- 900: "#1e3a8a",
+ 50: "#fdf2f8",
+ 100: "#fce7f3",
+ 200: "#fbcfe8",
+ 300: "#f9a8d4",
+ 400: "#f472b6",
+ 500: "#ec4899",
+ 600: "#db2777",
+ 700: "#be185d",
+ 800: "#9d174d",
+ 900: "#831843",
},
success: {
50: "#f0fdf4",
@@ -70,7 +70,7 @@ const palette = {
} satisfies ThemeConfig["palette"];
const neutral: ThemeConfig["neutral"] = {
- 50: "#f9fafb",
+ 50: "#fbfaf9ff",
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
diff --git a/src/theme/ThemeProvider.tsx b/src/theme/ThemeProvider.tsx
index 9751255..ae8640e 100644
--- a/src/theme/ThemeProvider.tsx
+++ b/src/theme/ThemeProvider.tsx
@@ -101,8 +101,11 @@ export function ThemeProvider({
const nextTheme = Object.values(THEME_REGISTRY).find(
(t) => t.colorMode === next,
);
- if (nextTheme) setTheme(nextTheme.name);
- }, [theme.colorMode, setTheme]);
+ if (nextTheme) {
+ setActiveThemeName(nextTheme.name);
+ if (storageKey) localStorage.setItem(storageKey, nextTheme.name);
+ }
+ }, [theme.colorMode, storageKey]);
const overrideTheme = useCallback((o: ThemeOverride) => {
setOverride((prev) => (prev ? { ...prev, ...o } : o));
diff --git a/src/theme/index.css b/src/theme/index.css
index 04f7ae1..68bcb81 100644
--- a/src/theme/index.css
+++ b/src/theme/index.css
@@ -4,11 +4,269 @@
@tailwind components;
@tailwind utilities;
-:root {
- --radius: 0.5rem;
+@keyframes skeleton-pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.4;
+ }
+}
+
+@keyframes skeleton-wave {
+ 0% {
+ transform: translateX(-100%);
+ }
+
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+.skeleton-root {
+ display: block;
+ background-color: var(--color-border-subtle);
+ border-radius: var(--radius-sm, 4px);
+ overflow: hidden;
+ position: relative;
+}
+
+.skeleton-pulse {
+ animation: skeleton-pulse 2s ease-in-out infinite;
+}
+
+.skeleton-wave {
+ position: relative;
+}
+
+.skeleton-wave::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(
+ 90deg,
+ transparent 0,
+ var(--color-border) 50%,
+ transparent 100%
+ );
+ animation: skeleton-wave 1.6s linear infinite;
+}
+
+/* ===== Modern Premium Scrollbar ===== */
+
+.scrollbar {
+ overflow: auto;
+ scrollbar-width: thin;
+ /* Firefox */
+ scrollbar-color: var(--color-border, rgba(150, 150, 150, 0.3)) transparent;
+}
+
+/* Scrollbar size */
+.scrollbar::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+
+/* Track */
+.scrollbar::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+/* Thumb */
+.scrollbar::-webkit-scrollbar-thumb {
+ background-color: var(--color-border-subtle, rgba(150, 150, 150, 0.3));
+ border-radius: 9999px;
+ background-clip: padding-box;
+ transition: background-color 0.2s ease-in-out;
+}
+
+/* Hover */
+.scrollbar:hover::-webkit-scrollbar-thumb,
+.scrollbar::-webkit-scrollbar-thumb:hover {
+ background-color: var(--color-border, rgba(150, 150, 150, 0.5));
+}
+
+/* Active (dragging) */
+.scrollbar::-webkit-scrollbar-thumb:active {
+ background-color: var(--color-text-subtle, rgba(150, 150, 150, 0.7));
+}
+
+/* Remove arrows */
+.scrollbar::-webkit-scrollbar-button {
+ display: none !important;
+ width: 0 !important;
+ height: 0 !important;
+}
+
+/* Some Windows versions need this */
+.scrollbar::-webkit-scrollbar-button:start:decrement,
+.scrollbar::-webkit-scrollbar-button:end:increment {
+ display: none !important;
+}
+
+/* ===== Reveal Animations ===== */
+/* ===============================
+ Base Reveal Class
+================================ */
+
+.reveal {
+ opacity: 0;
+ will-change: transform, opacity, filter;
+ /* Add default transition in case no animation class is applied */
+ transition: all 0.8s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+/* When element becomes visible */
+.reveal.is-visible {
+ opacity: 1;
+}
+
+/* Optional stagger support */
+.reveal {
+ animation-delay: var(--delay, 0ms);
+}
+
+/* ===============================
+ 1. Reveal Up
+================================ */
+
+@keyframes reveal-up {
+ from {
+ opacity: 0;
+ transform: translateY(40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.reveal-up.is-visible {
+ animation: reveal-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 2. Reveal Down
+================================ */
+
+@keyframes reveal-down {
+ from {
+ opacity: 0;
+ transform: translateY(-40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.reveal-down.is-visible {
+ animation: reveal-down 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 3. Reveal Left
+================================ */
+
+@keyframes reveal-left {
+ from {
+ opacity: 0;
+ transform: translateX(40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.reveal-left.is-visible {
+ animation: reveal-left 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 4. Reveal Right
+================================ */
+
+@keyframes reveal-right {
+ from {
+ opacity: 0;
+ transform: translateX(-40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.reveal-right.is-visible {
+ animation: reveal-right 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 5. Fade In
+================================ */
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+.fade-in.is-visible {
+ animation: fade-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 6. Scale Pop
+================================ */
+
+@keyframes reveal-scale {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.reveal-scale.is-visible {
+ animation: reveal-scale 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 7. Premium Blur Reveal
+================================ */
+
+@keyframes reveal-blur {
+ from {
+ opacity: 0;
+ transform: translateY(30px) scale(0.98);
+ filter: blur(12px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ filter: blur(0);
+ }
}
-.dark {
- --color-background: #020617;
- --color-foreground: #f8fafc;
+.reveal-blur.is-visible {
+ animation: reveal-blur 1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
diff --git a/vite.config.ts b/vite.config.ts
index b176012..e1dda2c 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,8 @@
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-import { resolve, dirname } from 'path';
-import { fileURLToPath } from 'url';
-import dts from 'vite-plugin-dts';
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import { resolve, dirname } from "path";
+import { fileURLToPath } from "url";
+import dts from "vite-plugin-dts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -12,38 +12,38 @@ export default defineConfig({
plugins: [
react(),
dts({
- include: ['src'],
- exclude: ['**/*.test.tsx', '**/*.test.ts', '**/*.stories.tsx'],
+ include: ["src"],
+ exclude: ["**/*.test.tsx", "**/*.test.ts"],
rollupTypes: false,
}),
],
resolve: {
alias: {
- '@': resolve(__dirname, './src'),
- '@/components': resolve(__dirname, './src/components'),
- '@/hooks': resolve(__dirname, './src/hooks'),
- '@/utils': resolve(__dirname, './src/utils'),
- '@/theme': resolve(__dirname, './src/theme'),
+ "@": resolve(__dirname, "./src"),
+ "@/components": resolve(__dirname, "./src/components"),
+ "@/hooks": resolve(__dirname, "./src/hooks"),
+ "@/utils": resolve(__dirname, "./src/utils"),
+ "@/theme": resolve(__dirname, "./src/theme"),
},
},
build: {
lib: {
- entry: resolve(__dirname, 'src/index.ts'),
- name: 'UILibrary',
- formats: ['es', 'cjs'],
- fileName: (format) => `index.${format === 'es' ? 'es' : 'cjs'}.js`,
+ entry: resolve(__dirname, "src/index.ts"),
+ name: "UILibrary",
+ formats: ["es", "cjs"],
+ fileName: (format) => `index.${format === "es" ? "es" : "cjs"}.js`,
},
rollupOptions: {
- external: ['react', 'react-dom', 'react/jsx-runtime'],
+ external: ["react", "react-dom", "react/jsx-runtime"],
output: {
globals: {
- react: 'React',
- 'react-dom': 'ReactDOM',
- 'react/jsx-runtime': 'react/jsx-runtime',
+ react: "React",
+ "react-dom": "ReactDOM",
+ "react/jsx-runtime": "react/jsx-runtime",
},
assetFileNames: (assetInfo) => {
- if (assetInfo.name === 'style.css') return 'style.css';
- return assetInfo.name || '';
+ if (assetInfo.name === "style.css") return "style.css";
+ return assetInfo.name || "";
},
},
},