diff --git a/commitlint.config.js b/commitlint.config.js
index 3fd1fba..fbdae80 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -18,5 +18,13 @@ export default {
'revert',
],
],
+
+ 'type-case': [2, 'always', 'lower-case'],
+ 'subject-empty': [2, 'never'],
+ 'subject-case': [
+ 2,
+ 'never',
+ ['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
+ ],
},
-};
+};
\ No newline at end of file
diff --git a/index.html b/index.html
index 6821334..f30e919 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,8 @@
-
ui-library
+
+ Awesome UI Library
diff --git a/package-lock.json b/package-lock.json
index f6da95a..df0e890 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "ui-library",
"version": "0.0.0",
"dependencies": {
+ "@floating-ui/react": "^0.27.19",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
@@ -15,6 +16,9 @@
"@commitlint/cli": "^20.4.1",
"@commitlint/config-conventional": "^20.4.1",
"@eslint/js": "^9.39.1",
+ "@mdx-js/react": "^3.1.1",
+ "@mdx-js/rollup": "^3.1.1",
+ "@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
@@ -38,6 +42,7 @@
"playwright": "^1.58.2",
"postcss": "^8.4.49",
"prettier": "^3.8.1",
+ "react-router-dom": "^7.13.1",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^3.4.17",
"typescript": "~5.7.0",
@@ -160,6 +165,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -808,6 +814,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -848,6 +855,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -1051,6 +1059,59 @@
}
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.27.19",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.19.tgz",
+ "integrity": "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.8",
+ "@floating-ui/utils": "^0.2.11",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0",
+ "react-dom": ">=17.0.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1176,6 +1237,112 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@mdx-js/mdx": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
+ "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdx": "^2.0.0",
+ "acorn": "^8.0.0",
+ "collapse-white-space": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "estree-util-scope": "^1.0.0",
+ "estree-walker": "^3.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "markdown-extensions": "^2.0.0",
+ "recma-build-jsx": "^1.0.0",
+ "recma-jsx": "^1.0.0",
+ "recma-stringify": "^1.0.0",
+ "rehype-recma": "^1.0.0",
+ "remark-mdx": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "source-map": "^0.7.0",
+ "unified": "^11.0.0",
+ "unist-util-position-from-estree": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/@mdx-js/mdx/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/@mdx-js/mdx/node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "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/@mdx-js/rollup": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@mdx-js/rollup/-/rollup-3.1.1.tgz",
+ "integrity": "sha512-v8satFmBB+DqDzYohnm1u2JOvxx6Hl3pUvqzJvfs2Zk/ngZ1aRUhsWpXvwPkNeGN9c2NCm/38H29ZqXQUjf8dw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@mdx-js/mdx": "^3.0.0",
+ "@rollup/pluginutils": "^5.0.0",
+ "source-map": "^0.7.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "rollup": ">=2"
+ }
+ },
+ "node_modules/@mdx-js/rollup/node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/@microsoft/api-extractor": {
"version": "7.56.3",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.56.3.tgz",
@@ -1912,6 +2079,33 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
+ "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "6.0.10"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
+ }
+ },
+ "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
@@ -2014,8 +2208,7 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -2073,6 +2266,16 @@
"assertion-error": "^2.0.1"
}
},
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -2087,6 +2290,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2094,12 +2317,37 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "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/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "24.10.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
"integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -2110,6 +2358,7 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2120,10 +2369,18 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "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",
@@ -2169,6 +2426,7 @@
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
@@ -2380,6 +2638,13 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/@vitejs/plugin-react": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
@@ -2407,6 +2672,7 @@
"integrity": "sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/mocker": "4.0.18",
"@vitest/utils": "4.0.18",
@@ -2430,6 +2696,7 @@
"integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/browser": "4.0.18",
"@vitest/mocker": "4.0.18",
@@ -2574,6 +2841,7 @@
"integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/utils": "4.0.18",
"fflate": "^0.8.2",
@@ -2746,6 +3014,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3083,6 +3352,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/astring": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
+ "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "astring": "bin/astring"
+ }
+ },
"node_modules/async-function": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
@@ -3166,6 +3445,17 @@
"node": ">= 0.4"
}
},
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3250,6 +3540,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3355,6 +3646,17 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3372,6 +3674,50 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -3531,6 +3877,17 @@
"node": ">=6"
}
},
+ "node_modules/collapse-white-space": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
+ "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3558,6 +3915,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/commander": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
@@ -3649,12 +4017,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
@@ -3896,6 +4279,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/decode-named-character-reference": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3949,6 +4346,20 @@
"node": ">=6"
}
},
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -3978,8 +4389,7 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/dot-prop": {
"version": "5.3.0",
@@ -4225,19 +4635,53 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "node_modules/esast-util-from-estree": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz",
+ "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-visit": "^2.0.0",
+ "unist-util-position-from-estree": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "node_modules/esast-util-from-js": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz",
+ "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "acorn": "^8.0.0",
+ "esast-util-from-estree": "^2.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
@@ -4254,6 +4698,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -4486,6 +4931,114 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-util-attach-comments": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz",
+ "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/estree-util-build-jsx": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz",
+ "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "estree-walker": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/estree-util-build-jsx/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/estree-util-scope": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz",
+ "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "devlop": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/estree-util-to-js": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz",
+ "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "astring": "^1.8.0",
+ "source-map": "^0.7.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/estree-util-to-js/node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/estree-util-visit": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz",
+ "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -4527,6 +5080,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5099,6 +5659,77 @@
"node": ">= 0.4"
}
},
+ "node_modules/hast-util-to-estree": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz",
+ "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-attach-comments": "^3.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -5278,6 +5909,13 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -5293,6 +5931,32 @@
"node": ">= 0.4"
}
},
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -5448,6 +6112,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -5523,6 +6198,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/is-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
@@ -5847,6 +6533,7 @@
"integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.31",
"@asamuzakjp/dom-selector": "^6.8.1",
@@ -6189,6 +6876,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -6205,7 +6903,6 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -6248,6 +6945,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/markdown-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz",
+ "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -6258,92 +6968,895 @@
"node": ">= 0.4"
}
},
- "node_modules/mdn-data": {
- "version": "2.12.2",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
- "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/meow": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
- "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "node_modules/mdast-util-mdx": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz",
+ "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 8"
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
},
- "engines": {
- "node": ">=8.6"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/micromatch/node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8.6"
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/mimic-function": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
- "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/min-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
- "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=4"
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/meow": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
+ "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-mdx-expression": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz",
+ "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-mdx-expression": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-events-to-acorn": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-mdx-jsx": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz",
+ "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "micromark-factory-mdx-expression": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-events-to-acorn": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-mdx-md": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz",
+ "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-mdxjs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz",
+ "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.0.0",
+ "acorn-jsx": "^5.0.0",
+ "micromark-extension-mdx-expression": "^3.0.0",
+ "micromark-extension-mdx-jsx": "^3.0.0",
+ "micromark-extension-mdx-md": "^2.0.0",
+ "micromark-extension-mdxjs-esm": "^3.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-mdxjs-esm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz",
+ "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-events-to-acorn": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-position-from-estree": "^2.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-mdx-expression": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz",
+ "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-events-to-acorn": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-position-from-estree": "^2.0.0",
+ "vfile-message": "^4.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-events-to-acorn": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz",
+ "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/unist": "^3.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-visit": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "vfile-message": "^4.0.0"
+ }
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -6695,6 +8208,33 @@
"node": ">=6"
}
},
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -6852,6 +8392,7 @@
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"playwright-core": "1.58.2"
},
@@ -6918,6 +8459,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -7093,7 +8635,6 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -7109,7 +8650,6 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10"
},
@@ -7117,6 +8657,17 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -7170,6 +8721,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -7179,6 +8731,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -7191,8 +8744,7 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/react-refresh": {
"version": "0.18.0",
@@ -7204,6 +8756,46 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
+ "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
+ "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.13.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -7240,6 +8832,77 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/recma-build-jsx": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz",
+ "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-util-build-jsx": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/recma-jsx": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz",
+ "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn-jsx": "^5.0.0",
+ "estree-util-to-js": "^2.0.0",
+ "recma-parse": "^1.0.0",
+ "recma-stringify": "^1.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/recma-parse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz",
+ "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "esast-util-from-js": "^2.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/recma-stringify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz",
+ "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-util-to-js": "^2.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -7311,6 +8974,72 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/rehype-recma": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz",
+ "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "hast-util-to-estree": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-mdx": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz",
+ "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-mdx": "^3.0.0",
+ "micromark-extension-mdxjs": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -7403,6 +9132,7 @@
"integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -7553,6 +9283,13 @@
"node": ">=10"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -7786,6 +9523,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -7932,6 +9680,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
@@ -7974,6 +9737,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/style-to-js": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "style-to-object": "1.0.14"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
"node_modules/sucrase": {
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
@@ -8040,6 +9823,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tabbable": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
+ "license": "MIT"
+ },
"node_modules/tailwind-merge": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
@@ -8057,6 +9846,7 @@
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -8235,6 +10025,28 @@
"node": ">=20"
}
},
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
@@ -8352,6 +10164,7 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8427,6 +10240,113 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position-from-estree": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz",
+ "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -8485,12 +10405,43 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/vite": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -9092,6 +11043,7 @@
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
@@ -9600,6 +11552,7 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -9616,6 +11569,17 @@
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
}
}
}
diff --git a/package.json b/package.json
index 61c8274..fa01f39 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,9 @@
"private": true,
"version": "0.0.0",
"type": "module",
+ "sideEffects": [
+ "*.css"
+ ],
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
@@ -14,6 +17,7 @@
"test:coverage": "vitest run --coverage"
},
"dependencies": {
+ "@floating-ui/react": "^0.27.19",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
@@ -21,6 +25,9 @@
"@commitlint/cli": "^20.4.1",
"@commitlint/config-conventional": "^20.4.1",
"@eslint/js": "^9.39.1",
+ "@mdx-js/react": "^3.1.1",
+ "@mdx-js/rollup": "^3.1.1",
+ "@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
@@ -44,6 +51,7 @@
"playwright": "^1.58.2",
"postcss": "^8.4.49",
"prettier": "^3.8.1",
+ "react-router-dom": "^7.13.1",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^3.4.17",
"typescript": "~5.7.0",
diff --git a/src/App.tsx b/src/App.tsx
index 8d2452f..a8a5180 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,11 +1,24 @@
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { ThemeProvider } from "@/theme";
import Playground from "./Playground";
+import DocsApp from "./docs/DocsApp";
import "@/theme/index.css";
function App() {
return (
-
+
+
+ {/* Playground Route */}
+ } />
+
+ {/* Documentation Route */}
+ } />
+
+ {/* Fallback */}
+ } />
+
+
);
}
diff --git a/src/Playground.tsx b/src/Playground.tsx
index a83c306..c3f5343 100644
--- a/src/Playground.tsx
+++ b/src/Playground.tsx
@@ -1,4 +1,5 @@
import { useState } from "react";
+import { Link } from "react-router-dom";
import { Button } from "@/components/Button";
import { Input } from "@/components/Input";
import { Modal } from "@/components/Modal";
@@ -8,9 +9,12 @@ 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 { Checkbox } from "@/components/Checkbox";
import { ScrollArea, ScrollElement } from "@/components/ScrollArea";
-import RadioGroup from "@/components/RadioGroup";
+import { RadioGroup } from "@/components/RadioGroup";
+import Switch from "@/components/Switch/Switch";
+import Popover from "@/components/Popover/Popover";
+import { Dropdown } from "@/components/Dropdown/Dropdown";
import reactLogo from "@/assets/react.svg";
import type { ThemeOverride } from "@/theme";
@@ -167,6 +171,9 @@ export default function Playground() {
const [isSimulatingData, setIsSimulatingData] = useState(true);
const [radioValue, setRadioValue] = useState("option-1");
+ const [dropdownValue, setDropdownValue] = useState("");
+ const [paymentValue, setPaymentValue] = useState("");
+ const [switchState, setSwitchState] = useState(false);
const [checkedItems, setCheckedItems] = useState({
item1: false,
@@ -219,6 +226,13 @@ export default function Playground() {
Interactive playground for testing components
+
+
+
+ Documentation
+
+
+
@@ -1454,6 +1468,98 @@ export default function Playground() {
+ {/* Switch Section */}
+
+
+
+ Switches
+
+
+ Binary toggle controls with support for labels, loading states, and
+ different sizes.
+
+
+
+
+ {/* Basic Usage */}
+
+
+
+ Interactive State
+
+
+
+
+
+
+
+
+
+
+ {/* Sizes & States */}
+
+
+
+ Sizes & States
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Labels & Descriptions */}
+
+
+ Labels & Descriptions
+
+
+
+
+
+
+
+
+
{/* ScrollArea Section */}
+ {/* Popover Section */}
+
+
+
+ Popover
+
+
+ Smart, accessible floating popovers powered by @floating-ui/react.
+
+
+
+
+
+
+ Interactive Popover
+
+
+ This popover handles clicks naturally. Notice how focus is
+ trapped correctly.
+
+
+
+ Acknowledge
+
+
+
+ }
+ >
+ Open Dialog Popover
+
+
+
+
+ Aligned Top
+
+
+ Fades and shifts if screen is too small.
+
+
+ }
+ >
+ Top Info
+
+
+
+
+
+ Real-World Use Cases
+
+
+
+ {/* Real Case 1: Profile Menu */}
+
+
+ Profile Menu
+
+
+
+
+ John Doe
+
+
+ john.doe@enterprise.com
+
+
+
+ Account Settings
+
+
+ Billing & Teams
+
+
+ Help & Support
+
+
+
+ Sign Out
+
+
+ }
+ >
+
+
+
+
+ {/* Real Case 2: Advanced Filter */}
+
+ }
+ >
+
+ Dimensions{" "}
+
+ 3
+
+
+
+
+
+ {/* Real Case 3: Interactive Info Card */}
+
+
+ System Info Tooltip
+
+
+
+
+
+ Systems Operational
+
+
+ All database clusters and API endpoints are currently
+ reporting 100% uptime in the US-East region.
+
+
+ View Detailed Status →
+
+
+
+ }
+ >
+
+ i
+
+
+
+
+
+
+
+ {/* Dropdown Section */}
+
+
+
+ Dropdown
+
+
+ Accessible select menus with keyboard navigation, validation, sizes,
+ and loading states.
+
+
+
+
+ {/* Interactive */}
+
+
+
+ Interactive
+
+
+
+ setDropdownValue(val)}
+ />
+ {dropdownValue && (
+
+ Selected: {dropdownValue}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
setPaymentValue(val)}
+ options={[
+ {
+ label: "Credit Card",
+ value: "card",
+ icon: (
+
+
+
+
+ ),
+ },
+ {
+ label: "PayPal",
+ value: "paypal",
+ icon: (
+
+
+
+ ),
+ },
+ {
+ label: "Bank Transfer",
+ value: "bank",
+ icon: (
+
+
+
+
+ ),
+ },
+ {
+ label: "Crypto",
+ value: "crypto",
+ icon: (
+
+
+
+
+
+ ),
+ disabled: true,
+ },
+ ]}
+ />
+
+
+
+ {/* States */}
+
+
+
+ States
+
+
+
+
+
+
+
+
+
+ {/* Sizes */}
+
+
+
+ Sizes
+
+
+
+
+
+
+
+
+
+
+
{/* Modal */}
{
+export const CheckIcon = ({
+ size,
+ className,
+}: {
+ size?: string;
+ className?: string;
+}) => {
return (
{
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index c503789..6074c17 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -173,7 +173,7 @@ const Button = forwardRef(
{isLoading ? (
<>
- {children && {children} }
+ {children}
>
) : (
<>
@@ -185,7 +185,7 @@ const Button = forwardRef(
{prefixIcon}
)}
- {children && {children} }
+ {children}
{suffixIcon && (
{
diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx
index 2233595..b0e2d29 100644
--- a/src/components/Checkbox/Checkbox.tsx
+++ b/src/components/Checkbox/Checkbox.tsx
@@ -1,7 +1,7 @@
import { forwardRef, useEffect, useRef, useImperativeHandle } from "react";
import type { CheckboxProps } from "./Checkbox.types";
import { useTheme } from "@/theme";
-import Skeleton from "../Skeleton";
+import { Skeleton } from "../Skeleton";
import { cn } from "@/utils/cn";
import {
descriptionSizeClasses,
@@ -111,7 +111,7 @@ const Checkbox = forwardRef(
(
"peer-checked:[&_.check-icon]:opacity-100 peer-checked:[&_.check-icon]:scale-100",
"peer-indeterminate:border-[var(--primary)] peer-indeterminate:bg-[var(--primary)]",
"peer-indeterminate:[&_.indeterminate-icon]:opacity-100 peer-indeterminate:[&_.indeterminate-icon]:scale-100",
- "peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--ring)] peer-focus-visible:ring-offset-2",
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--ring)]",
)}
>
{/* Check Icon */}
@@ -163,4 +163,4 @@ const Checkbox = forwardRef
(
Checkbox.displayName = "Checkbox";
-export default Checkbox;
+export { Checkbox };
diff --git a/src/components/Checkbox/index.ts b/src/components/Checkbox/index.ts
index dc638ad..e5bcad8 100644
--- a/src/components/Checkbox/index.ts
+++ b/src/components/Checkbox/index.ts
@@ -1,3 +1,3 @@
-export { default } from "./Checkbox";
+export { Checkbox } from "./Checkbox";
export * from "./Checkbox.types";
export * from "./Checkbox.constants";
diff --git a/src/components/Dropdown/Dropdown.constants.ts b/src/components/Dropdown/Dropdown.constants.ts
new file mode 100644
index 0000000..fe6019d
--- /dev/null
+++ b/src/components/Dropdown/Dropdown.constants.ts
@@ -0,0 +1,16 @@
+import type { DropdownProps } from "./Dropdown.types";
+
+export const SKELETON_SIZE_MAP: Record<
+ NonNullable,
+ { height: string; width: string }
+> = {
+ sm: { height: "28px", width: "100%" },
+ md: { height: "32px", width: "100%" },
+ lg: { height: "36px", width: "100%" },
+};
+
+export const DROPDOWN_SIZE_MAP = {
+ sm: "h-8 px-2 text-xs",
+ md: "h-10 px-3 text-sm",
+ lg: "h-12 px-4 text-base",
+};
diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx
new file mode 100644
index 0000000..6e13244
--- /dev/null
+++ b/src/components/Dropdown/Dropdown.tsx
@@ -0,0 +1,521 @@
+import {
+ forwardRef,
+ useCallback,
+ useEffect,
+ useId,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import {
+ autoUpdate,
+ flip,
+ offset as offsetMiddleware,
+ size as sizeMiddleware,
+ useFloating,
+} from "@floating-ui/react";
+import type { DropdownProps } from "./Dropdown.types";
+import { DROPDOWN_SIZE_MAP, SKELETON_SIZE_MAP } from "./Dropdown.constants";
+import { Skeleton } from "../Skeleton";
+import { useTheme } from "@/theme";
+import { cn } from "@/utils";
+import { Chevron } from "@/assets/Icons/Chevron";
+import { SearchIcon } from "@/assets/Icons/SearchIcon";
+import { Input } from "@/components/Input";
+
+/* ─── Dropdown ─────────────────────────────────────────────────────────────── */
+
+const Dropdown = forwardRef((props, ref) => {
+ const uid = useId();
+ const {
+ id = uid,
+ options,
+ size = "md",
+ isLoading,
+ onChange,
+ label,
+ required = false,
+ selectedValue,
+ defaultOpen,
+ isOpen,
+ error,
+ disabled,
+ offset = 4,
+ className,
+ zIndex,
+ placeholder = "Select option",
+ searchable = false,
+ searchPlaceholder = "Search...",
+ } = props;
+
+ const { theme } = useTheme();
+ const isDark = theme.colorMode === "dark";
+
+ /* ── state ─────────────────────────────────────────────────────────────── */
+
+ const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);
+ const [highlighted, setHighlighted] = useState(-1);
+ const [search, setSearch] = useState("");
+ const triggerRef = useRef(null);
+ const panelRef = useRef(null);
+ const searchRef = useRef(null);
+
+ const controlled = isOpen !== undefined;
+ const open = controlled ? isOpen : internalOpen;
+
+ /* ── filtered options ────────────────────────────────────────────────────── */
+
+ const filtered = useMemo(() => {
+ if (!searchable || !search.trim()) return options;
+ const q = search.toLowerCase();
+ return options.filter((o) => {
+ const text = typeof o.label === "string" ? o.label : String(o.label);
+ return text.toLowerCase().includes(q);
+ });
+ }, [options, search, searchable]);
+
+ /* ── floating-ui ───────────────────────────────────────────────────────── */
+
+ const { refs, floatingStyles } = useFloating({
+ open,
+ placement: "bottom-start",
+ middleware: [
+ offsetMiddleware(offset),
+ flip({ fallbackAxisSideDirection: "start" }),
+ sizeMiddleware({
+ apply({ rects, elements }) {
+ Object.assign(elements.floating.style, {
+ width: `${rects.reference.width}px`,
+ maxWidth: `${rects.reference.width}px`,
+ });
+ },
+ }),
+ ],
+ whileElementsMounted: autoUpdate,
+ });
+
+ /* ── toggle / select ───────────────────────────────────────────────────── */
+
+ const toggle = useCallback(
+ (next: boolean) => {
+ if (disabled) return;
+ if (!controlled) setInternalOpen(next);
+ if (next) {
+ const idx = options.findIndex((o) => o.value === selectedValue);
+ setHighlighted(idx >= 0 ? idx : -1);
+ setSearch("");
+ } else {
+ setHighlighted(-1);
+ setSearch("");
+ }
+ },
+ [disabled, controlled, options, selectedValue],
+ );
+
+ const select = useCallback(
+ (v: string) => {
+ if (disabled) return;
+ onChange?.(v);
+ toggle(false);
+ triggerRef.current?.focus();
+ },
+ [disabled, onChange, toggle],
+ );
+
+ /* ── side-effects ──────────────────────────────────────────────────────── */
+
+ // Focus search input when panel opens
+ useEffect(() => {
+ if (open && searchable) {
+ requestAnimationFrame(() => searchRef.current?.focus());
+ }
+ }, [open, searchable]);
+
+ // Close on outside click
+ useEffect(() => {
+ if (!open) return;
+ const handler = (e: PointerEvent) => {
+ const t = e.target as Node;
+ if (triggerRef.current?.contains(t) || panelRef.current?.contains(t))
+ return;
+ toggle(false);
+ };
+ document.addEventListener("pointerdown", handler);
+ return () => document.removeEventListener("pointerdown", handler);
+ }, [open, toggle]);
+
+ // Scroll highlighted item into view
+ useEffect(() => {
+ if (!open || highlighted < 0 || !panelRef.current) return;
+ const items =
+ panelRef.current.querySelectorAll("button[data-item]");
+ items[highlighted]?.scrollIntoView({ block: "nearest" });
+ }, [highlighted, open]);
+
+ /* ── keyboard ──────────────────────────────────────────────────────────── */
+
+ const step = useCallback(
+ (from: number, dir: 1 | -1) => {
+ const list = filtered;
+ let i = from;
+ for (const _ of list) {
+ void _;
+ i += dir;
+ if (i >= list.length) i = 0;
+ if (i < 0) i = list.length - 1;
+ if (!list[i]?.disabled) return i;
+ }
+ return from;
+ },
+ [filtered],
+ );
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (disabled) return;
+ const list = filtered;
+ switch (e.key) {
+ case "ArrowDown":
+ e.preventDefault();
+ if (!open) {
+ toggle(true);
+ setHighlighted(
+ Math.max(
+ list.findIndex((o) => o.value === selectedValue),
+ 0,
+ ),
+ );
+ } else {
+ setHighlighted((p) => step(p, 1));
+ }
+ break;
+ case "ArrowUp":
+ e.preventDefault();
+ if (!open) {
+ toggle(true);
+ setHighlighted(list.length - 1);
+ } else {
+ setHighlighted((p) => step(p, -1));
+ }
+ break;
+ case "Enter":
+ e.preventDefault();
+ if (open && highlighted >= 0) {
+ const o = list[highlighted];
+ if (o && !o.disabled) select(o.value);
+ } else if (!open) {
+ toggle(true);
+ }
+ break;
+ case " ":
+ // Allow space in search input
+ if (searchable && open) return;
+ e.preventDefault();
+ if (open && highlighted >= 0) {
+ const o = list[highlighted];
+ if (o && !o.disabled) select(o.value);
+ } else if (!open) {
+ toggle(true);
+ }
+ break;
+ case "Escape":
+ if (open) {
+ e.preventDefault();
+ toggle(false);
+ triggerRef.current?.focus();
+ }
+ break;
+ case "Home":
+ if (open) {
+ e.preventDefault();
+ const f = list.findIndex((o) => !o.disabled);
+ if (f >= 0) setHighlighted(f);
+ }
+ break;
+ case "End":
+ if (open) {
+ e.preventDefault();
+ const l = [...list].reverse().findIndex((o) => !o.disabled);
+ if (l >= 0) setHighlighted(list.length - 1 - l);
+ }
+ break;
+ }
+ },
+ [
+ disabled,
+ open,
+ filtered,
+ selectedValue,
+ highlighted,
+ toggle,
+ select,
+ step,
+ searchable,
+ ],
+ );
+
+ /* ── ref merging ───────────────────────────────────────────────────────── */
+
+ const setTrigger = useCallback(
+ (n: HTMLButtonElement | null) => {
+ triggerRef.current = n;
+ refs.setReference(n);
+ if (typeof ref === "function") ref(n);
+ else if (ref) ref.current = n;
+ },
+ [ref, refs],
+ );
+
+ const setPanel = useCallback(
+ (n: HTMLDivElement | null) => {
+ panelRef.current = n;
+ refs.setFloating(n);
+ },
+ [refs],
+ );
+
+ /* ── loading ───────────────────────────────────────────────────────────── */
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ /* ── derived ───────────────────────────────────────────────────────────── */
+
+ const chosen = options.find((o) => o.value === selectedValue);
+ const listboxId = `${id}-listbox`;
+
+ const vars = {
+ "--dd-radius": theme.shape.radiusMd,
+ "--dd-radius-lg": theme.shape.radiusLg,
+ "--dd-border": error ? theme.palette.error[500] : theme.tokens.borderStrong,
+ "--dd-border-subtle": theme.tokens.borderSubtle,
+ "--dd-ring": error ? theme.palette.error[500] : theme.tokens.ring,
+ "--dd-bg": theme.tokens.input,
+ "--dd-bg-hover": theme.tokens.inputHover,
+ "--dd-text": theme.tokens.foreground,
+ "--dd-placeholder": theme.tokens.foregroundSubtle,
+ "--dd-label": theme.tokens.foreground,
+ "--dd-error": theme.palette.error[500],
+ "--dd-elevated": theme.tokens.elevated,
+ "--dd-muted": theme.tokens.foregroundMuted,
+ "--dd-accent": theme.tokens.accent,
+ "--dd-item-bg": `${theme.tokens.accent}18`,
+ "--dd-hover-bg": `${theme.tokens.foreground}0f`,
+ "--dd-panel-bg": theme.tokens.elevated,
+ "--dd-panel-border": theme.tokens.borderSubtle,
+ "--dd-panel-shadow": isDark
+ ? `0 8px 32px ${theme.neutral[50]}80`
+ : `0 8px 32px ${theme.neutral[400]}14`,
+ } as React.CSSProperties;
+
+ /* ── render ────────────────────────────────────────────────────────────── */
+
+ return (
+
+ {label && (
+
+ {label}
+ {required && * }
+
+ )}
+
+
+ {/* ── trigger ──────────────────────────────────────────────────── */}
+
toggle(!open)}
+ onKeyDown={!searchable || !open ? handleKeyDown : undefined}
+ className={cn(
+ "flex items-center justify-between w-full",
+ "border border-[var(--dd-border)] bg-[var(--dd-bg)]",
+ "focus:outline-none focus:ring-2 focus:ring-[var(--dd-ring)] focus:border-transparent",
+ "hover:bg-[var(--dd-bg-hover)]",
+ "rounded-[var(--dd-radius)] transition-all duration-150",
+ DROPDOWN_SIZE_MAP[size],
+ chosen ? "text-[var(--dd-text)]" : "text-[var(--dd-placeholder)]",
+ disabled && "opacity-50 cursor-not-allowed",
+ className,
+ )}
+ >
+
+ {chosen?.icon && (
+
+ {chosen.icon}
+
+ )}
+ {chosen?.label || placeholder}
+
+
+
+
+ {/* ── panel ────────────────────────────────────────────────────── */}
+ {open && (
+
+
+ {/* ── search input ─────────────────────────────────────── */}
+ {searchable && (
+
+ {
+ const val = e.target.value;
+ setSearch(val);
+ const q = val.toLowerCase();
+ const matches = q
+ ? options.filter((o) => {
+ const t =
+ typeof o.label === "string"
+ ? o.label
+ : String(o.label);
+ return t.toLowerCase().includes(q);
+ })
+ : options;
+ setHighlighted(matches.length > 0 ? 0 : -1);
+ }}
+ onKeyDown={handleKeyDown}
+ placeholder={searchPlaceholder}
+ leftIcon={ }
+ aria-label="Search options"
+ />
+
+ )}
+
+ {/* ── options list ─────────────────────────────────────── */}
+
+ {filtered.length === 0 ? (
+
+ {searchable && search
+ ? "No results found"
+ : "No options available"}
+
+ ) : (
+ filtered.map((option, i) => {
+ const active = option.value === selectedValue;
+ const lit = i === highlighted;
+
+ return (
+
select(option.value)}
+ onMouseEnter={() => setHighlighted(i)}
+ onMouseLeave={() => setHighlighted(-1)}
+ className={cn(
+ "text-[13px] text-left outline-none",
+ "flex items-center justify-between",
+ "mx-1 px-2 py-1.5 rounded-sm",
+ "transition-colors duration-75",
+
+ active && [
+ "bg-[var(--dd-item-bg)] backdrop-blur-sm",
+ "text-[var(--dd-accent)]",
+ "font-medium",
+ ],
+
+ !active && "text-[var(--dd-text)]",
+
+ lit &&
+ !active &&
+ "bg-[var(--dd-hover-bg)] backdrop-blur-sm",
+ !active &&
+ !option.disabled &&
+ "hover:bg-[var(--dd-hover-bg)] hover:backdrop-blur-sm",
+
+ option.disabled
+ ? "opacity-40 cursor-not-allowed"
+ : "cursor-pointer",
+ )}
+ style={{ width: "calc(100% - 8px)" }}
+ >
+
+ {option.icon && (
+
+ {option.icon}
+
+ )}
+ {option.label}
+
+ {active && (
+
+
+
+
+
+ )}
+
+ );
+ })
+ )}
+
+
+
+ )}
+
+
+ {error && typeof error === "string" && (
+
+ {error}
+
+ )}
+
+ );
+});
+
+Dropdown.displayName = "Dropdown";
+
+export { Dropdown };
diff --git a/src/components/Dropdown/Dropdown.types.ts b/src/components/Dropdown/Dropdown.types.ts
new file mode 100644
index 0000000..078c984
--- /dev/null
+++ b/src/components/Dropdown/Dropdown.types.ts
@@ -0,0 +1,60 @@
+export interface DropdownOption {
+ label: React.ReactNode;
+ value: string;
+ disabled?: boolean;
+ /** Icon element rendered before the label */
+ icon?: React.ReactNode;
+}
+
+export interface DropdownProps {
+ id?: string;
+
+ /** Controlled open state */
+ isOpen?: boolean;
+
+ /** Uncontrolled initial state */
+ defaultOpen?: boolean;
+
+ /** Loading state */
+ isLoading?: boolean;
+
+ /** Size variant */
+ size?: "sm" | "md" | "lg";
+
+ /** Value change handler */
+ onChange?: (value: string) => void;
+
+ label?: React.ReactNode;
+
+ required?: boolean;
+
+ /** Currently selected value */
+ selectedValue?: string;
+
+ /** List of options */
+ options: DropdownOption[];
+
+ /** Error message */
+ error?: string;
+
+ /** Placeholder text when no option is selected */
+ placeholder?: string;
+
+ /** Disable dropdown */
+ disabled?: boolean;
+
+ /** Offset from trigger */
+ offset?: number;
+
+ /** Custom class */
+ className?: string;
+
+ /** z-index */
+ zIndex?: number;
+
+ /** Enable search/filter input inside the dropdown */
+ searchable?: boolean;
+
+ /** Placeholder for the search input */
+ searchPlaceholder?: string;
+}
diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx
index f92357e..43bc8cd 100644
--- a/src/components/Input/Input.tsx
+++ b/src/components/Input/Input.tsx
@@ -80,7 +80,7 @@ const Input = forwardRef(
className={cn(
"group relative flex w-full items-center gap-2 px-3 rounded-[var(--radius)] border bg-[var(--bg)] transition-all duration-200",
"border-[var(--border)]",
- "focus-within:ring-2 focus-within:ring-[var(--ring)] focus-within:ring-offset-2",
+ "focus-within:ring-2 focus-within:ring-[var(--ring)]",
disabled && "opacity-60 cursor-not-allowed",
)}
>
diff --git a/src/components/Input/index.ts b/src/components/Input/index.ts
index 35d2bdc..901daca 100644
--- a/src/components/Input/index.ts
+++ b/src/components/Input/index.ts
@@ -1 +1,2 @@
-export { Input, type InputProps } from './Input';
+export { Input } from "./Input";
+export type { InputProps } from "./Input.types";
diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
index 316fe47..1015da6 100644
--- a/src/components/Modal/Modal.tsx
+++ b/src/components/Modal/Modal.tsx
@@ -6,7 +6,7 @@ import type { ModalProps } from "./Modal.types";
import { MODAL_SIZES } from "./Modal.types";
import { CloseIcon } from "@/assets/Icons/CloseIcon";
-export const Modal: FC = ({
+const Modal: FC = ({
id,
isOpen,
onClose,
@@ -108,4 +108,4 @@ export const Modal: FC = ({
Modal.displayName = "Modal";
-export default Modal;
+export { Modal };
diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx
new file mode 100644
index 0000000..92b12bf
--- /dev/null
+++ b/src/components/Popover/Popover.tsx
@@ -0,0 +1,291 @@
+import React, { forwardRef, useState, useId, useCallback } from "react";
+import type { PopoverProps } from "./Popover.types";
+import {
+ useFloating,
+ autoUpdate,
+ offset as offsetMiddleware,
+ flip,
+ shift,
+ size as sizeMiddleware,
+ arrow as arrowMiddleware,
+ useClick,
+ useDismiss,
+ useRole,
+ useInteractions,
+ FloatingFocusManager,
+ useMergeRefs,
+ useTransitionStyles,
+ FloatingPortal,
+ FloatingArrow,
+} from "@floating-ui/react";
+import { useRef } from "react";
+import { useTheme } from "@/theme";
+import { cn } from "@/utils";
+
+const ARROW_HEIGHT = 8;
+const ARROW_WIDTH = 8;
+const ARROW_OFFSET = ARROW_HEIGHT;
+
+const Popover = forwardRef((props, ref) => {
+ const { theme } = useTheme();
+ const generatedId = useId();
+
+ const {
+ children,
+ id = generatedId,
+ isOpen,
+ defaultOpen = false,
+ onOpenChange,
+ content,
+ placement = "bottom",
+ offset = 8,
+ showArrow = false,
+ disabled = false,
+ className,
+ zIndex = 1000,
+ usePortal = true,
+ portalContainer,
+ matchTriggerWidth = false,
+ keepTriggerFocus = false,
+ } = props;
+
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
+ const isControlled = isOpen !== undefined;
+ const open = isControlled ? isOpen : internalOpen;
+
+ const handleOpenChange = useCallback(
+ (newOpen: boolean) => {
+ if (disabled) return;
+ if (!isControlled) setInternalOpen(newOpen);
+ onOpenChange?.(newOpen);
+ },
+ [disabled, isControlled, onOpenChange],
+ );
+
+ const arrowRef = useRef(null);
+
+ const {
+ refs: floatingRefs,
+ floatingStyles,
+ context,
+ } = useFloating({
+ open,
+ onOpenChange: handleOpenChange,
+ placement,
+ middleware: [
+ offsetMiddleware(offset + (showArrow ? ARROW_OFFSET : 0)),
+ flip({ fallbackAxisSideDirection: "start" }),
+ shift({ padding: 8 }),
+ ...(matchTriggerWidth
+ ? [
+ sizeMiddleware({
+ apply({ rects, elements }) {
+ Object.assign(elements.floating.style, {
+ width: `${rects.reference.width}px`,
+ });
+ },
+ }),
+ ]
+ : []),
+ /* eslint-disable react-hooks/refs */
+ ...(showArrow
+ ? [arrowMiddleware({ element: arrowRef, padding: 8 })]
+ : []),
+ /* eslint-enable react-hooks/refs */
+ ],
+ whileElementsMounted: autoUpdate,
+ });
+
+ const { setReference, setFloating } = floatingRefs;
+ const mergedRef = useMergeRefs([setReference, ref]);
+
+ const click = useClick(context, {
+ enabled: !disabled,
+ keyboardHandlers: !keepTriggerFocus,
+ });
+ const dismiss = useDismiss(context, { outsidePressEvent: "mousedown" });
+ const role = useRole(context, {
+ role: keepTriggerFocus ? "listbox" : "dialog",
+ });
+
+ const { getReferenceProps, getFloatingProps } = useInteractions([
+ click,
+ dismiss,
+ role,
+ ]);
+
+ const { isMounted, styles: transitionStyles } = useTransitionStyles(context, {
+ duration: { open: 180, close: 120 },
+ initial: ({ side }) => ({
+ opacity: 0,
+ transform: {
+ top: "scale(0.97) translateY(4px)",
+ bottom: "scale(0.97) translateY(-4px)",
+ left: "scale(0.97) translateX(4px)",
+ right: "scale(0.97) translateX(-4px)",
+ }[side],
+ }),
+ });
+
+ const arrowFill = theme.tokens.elevated;
+
+ const isReactChild = React.isValidElement<{
+ disabled?: boolean;
+ [key: string]: unknown;
+ }>(children);
+
+ const referenceProps = getReferenceProps({
+ id,
+ ...(isReactChild ? children.props : {}),
+ ...(isReactChild && { disabled: disabled || children.props.disabled }),
+ });
+
+ const trigger = isReactChild ? (
+ // eslint-disable-next-line react-hooks/refs
+ React.cloneElement(children, {
+ ...referenceProps,
+ ref: mergedRef,
+ } as Partial<{ disabled?: boolean; [key: string]: unknown }> & {
+ ref: typeof mergedRef;
+ })
+ ) : (
+ }
+ id={id}
+ type="button"
+ disabled={disabled}
+ className={cn(
+ "inline-flex cursor-pointer select-none",
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
+ "focus-visible:ring-[var(--ring-color)]",
+ disabled && "pointer-events-none opacity-50",
+ )}
+ aria-expanded={open}
+ aria-haspopup="dialog"
+ {...(referenceProps as React.ButtonHTMLAttributes)}
+ >
+ {children}
+
+ );
+
+ // ─── Render ──────────────────────────────────────────────────────────────
+
+ const ContentWrapper = usePortal ? FloatingPortal : React.Fragment;
+ const portalProps = usePortal ? { root: portalContainer } : {};
+
+ return (
+ <>
+ {trigger}
+
+ {isMounted && (
+
+ {keepTriggerFocus ? (
+
+
+ {content}
+
+
+ {showArrow && (
+
+ )}
+
+ ) : (
+
+
+
+ {content}
+
+
+ {showArrow && (
+
+ )}
+
+
+ )}
+
+ )}
+ >
+ );
+});
+
+Popover.displayName = "Popover";
+
+export default Popover;
diff --git a/src/components/Popover/Popover.types.ts b/src/components/Popover/Popover.types.ts
new file mode 100644
index 0000000..bd6345e
--- /dev/null
+++ b/src/components/Popover/Popover.types.ts
@@ -0,0 +1,80 @@
+import type { Placement } from "@floating-ui/react";
+
+export interface PopoverProps {
+ /** The trigger element. A valid React element gets cloneElement'd; raw text/nodes get wrapped in a . */
+ children: React.ReactNode;
+
+ /** Optional id forwarded to the trigger. Defaults to a stable generated id. */
+ id?: string;
+
+ /**
+ * Controlled open state. When provided the component becomes fully controlled
+ * and you must manage state changes via `onOpenChange`.
+ */
+ isOpen?: boolean;
+
+ /** Uncontrolled initial open state. Ignored when `isOpen` is provided. */
+ defaultOpen?: boolean;
+
+ /** Callback fired whenever the popover requests an open-state change. */
+ onOpenChange?: (open: boolean) => void;
+
+ /** The content rendered inside the floating panel. */
+ content: React.ReactNode;
+
+ /**
+ * Preferred placement relative to the trigger.
+ * Floating UI will automatically flip/shift if there isn't enough space.
+ * @default "bottom"
+ */
+ placement?: Placement;
+
+ /**
+ * Gap in pixels between the trigger and the floating panel.
+ * When `showArrow` is true the arrow height is added on top of this value automatically.
+ * @default 8
+ */
+ offset?: number;
+
+ /**
+ * Render a directional arrow pointing from the panel toward the trigger.
+ * Uses Floating UI's `` and inherits theme colours.
+ * @default false
+ */
+ showArrow?: boolean;
+
+ /**
+ * When true the trigger interaction is disabled and open-state changes are ignored.
+ * @default false
+ */
+ disabled?: boolean;
+
+ /** Extra class names forwarded to the inner panel wrapper. */
+ className?: string;
+
+ /**
+ * CSS z-index of the floating panel.
+ * @default 1000
+ */
+ zIndex?: number;
+
+ /** Use React Portal? */
+ usePortal?: boolean;
+
+ /** Portal container element */
+ portalContainer?: HTMLElement | null;
+
+ /**
+ * When true the floating panel will match the width of the trigger element.
+ * Useful for dropdowns / selects.
+ * @default false
+ */
+ matchTriggerWidth?: boolean;
+
+ /**
+ * When true, focus stays on the trigger instead of moving into the floating panel.
+ * Useful for combobox/dropdown patterns where keyboard is handled on the trigger.
+ * @default false
+ */
+ keepTriggerFocus?: boolean;
+}
diff --git a/src/components/Popover/index.ts b/src/components/Popover/index.ts
new file mode 100644
index 0000000..0571505
--- /dev/null
+++ b/src/components/Popover/index.ts
@@ -0,0 +1,2 @@
+export { default as Popover } from "./Popover";
+export * from "./Popover.types";
diff --git a/src/components/RadioGroup/RadioGroup.test.tsx b/src/components/RadioGroup/RadioGroup.test.tsx
index 5677804..ed09631 100644
--- a/src/components/RadioGroup/RadioGroup.test.tsx
+++ b/src/components/RadioGroup/RadioGroup.test.tsx
@@ -1,6 +1,6 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
-import RadioGroup from "./RadioGroup";
+import { RadioGroup } from "./RadioGroup";
import { ThemeProvider } from "../../theme";
const options = [
diff --git a/src/components/RadioGroup/RadioGroup.tsx b/src/components/RadioGroup/RadioGroup.tsx
index 97849e9..3a61f20 100644
--- a/src/components/RadioGroup/RadioGroup.tsx
+++ b/src/components/RadioGroup/RadioGroup.tsx
@@ -1,7 +1,7 @@
import { forwardRef, useState } from "react";
import type { RadioGroupProps } from "./RadioGroup.types";
import { useTheme } from "@/theme";
-import Skeleton from "../Skeleton";
+import { Skeleton } from "../Skeleton";
import { Typography } from "../Typography";
import { cn } from "@/utils";
import {
@@ -182,7 +182,7 @@ const RadioGroup = forwardRef(
"border-[var(--primary)] bg-[var(--primary-muted)]",
checkedBorderWidths[size],
),
- "peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--ring)] peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-[var(--bg-color)]",
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--ring)]",
)}
/>
@@ -250,4 +250,4 @@ const RadioGroup = forwardRef(
RadioGroup.displayName = "RadioGroup";
-export default RadioGroup;
+export { RadioGroup };
diff --git a/src/components/RadioGroup/index.ts b/src/components/RadioGroup/index.ts
index ff6b007..61d8273 100644
--- a/src/components/RadioGroup/index.ts
+++ b/src/components/RadioGroup/index.ts
@@ -1,2 +1,2 @@
-export { default } from "./RadioGroup";
+export { RadioGroup } from "./RadioGroup";
export * from "./RadioGroup.types";
diff --git a/src/components/ScrollArea/ScrollArea.tsx b/src/components/ScrollArea/ScrollArea.tsx
index e233900..0505f1b 100644
--- a/src/components/ScrollArea/ScrollArea.tsx
+++ b/src/components/ScrollArea/ScrollArea.tsx
@@ -65,5 +65,8 @@ export const ScrollAreaItem = ({
);
};
-export { ScrollAreaComponent as ScrollArea };
+const ScrollArea = ScrollAreaComponent as any;
+ScrollArea.Item = ScrollAreaItem;
+
+export { ScrollArea };
export { ScrollAreaItem as ScrollElement };
diff --git a/src/components/Skeleton/Skeleton.tsx b/src/components/Skeleton/Skeleton.tsx
index cd6802d..26a064d 100644
--- a/src/components/Skeleton/Skeleton.tsx
+++ b/src/components/Skeleton/Skeleton.tsx
@@ -55,7 +55,7 @@ function variantStyles(variant: SkeletonVariant): CSSProperties {
}
}
-export function Skeleton({
+function Skeleton({
variant = "rectangular",
animation = "wave",
width,
@@ -97,4 +97,4 @@ export function Skeleton({
);
}
-export default Skeleton;
+export { Skeleton };
diff --git a/src/components/Skeleton/index.ts b/src/components/Skeleton/index.ts
index de1f29c..4b9cd27 100644
--- a/src/components/Skeleton/index.ts
+++ b/src/components/Skeleton/index.ts
@@ -1,2 +1,2 @@
-export * from "./Skeleton";
-export { default } from "./Skeleton";
+export { Skeleton } from "./Skeleton";
+export type { SkeletonProps } from "./Skeleton";
diff --git a/src/components/Switch/Switch.constants.ts b/src/components/Switch/Switch.constants.ts
new file mode 100644
index 0000000..8305d99
--- /dev/null
+++ b/src/components/Switch/Switch.constants.ts
@@ -0,0 +1,43 @@
+import type { SwitchProps } from "./Switch.types";
+
+export const labelSizeClasses: Record<
+ NonNullable,
+ string
+> = {
+ small: "text-[13px] font-medium leading-[1.45] tracking-[-0.01em]",
+ medium: "text-[14px] font-medium leading-[1.45] tracking-[-0.01em]",
+ large: "text-[15px] font-medium leading-[1.45] tracking-[-0.01em]",
+};
+
+export const helperSizeClasses: Record<
+ NonNullable,
+ string
+> = {
+ small: "text-[11px] leading-[1.5] tracking-[-0.005em]",
+ medium: "text-[12px] leading-[1.5] tracking-[-0.005em]",
+ large: "text-[13px] leading-[1.5] tracking-[-0.005em]",
+};
+
+export const trackSizeClasses: Record<
+ NonNullable,
+ { width: string; height: string; thumbSize: string; thumbOffset: string }
+> = {
+ small: {
+ width: "28px",
+ height: "16px",
+ thumbSize: "12px",
+ thumbOffset: "2px",
+ },
+ medium: {
+ width: "36px",
+ height: "20px",
+ thumbSize: "16px",
+ thumbOffset: "2px",
+ },
+ large: {
+ width: "44px",
+ height: "24px",
+ thumbSize: "20px",
+ thumbOffset: "2px",
+ },
+};
diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx
new file mode 100644
index 0000000..1c5a2a2
--- /dev/null
+++ b/src/components/Switch/Switch.tsx
@@ -0,0 +1,241 @@
+import { forwardRef, useState, useId } from "react";
+import type { SwitchProps } from "./Switch.types";
+import { useTheme } from "@/theme";
+import { cn } from "@/utils/cn";
+import {
+ labelSizeClasses,
+ helperSizeClasses,
+ trackSizeClasses,
+} from "./Switch.constants";
+import { Skeleton } from "../Skeleton";
+
+const Switch = forwardRef((props, ref) => {
+ const {
+ id: idProp,
+ checked,
+ onChange,
+ disabled,
+ required,
+ isLoading,
+ size = "medium",
+ label,
+ helperText,
+ description,
+ error,
+ name,
+ className,
+ style,
+ ...rest
+ } = props;
+
+ const { theme } = useTheme();
+ const autoId = useId();
+ const id = idProp ?? autoId;
+
+ const labelId = label ? `${id}-label` : undefined;
+ const descriptionId =
+ description || helperText ? `${id}-description` : undefined;
+ const errorId = error ? `${id}-error` : undefined;
+
+ const [checkedState, setCheckedState] = useState(checked ?? false);
+
+ const isControlled = checked !== undefined;
+
+ const handleCheckedChange = (newChecked: boolean) => {
+ if (!isControlled) {
+ setCheckedState(newChecked);
+ }
+ onChange?.(newChecked);
+ };
+
+ const isCurrentlyChecked = isControlled ? checked : checkedState;
+ const currentTrackSize = trackSizeClasses[size];
+
+ const themeStyles = {
+ "--switch-primary": theme.palette.primary[500],
+ "--switch-primary-hover": theme.palette.primary[600],
+ "--switch-track-bg": theme.tokens.border,
+ "--switch-track-hover-bg": theme.tokens.borderStrong ?? theme.tokens.border,
+ "--switch-thumb-bg": "white",
+ "--switch-focus-ring": theme.palette.primary[300],
+ "--text-color": theme.tokens.foreground,
+ "--text-secondary": theme.tokens.foregroundMuted,
+ "--error-color": theme.palette.error[500],
+ "--error-focus-ring": theme.palette.error[300],
+ } as React.CSSProperties;
+
+ if (isLoading) {
+ return (
+
+
+
+ {label && (
+
+ )}
+
+
+ );
+ }
+
+ const ariaDescribedBy =
+ [errorId, descriptionId].filter(Boolean).join(" ") || undefined;
+
+ return (
+
+ {/* Label + Track row */}
+
+ {/* Track / Button */}
+ {
+ e.preventDefault();
+ if (!disabled) handleCheckedChange(!isCurrentlyChecked);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === "ArrowLeft" && isCurrentlyChecked && !disabled) {
+ e.preventDefault();
+ handleCheckedChange(false);
+ }
+ if (e.key === "ArrowRight" && !isCurrentlyChecked && !disabled) {
+ e.preventDefault();
+ handleCheckedChange(true);
+ }
+ }}
+ style={{
+ width: currentTrackSize.width,
+ height: currentTrackSize.height,
+ }}
+ >
+ {/* Thumb */}
+
+
+
+ {/* Label */}
+ {label && (
+
+ {label}
+ {required && (
+
+ *
+
+ )}
+
+ )}
+
+
+ {(description || helperText || error) && (
+
+ {error ? (
+
+ {error}
+
+ ) : description || helperText ? (
+
+ {description || helperText}
+
+ ) : null}
+
+ )}
+
+ );
+});
+
+Switch.displayName = "Switch";
+
+export default Switch;
diff --git a/src/components/Switch/Switch.types.ts b/src/components/Switch/Switch.types.ts
new file mode 100644
index 0000000..983e3d9
--- /dev/null
+++ b/src/components/Switch/Switch.types.ts
@@ -0,0 +1,42 @@
+export interface SwitchProps {
+ id?: string;
+
+ /** Controlled state */
+ checked?: boolean;
+
+ /** Fired when switch changes */
+ onChange?: (checked: boolean) => void;
+
+ /** Disable interaction */
+ disabled?: boolean;
+
+ /** Required field */
+ required?: boolean;
+
+ /** Loading state */
+ isLoading?: boolean;
+
+ /** Switch size */
+ size?: "small" | "medium" | "large";
+
+ /** Label next to switch */
+ label?: React.ReactNode;
+
+ /** description */
+ description?: React.ReactNode;
+
+ /** Helper text below */
+ helperText?: React.ReactNode;
+
+ /** Error message */
+ error?: React.ReactNode;
+
+ /** Name for form submission */
+ name?: string;
+
+ /** Custom class */
+ className?: string;
+
+ /** Inline style */
+ style?: React.CSSProperties;
+}
diff --git a/src/components/Switch/index.ts b/src/components/Switch/index.ts
new file mode 100644
index 0000000..5d9be1a
--- /dev/null
+++ b/src/components/Switch/index.ts
@@ -0,0 +1,2 @@
+export { default as Switch } from "./Switch";
+export * from "./Switch.types";
diff --git a/src/components/index.ts b/src/components/index.ts
index e95ab28..b3e5f72 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -9,6 +9,7 @@ export * from "./Skeleton";
export * from "./Checkbox";
export * from "./ScrollArea";
export * from "./RadioGroup";
+export * from "./Switch";
// ── Theme ──
export { ThemeProvider, useTheme, defaultTheme, darkTheme } from "../theme";
diff --git a/src/docs/DocsApp.tsx b/src/docs/DocsApp.tsx
new file mode 100644
index 0000000..4ab5521
--- /dev/null
+++ b/src/docs/DocsApp.tsx
@@ -0,0 +1,1173 @@
+import React, { Suspense, lazy, useState } from "react";
+import { Routes, Route, Navigate, Link } from "react-router-dom";
+import { DocsLayout } from "./DocsLayout";
+import { useTheme } from "@/theme";
+import { Button } from "@/components/Button";
+import { Input } from "@/components/Input";
+import { Badge } from "@/components/Badge";
+import { Checkbox } from "@/components/Checkbox";
+
+// Lazy load MDX pages
+const ButtonDocs = lazy(() => import("./pages/Button.mdx"));
+const InputDocs = lazy(() => import("./pages/Input.mdx"));
+const RadioGroupDocs = lazy(() => import("./pages/RadioGroup.mdx"));
+const AccordionDocs = lazy(() => import("./pages/Accordion.mdx"));
+const AvatarDocs = lazy(() => import("./pages/Avatar.mdx"));
+const BadgeDocs = lazy(() => import("./pages/Badge.mdx"));
+const CheckboxDocs = lazy(() => import("./pages/Checkbox.mdx"));
+const DropdownDocs = lazy(() => import("./pages/Dropdown.mdx"));
+const ModalDocs = lazy(() => import("./pages/Modal.mdx"));
+const PopoverDocs = lazy(() => import("./pages/Popover.mdx"));
+const ScrollAreaDocs = lazy(() => import("./pages/ScrollArea.mdx"));
+const SkeletonDocs = lazy(() => import("./pages/Skeleton.mdx"));
+const SwitchDocs = lazy(() => import("./pages/Switch.mdx"));
+const TypographyDocs = lazy(() => import("./pages/Typography.mdx"));
+const ThemingPage = lazy(() =>
+ import("./pages/ThemingPage").then((m) => ({ default: m.ThemingPage })),
+);
+
+const components = [
+ {
+ name: "Accordion",
+ path: "/docs/accordion",
+ description: "Collapsible content sections",
+ },
+ {
+ name: "Avatar",
+ path: "/docs/avatar",
+ description: "User profile images with fallbacks",
+ },
+ {
+ name: "Badge",
+ path: "/docs/badge",
+ description: "Status indicators and labels",
+ },
+ {
+ name: "Button",
+ path: "/docs/button",
+ description: "Interactive action triggers",
+ },
+ {
+ name: "Checkbox",
+ path: "/docs/checkbox",
+ description: "Multi-select form controls",
+ },
+ {
+ name: "Dropdown",
+ path: "/docs/dropdown",
+ description: "Select menus with keyboard navigation",
+ },
+ {
+ name: "Input",
+ path: "/docs/input",
+ description: "Text fields with validation",
+ },
+ {
+ name: "Modal",
+ path: "/docs/modal",
+ description: "Accessible dialog overlays",
+ },
+ {
+ name: "Popover",
+ path: "/docs/popover",
+ description: "Contextual floating menus",
+ },
+ {
+ name: "Radio Group",
+ path: "/docs/radio-group",
+ description: "Single-select option groups",
+ },
+ {
+ name: "Scroll Area",
+ path: "/docs/scroll-area",
+ description: "Scrollable content containers",
+ },
+ {
+ name: "Skeleton",
+ path: "/docs/skeleton",
+ description: "Loading placeholders",
+ },
+ {
+ name: "Switch",
+ path: "/docs/switch",
+ description: "Instant setting toggles",
+ },
+ {
+ name: "Typography",
+ path: "/docs/typography",
+ description: "Text styles and hierarchy",
+ },
+];
+
+const COLOR_PRESETS = [
+ { name: "Pink", value: "#ec4899" },
+ { name: "Blue", value: "#3b82f6" },
+ { name: "Violet", value: "#8b5cf6" },
+ { name: "Emerald", value: "#10b981" },
+ { name: "Orange", value: "#f97316" },
+ { name: "Red", value: "#ef4444" },
+ { name: "Cyan", value: "#06b6d4" },
+ { name: "Rose", value: "#f43f5e" },
+];
+
+function hexToHSL(hex: string): [number, number, number] {
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
+ const max = Math.max(r, g, b),
+ min = Math.min(r, g, b);
+ const l = (max + min) / 2;
+ if (max === min) return [0, 0, l];
+ const d = max - min;
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ let h = 0;
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
+ else if (max === g) h = ((b - r) / d + 2) / 6;
+ else h = ((r - g) / d + 4) / 6;
+ return [h * 360, s * 100, l * 100];
+}
+
+function hslToHex(h: number, s: number, l: number): string {
+ s /= 100;
+ l /= 100;
+ const a = s * Math.min(l, 1 - l);
+ const f = (n: number) => {
+ const k = (n + h / 30) % 12;
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
+ return Math.round(255 * Math.max(0, Math.min(1, color)))
+ .toString(16)
+ .padStart(2, "0");
+ };
+ return `#${f(0)}${f(8)}${f(4)}`;
+}
+
+function generatePaletteFromColor(hex: string) {
+ const [h, s] = hexToHSL(hex);
+ return {
+ 50: hslToHex(h, Math.min(100, s * 1.1), 97),
+ 100: hslToHex(h, Math.min(100, s * 1.1), 93),
+ 200: hslToHex(h, Math.min(100, s), 85),
+ 300: hslToHex(h, Math.min(100, s * 0.95), 73),
+ 400: hslToHex(h, Math.min(100, s * 0.9), 60),
+ 500: hex,
+ 600: hslToHex(h, Math.min(100, s * 0.95), 42),
+ 700: hslToHex(h, Math.min(100, s * 0.9), 34),
+ 800: hslToHex(h, Math.min(100, s * 0.85), 26),
+ 900: hslToHex(h, Math.min(100, s * 0.8), 18),
+ };
+}
+
+// ─── Surface Theme Presets ─────────────────────────────────────────────────
+
+interface SurfacePreset {
+ name: string;
+ description: string;
+ preview: [string, string, string];
+ neutral: Record;
+ tokens: Record;
+}
+
+const DARK_SURFACE_PRESETS: SurfacePreset[] = [
+ {
+ name: "Midnight",
+ description: "Pure black",
+ preview: ["#080808", "#181818", "#f2f2f2"],
+ neutral: {
+ 50: "#080808",
+ 100: "#101010",
+ 200: "#181818",
+ 300: "#202020",
+ 400: "#383838",
+ 500: "#585858",
+ 600: "#787878",
+ 700: "#a0a0a0",
+ 800: "#c8c8c8",
+ 900: "#f2f2f2",
+ },
+ tokens: {
+ background: "#080808",
+ surface: "#101010",
+ surfaceHover: "#181818",
+ elevated: "#181818",
+ foreground: "#f2f2f2",
+ foregroundMuted: "#a0a0a0",
+ foregroundSubtle: "#787878",
+ border: "#202020",
+ borderSubtle: "#181818",
+ borderStrong: "#383838",
+ input: "#181818",
+ inputHover: "#202020",
+ inputFocus: "#101010",
+ },
+ },
+ {
+ name: "Charcoal",
+ description: "Soft dark gray",
+ preview: ["#121212", "#1e1e1e", "#e8e8e8"],
+ neutral: {
+ 50: "#121212",
+ 100: "#1a1a1a",
+ 200: "#1e1e1e",
+ 300: "#2c2c2c",
+ 400: "#404040",
+ 500: "#5e5e5e",
+ 600: "#808080",
+ 700: "#a8a8a8",
+ 800: "#d0d0d0",
+ 900: "#e8e8e8",
+ },
+ tokens: {
+ background: "#121212",
+ surface: "#1a1a1a",
+ surfaceHover: "#1e1e1e",
+ elevated: "#1e1e1e",
+ foreground: "#e8e8e8",
+ foregroundMuted: "#a8a8a8",
+ foregroundSubtle: "#808080",
+ border: "#2c2c2c",
+ borderSubtle: "#1e1e1e",
+ borderStrong: "#404040",
+ input: "#1e1e1e",
+ inputHover: "#2c2c2c",
+ inputFocus: "#1a1a1a",
+ },
+ },
+ {
+ name: "Ocean",
+ description: "Deep blue",
+ preview: ["#0a1628", "#111d33", "#e2e8f0"],
+ neutral: {
+ 50: "#0a1628",
+ 100: "#0f1d2e",
+ 200: "#111d33",
+ 300: "#1a2a42",
+ 400: "#2a3f5f",
+ 500: "#4a6182",
+ 600: "#6b82a0",
+ 700: "#8fa3bb",
+ 800: "#c4d1de",
+ 900: "#e2e8f0",
+ },
+ tokens: {
+ background: "#0a1628",
+ surface: "#0f1d2e",
+ surfaceHover: "#111d33",
+ elevated: "#111d33",
+ foreground: "#e2e8f0",
+ foregroundMuted: "#8fa3bb",
+ foregroundSubtle: "#6b82a0",
+ border: "#1a2a42",
+ borderSubtle: "#111d33",
+ borderStrong: "#2a3f5f",
+ input: "#111d33",
+ inputHover: "#1a2a42",
+ inputFocus: "#0f1d2e",
+ },
+ },
+ {
+ name: "Glass Blue",
+ description: "Frosted blue glass",
+ preview: ["#0c1929", "#132640", "#dbeafe"],
+ neutral: {
+ 50: "#0c1929",
+ 100: "#102030",
+ 200: "#132640",
+ 300: "#1c3554",
+ 400: "#2b4d75",
+ 500: "#3e6a9a",
+ 600: "#5b8aba",
+ 700: "#8cb4da",
+ 800: "#bcd5f0",
+ 900: "#dbeafe",
+ },
+ tokens: {
+ background: "#0c1929",
+ surface: "#102030",
+ surfaceHover: "#162d48",
+ elevated: "#132640",
+ foreground: "#dbeafe",
+ foregroundMuted: "#8cb4da",
+ foregroundSubtle: "#5b8aba",
+ border: "#1e3a5c",
+ borderSubtle: "#162d48",
+ borderStrong: "#2b4d75",
+ input: "#132640",
+ inputHover: "#1c3554",
+ inputFocus: "#102030",
+ },
+ },
+ {
+ name: "Slate",
+ description: "Cool blue-gray",
+ preview: ["#0f172a", "#1e293b", "#f1f5f9"],
+ neutral: {
+ 50: "#0f172a",
+ 100: "#1e293b",
+ 200: "#1e293b",
+ 300: "#334155",
+ 400: "#475569",
+ 500: "#64748b",
+ 600: "#94a3b8",
+ 700: "#cbd5e1",
+ 800: "#e2e8f0",
+ 900: "#f1f5f9",
+ },
+ tokens: {
+ background: "#0f172a",
+ surface: "#1e293b",
+ surfaceHover: "#273549",
+ elevated: "#1e293b",
+ foreground: "#f1f5f9",
+ foregroundMuted: "#94a3b8",
+ foregroundSubtle: "#64748b",
+ border: "#334155",
+ borderSubtle: "#1e293b",
+ borderStrong: "#475569",
+ input: "#1e293b",
+ inputHover: "#334155",
+ inputFocus: "#1e293b",
+ },
+ },
+ {
+ name: "Violet",
+ description: "Purple tint",
+ preview: ["#0e0a1a", "#1a1425", "#ede9fe"],
+ neutral: {
+ 50: "#0e0a1a",
+ 100: "#141020",
+ 200: "#1a1425",
+ 300: "#251e35",
+ 400: "#3b3255",
+ 500: "#5b5278",
+ 600: "#7c739a",
+ 700: "#a09abc",
+ 800: "#cbc5de",
+ 900: "#ede9fe",
+ },
+ tokens: {
+ background: "#0e0a1a",
+ surface: "#141020",
+ surfaceHover: "#1a1425",
+ elevated: "#1a1425",
+ foreground: "#ede9fe",
+ foregroundMuted: "#a09abc",
+ foregroundSubtle: "#7c739a",
+ border: "#251e35",
+ borderSubtle: "#1a1425",
+ borderStrong: "#3b3255",
+ input: "#1a1425",
+ inputHover: "#251e35",
+ inputFocus: "#141020",
+ },
+ },
+ {
+ name: "Rose Dark",
+ description: "Warm rose tint",
+ preview: ["#1a0a10", "#261218", "#fce7f3"],
+ neutral: {
+ 50: "#1a0a10",
+ 100: "#201015",
+ 200: "#261218",
+ 300: "#3a1c25",
+ 400: "#552a38",
+ 500: "#7a4055",
+ 600: "#a06078",
+ 700: "#c08898",
+ 800: "#dbb0be",
+ 900: "#fce7f3",
+ },
+ tokens: {
+ background: "#1a0a10",
+ surface: "#201015",
+ surfaceHover: "#261218",
+ elevated: "#261218",
+ foreground: "#fce7f3",
+ foregroundMuted: "#c08898",
+ foregroundSubtle: "#a06078",
+ border: "#3a1c25",
+ borderSubtle: "#261218",
+ borderStrong: "#552a38",
+ input: "#261218",
+ inputHover: "#3a1c25",
+ inputFocus: "#201015",
+ },
+ },
+ {
+ name: "Forest",
+ description: "Deep green",
+ preview: ["#071210", "#0d1f1b", "#ecfdf5"],
+ neutral: {
+ 50: "#071210",
+ 100: "#0a1a17",
+ 200: "#0d1f1b",
+ 300: "#162e28",
+ 400: "#264a40",
+ 500: "#3d6b5e",
+ 600: "#5a8c7c",
+ 700: "#86b5a2",
+ 800: "#b8d8ca",
+ 900: "#ecfdf5",
+ },
+ tokens: {
+ background: "#071210",
+ surface: "#0a1a17",
+ surfaceHover: "#0d1f1b",
+ elevated: "#0d1f1b",
+ foreground: "#ecfdf5",
+ foregroundMuted: "#86b5a2",
+ foregroundSubtle: "#5a8c7c",
+ border: "#162e28",
+ borderSubtle: "#0d1f1b",
+ borderStrong: "#264a40",
+ input: "#0d1f1b",
+ inputHover: "#162e28",
+ inputFocus: "#0a1a17",
+ },
+ },
+ {
+ name: "Warm",
+ description: "Cozy brown",
+ preview: ["#120f0b", "#1c1814", "#faf5ef"],
+ neutral: {
+ 50: "#120f0b",
+ 100: "#181510",
+ 200: "#1c1814",
+ 300: "#2a2520",
+ 400: "#3e3830",
+ 500: "#5c5448",
+ 600: "#7a7165",
+ 700: "#a09688",
+ 800: "#c8bfb2",
+ 900: "#faf5ef",
+ },
+ tokens: {
+ background: "#120f0b",
+ surface: "#181510",
+ surfaceHover: "#1c1814",
+ elevated: "#1c1814",
+ foreground: "#faf5ef",
+ foregroundMuted: "#a09688",
+ foregroundSubtle: "#7a7165",
+ border: "#2a2520",
+ borderSubtle: "#1c1814",
+ borderStrong: "#3e3830",
+ input: "#1c1814",
+ inputHover: "#2a2520",
+ inputFocus: "#181510",
+ },
+ },
+ {
+ name: "Onyx",
+ description: "True black OLED",
+ preview: ["#000000", "#0a0a0a", "#fafafa"],
+ neutral: {
+ 50: "#000000",
+ 100: "#050505",
+ 200: "#0a0a0a",
+ 300: "#151515",
+ 400: "#2a2a2a",
+ 500: "#505050",
+ 600: "#707070",
+ 700: "#999999",
+ 800: "#c0c0c0",
+ 900: "#fafafa",
+ },
+ tokens: {
+ background: "#000000",
+ surface: "#050505",
+ surfaceHover: "#0a0a0a",
+ elevated: "#0a0a0a",
+ foreground: "#fafafa",
+ foregroundMuted: "#999999",
+ foregroundSubtle: "#707070",
+ border: "#151515",
+ borderSubtle: "#0a0a0a",
+ borderStrong: "#2a2a2a",
+ input: "#0a0a0a",
+ inputHover: "#151515",
+ inputFocus: "#050505",
+ },
+ },
+];
+
+const LIGHT_SURFACE_PRESETS: SurfacePreset[] = [
+ {
+ name: "Default",
+ description: "Clean white",
+ preview: ["#fbfaf9", "#ffffff", "#111827"],
+ neutral: {
+ 50: "#fbfaf9",
+ 100: "#f3f4f6",
+ 200: "#e5e7eb",
+ 300: "#d1d5db",
+ 400: "#9ca3af",
+ 500: "#6b7280",
+ 600: "#4b5563",
+ 700: "#374151",
+ 800: "#1f2937",
+ 900: "#111827",
+ },
+ tokens: {
+ background: "#fbfaf9",
+ surface: "#ffffff",
+ surfaceHover: "#f3f4f6",
+ elevated: "#ffffff",
+ foreground: "#111827",
+ foregroundMuted: "#6b7280",
+ foregroundSubtle: "#9ca3af",
+ border: "#e5e7eb",
+ borderSubtle: "#f3f4f6",
+ borderStrong: "#9ca3af",
+ input: "#ffffff",
+ inputHover: "#fbfaf9",
+ inputFocus: "#ffffff",
+ },
+ },
+ {
+ name: "Snow Blue",
+ description: "Cool blue tint",
+ preview: ["#f0f4f8", "#ffffff", "#1a2b4a"],
+ neutral: {
+ 50: "#f0f4f8",
+ 100: "#e8edf2",
+ 200: "#d5dce6",
+ 300: "#b8c4d4",
+ 400: "#8a9ab5",
+ 500: "#5d7096",
+ 600: "#445a7a",
+ 700: "#344763",
+ 800: "#25344c",
+ 900: "#1a2b4a",
+ },
+ tokens: {
+ background: "#f0f4f8",
+ surface: "#ffffff",
+ surfaceHover: "#e8edf2",
+ elevated: "#ffffff",
+ foreground: "#1a2b4a",
+ foregroundMuted: "#5d7096",
+ foregroundSubtle: "#8a9ab5",
+ border: "#d5dce6",
+ borderSubtle: "#e8edf2",
+ borderStrong: "#8a9ab5",
+ input: "#ffffff",
+ inputHover: "#f0f4f8",
+ inputFocus: "#ffffff",
+ },
+ },
+ {
+ name: "Warm Sand",
+ description: "Soft cream",
+ preview: ["#faf6f0", "#ffffff", "#2d1f10"],
+ neutral: {
+ 50: "#faf6f0",
+ 100: "#f3ede4",
+ 200: "#e5ddd0",
+ 300: "#d1c5b4",
+ 400: "#a89680",
+ 500: "#7a6b58",
+ 600: "#5c4f3e",
+ 700: "#443a2e",
+ 800: "#322b20",
+ 900: "#2d1f10",
+ },
+ tokens: {
+ background: "#faf6f0",
+ surface: "#ffffff",
+ surfaceHover: "#f3ede4",
+ elevated: "#ffffff",
+ foreground: "#2d1f10",
+ foregroundMuted: "#7a6b58",
+ foregroundSubtle: "#a89680",
+ border: "#e5ddd0",
+ borderSubtle: "#f3ede4",
+ borderStrong: "#a89680",
+ input: "#ffffff",
+ inputHover: "#faf6f0",
+ inputFocus: "#ffffff",
+ },
+ },
+ {
+ name: "Lavender",
+ description: "Soft purple tint",
+ preview: ["#f5f3ff", "#ffffff", "#1e1040"],
+ neutral: {
+ 50: "#f5f3ff",
+ 100: "#ede9fe",
+ 200: "#ddd6fe",
+ 300: "#c4b5fd",
+ 400: "#a78bfa",
+ 500: "#7c5cbf",
+ 600: "#5b4399",
+ 700: "#453378",
+ 800: "#2e2254",
+ 900: "#1e1040",
+ },
+ tokens: {
+ background: "#f5f3ff",
+ surface: "#ffffff",
+ surfaceHover: "#ede9fe",
+ elevated: "#ffffff",
+ foreground: "#1e1040",
+ foregroundMuted: "#5b4399",
+ foregroundSubtle: "#a78bfa",
+ border: "#ddd6fe",
+ borderSubtle: "#ede9fe",
+ borderStrong: "#a78bfa",
+ input: "#ffffff",
+ inputHover: "#f5f3ff",
+ inputFocus: "#ffffff",
+ },
+ },
+ {
+ name: "Mint",
+ description: "Fresh green tint",
+ preview: ["#f0fdf4", "#ffffff", "#052e16"],
+ neutral: {
+ 50: "#f0fdf4",
+ 100: "#dcfce7",
+ 200: "#bbf7d0",
+ 300: "#86efac",
+ 400: "#4ade80",
+ 500: "#22c55e",
+ 600: "#16a34a",
+ 700: "#15803d",
+ 800: "#166534",
+ 900: "#052e16",
+ },
+ tokens: {
+ background: "#f0fdf4",
+ surface: "#ffffff",
+ surfaceHover: "#dcfce7",
+ elevated: "#ffffff",
+ foreground: "#052e16",
+ foregroundMuted: "#16a34a",
+ foregroundSubtle: "#4ade80",
+ border: "#bbf7d0",
+ borderSubtle: "#dcfce7",
+ borderStrong: "#4ade80",
+ input: "#ffffff",
+ inputHover: "#f0fdf4",
+ inputFocus: "#ffffff",
+ },
+ },
+ {
+ name: "Rose",
+ description: "Warm pink tint",
+ preview: ["#fff1f2", "#ffffff", "#4c0519"],
+ neutral: {
+ 50: "#fff1f2",
+ 100: "#ffe4e6",
+ 200: "#fecdd3",
+ 300: "#fda4af",
+ 400: "#fb7185",
+ 500: "#f43f5e",
+ 600: "#e11d48",
+ 700: "#be123c",
+ 800: "#9f1239",
+ 900: "#4c0519",
+ },
+ tokens: {
+ background: "#fff1f2",
+ surface: "#ffffff",
+ surfaceHover: "#ffe4e6",
+ elevated: "#ffffff",
+ foreground: "#4c0519",
+ foregroundMuted: "#be123c",
+ foregroundSubtle: "#fb7185",
+ border: "#fecdd3",
+ borderSubtle: "#ffe4e6",
+ borderStrong: "#fb7185",
+ input: "#ffffff",
+ inputHover: "#fff1f2",
+ inputFocus: "#ffffff",
+ },
+ },
+];
+
+const CopyInstallButton: React.FC = () => {
+ const { theme } = useTheme();
+ const [copied, setCopied] = useState(false);
+ return (
+ {
+ navigator.clipboard.writeText("npm install awesome-ui-library");
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ }}
+ className="h-8 px-4 rounded-md border text-xs font-mono flex items-center gap-2 transition-colors"
+ style={{
+ borderColor: theme.tokens.border,
+ color: theme.tokens.foregroundMuted,
+ backgroundColor: theme.tokens.surface,
+ }}
+ >
+ npm install awesome-ui-library
+ {copied ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+};
+
+const ColorPicker: React.FC = () => {
+ const { theme, overrideTheme, resetTheme } = useTheme();
+ const [customColor, setCustomColor] = useState(theme.palette.primary[500]);
+ const [copied, setCopied] = useState(null);
+
+ const applyColor = (color: string) => {
+ setCustomColor(color);
+ const palette = generatePaletteFromColor(color);
+ overrideTheme({
+ palette: { primary: palette },
+ tokens: {
+ accent: palette[600],
+ accentForeground: "#ffffff",
+ accentHover: palette[700],
+ ring: palette[500],
+ },
+ });
+ };
+
+ const copyColor = (value: string, label: string) => {
+ navigator.clipboard.writeText(value);
+ setCopied(label);
+ setTimeout(() => setCopied(null), 1500);
+ };
+
+ return (
+
+
+
Primary Color
+ {
+ resetTheme();
+ setCustomColor("#ec4899");
+ }}
+ className="text-xs opacity-50 hover:opacity-100 transition-opacity"
+ >
+ Reset
+
+
+
+ {/* Preset swatches */}
+
+ {COLOR_PRESETS.map((preset) => (
+ applyColor(preset.value)}
+ className="w-8 h-8 rounded-md border-2 transition-transform hover:scale-110"
+ style={{
+ backgroundColor: preset.value,
+ borderColor:
+ customColor === preset.value
+ ? theme.tokens.foreground
+ : "transparent",
+ }}
+ title={preset.name}
+ />
+ ))}
+
+
+ {/* Custom color input + copy */}
+
+ applyColor(e.target.value)}
+ className="w-8 h-8 rounded cursor-pointer border-0 p-0"
+ />
+ {
+ const v = e.target.value;
+ if (/^#[0-9a-fA-F]{6}$/.test(v)) applyColor(v);
+ setCustomColor(v);
+ }}
+ className="h-8 px-3 rounded-md border text-sm font-mono w-28"
+ style={{
+ borderColor: theme.tokens.border,
+ backgroundColor: theme.tokens.background,
+ color: theme.tokens.foreground,
+ }}
+ maxLength={7}
+ />
+ copyColor(customColor, "hex")}
+ className="h-8 px-3 rounded-md border text-xs font-medium transition-colors"
+ style={{
+ borderColor: theme.tokens.border,
+ color:
+ copied === "hex"
+ ? theme.palette.success[500]
+ : theme.tokens.foregroundMuted,
+ }}
+ >
+ {copied === "hex" ? "Copied!" : "Copy"}
+
+
+
+ {/* Theme Variations */}
+
+
+ {theme.colorMode === "dark" ? "Dark" : "Light"} Surface Themes
+
+
+ {(theme.colorMode === "dark"
+ ? DARK_SURFACE_PRESETS
+ : LIGHT_SURFACE_PRESETS
+ ).map((preset) => (
+
{
+ overrideTheme({
+ neutral: preset.neutral as unknown as typeof theme.neutral,
+ tokens: preset.tokens,
+ });
+ }}
+ className="rounded-md border p-2 text-left transition-all hover:border-[var(--color-accent)]"
+ style={{
+ borderColor: theme.tokens.border,
+ backgroundColor: theme.tokens.surface,
+ }}
+ >
+
+ {preset.preview.map((c, i) => (
+
+ ))}
+
+
+ {preset.name}
+
+
+ {preset.description}
+
+
+ ))}
+
+
+
+ );
+};
+
+const LivePreview: React.FC = () => {
+ const { theme } = useTheme();
+
+ return (
+
+
Live Preview
+
+
+ Primary
+
+
+ Secondary
+
+
+ Outline
+
+
+ Ghost
+
+
+
+
+ Active
+ Online
+ Pending
+ Offline
+
+
+
+ );
+};
+
+const LandingPage: React.FC = () => {
+ const { theme } = useTheme();
+
+ return (
+
+ {/* Hero + Theme Customizer */}
+
+
+ v2.0.0
+
+
+ Build better interfaces,{" "}
+ faster.
+
+
+ A production-ready React component library with built-in theming,
+ accessibility, and TypeScript support.
+
+
+
+ Browse Components
+
+
+
+
+
+ {/* Theme Customizer - visible without scrolling */}
+
+
+ Pick a primary color to preview
+
+
+
+
+
+
+
+ {/* Features */}
+
+
+ Why Awesome UI?
+
+
+ Built for teams that ship fast without cutting corners.
+
+
+ {[
+ {
+ icon: (
+
+
+
+ ),
+ title: "Accessible",
+ desc: "WAI-ARIA compliant. Full keyboard navigation and screen reader support out of the box.",
+ },
+ {
+ icon: (
+
+
+
+ ),
+ title: "Themeable",
+ desc: "CSS variable theming with light/dark modes. Override any color at runtime — zero JS cost.",
+ },
+ {
+ icon: (
+
+
+
+ ),
+ title: "TypeScript",
+ desc: "Every component is fully typed. Exported interfaces for all props — autocomplete just works.",
+ },
+ ].map((feature) => (
+
+
+ {feature.icon}
+
+
{feature.title}
+
+ {feature.desc}
+
+
+ ))}
+
+
+
+ {/* Components Grid */}
+
+
+
Components
+
+ {components.length} available
+
+
+
+ {components.map((comp) => (
+
{
+ e.currentTarget.style.borderColor = theme.palette.primary[500];
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.borderColor = theme.tokens.border;
+ }}
+ >
+
{comp.name}
+
+ {comp.description}
+
+
+ ))}
+
+
+
+ );
+};
+
+export const DocsApp: React.FC = () => {
+ return (
+
+
+ Loading...
+
+ }
+ >
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+};
+
+export default DocsApp;
diff --git a/src/docs/DocsLayout.tsx b/src/docs/DocsLayout.tsx
new file mode 100644
index 0000000..19aec0c
--- /dev/null
+++ b/src/docs/DocsLayout.tsx
@@ -0,0 +1,391 @@
+import React, { useState, useMemo } from "react";
+import { Link, useLocation } from "react-router-dom";
+import { cn } from "@/utils";
+import { useTheme } from "@/theme";
+
+const navigation = [
+ {
+ title: "Getting Started",
+ links: [
+ { name: "Introduction", path: "/docs" },
+ { name: "Theming", path: "/docs/theming" },
+ ],
+ },
+ {
+ title: "Components",
+ links: [
+ { name: "Accordion", path: "/docs/accordion" },
+ { name: "Avatar", path: "/docs/avatar" },
+ { name: "Badge", path: "/docs/badge" },
+ { name: "Button", path: "/docs/button" },
+ { name: "Checkbox", path: "/docs/checkbox" },
+ { name: "Dropdown", path: "/docs/dropdown" },
+ { name: "Input", path: "/docs/input" },
+ { name: "Modal", path: "/docs/modal" },
+ { name: "Popover", path: "/docs/popover" },
+ { name: "Radio Group", path: "/docs/radio-group" },
+ { name: "Scroll Area", path: "/docs/scroll-area" },
+ { name: "Skeleton", path: "/docs/skeleton" },
+ { name: "Switch", path: "/docs/switch" },
+ { name: "Typography", path: "/docs/typography" },
+ ],
+ },
+];
+
+export const DocsLayout: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
+ const location = useLocation();
+ const { theme, toggleColorMode } = useTheme();
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+ const [searchQuery, setSearchQuery] = useState("");
+
+ const activeLink = navigation
+ .flatMap((n) => n.links)
+ .find((l) => l.path === location.pathname);
+
+ const isHomePage =
+ location.pathname === "/docs" || location.pathname === "/docs/";
+
+ const filteredNavigation = useMemo(() => {
+ if (!searchQuery) return navigation;
+ return navigation
+ .map((section) => ({
+ ...section,
+ links: section.links.filter((link) =>
+ link.name.toLowerCase().includes(searchQuery.toLowerCase()),
+ ),
+ }))
+ .filter((section) => section.links.length > 0);
+ }, [searchQuery]);
+
+ return (
+
+ {/* Header */}
+
+
+
+ {/* Sidebar */}
+
+ {/* Mobile search */}
+
+ setSearchQuery(e.target.value)}
+ className="w-full h-9 px-3 rounded-md border text-sm"
+ style={{
+ borderColor: theme.tokens.border,
+ backgroundColor: theme.tokens.surface,
+ color: theme.tokens.foreground,
+ }}
+ />
+
+
+
+ {filteredNavigation.map((section) => (
+
+
+ {section.title}
+
+
+ {section.links.map((link) => (
+ setIsSidebarOpen(false)}
+ className={cn(
+ "block px-2 py-1.5 text-sm rounded-md transition-colors",
+ location.pathname === link.path
+ ? "font-medium"
+ : "opacity-60 hover:opacity-100",
+ )}
+ style={
+ location.pathname === link.path
+ ? {
+ color: theme.palette.primary[500],
+ backgroundColor: `${theme.palette.primary[500]}10`,
+ }
+ : undefined
+ }
+ >
+ {link.name}
+
+ ))}
+
+
+ ))}
+
+
+
+ {/* Main Content */}
+
+ {/* Breadcrumb */}
+ {!isHomePage && (
+
+
+ Docs
+
+ /
+
+ {activeLink?.name}
+
+
+ )}
+
+
+ {children}
+
+
+
+
+ {/* Footer */}
+ {isHomePage && (
+
+ Built with React, TypeScript & Tailwind CSS
+
+ )}
+
+ );
+};
diff --git a/src/docs/components/ComponentPreview.tsx b/src/docs/components/ComponentPreview.tsx
new file mode 100644
index 0000000..e1aa054
--- /dev/null
+++ b/src/docs/components/ComponentPreview.tsx
@@ -0,0 +1,94 @@
+import React, { useState } from "react";
+import { useTheme } from "@/theme";
+
+interface ComponentPreviewProps {
+ children: React.ReactNode;
+ code: string;
+ className?: string;
+}
+
+export const ComponentPreview: React.FC = ({
+ children,
+ code,
+ className,
+}) => {
+ const [showCode, setShowCode] = useState(false);
+ const [copied, setCopied] = useState(false);
+ const { theme } = useTheme();
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText(code);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ };
+
+ return (
+
+ {/* Preview */}
+
+
+ {/* Actions */}
+
+ setShowCode(!showCode)}
+ className="text-xs font-medium px-2 py-1 rounded transition-colors"
+ style={{
+ color: showCode
+ ? theme.palette.primary[500]
+ : theme.tokens.foregroundMuted,
+ }}
+ >
+ {showCode ? "Hide Code" : "View Code"}
+
+
+ {copied ? "Copied" : "Copy"}
+
+
+
+ {/* Code */}
+ {showCode && (
+
+ )}
+
+ );
+};
diff --git a/src/docs/components/DropdownDemo.tsx b/src/docs/components/DropdownDemo.tsx
new file mode 100644
index 0000000..2deb4e3
--- /dev/null
+++ b/src/docs/components/DropdownDemo.tsx
@@ -0,0 +1,234 @@
+import React, { useState } from "react";
+import { Dropdown } from "@/components/Dropdown/Dropdown";
+import { Badge } from "@/components/Badge";
+
+export const BasicDemo: React.FC = () => {
+ const [value, setValue] = useState("");
+
+ return (
+
+
+ {value && (
+
+ Selected: {value}
+
+ )}
+
+ );
+};
+
+export const IconDemo: React.FC = () => {
+ const [value, setValue] = useState("");
+
+ return (
+
+
+
+
+
+ ),
+ },
+ {
+ label: "Bank Transfer",
+ value: "bank",
+ icon: (
+
+
+
+
+ ),
+ },
+ {
+ label: "Crypto",
+ value: "crypto",
+ icon: (
+
+
+
+
+
+ ),
+ disabled: true,
+ },
+ ]}
+ />
+
+ );
+};
+
+const COUNTRIES = [
+ "United States",
+ "United Kingdom",
+ "Canada",
+ "Germany",
+ "France",
+ "Japan",
+ "Australia",
+ "Brazil",
+ "India",
+ "South Korea",
+ "Mexico",
+ "Italy",
+ "Spain",
+ "Netherlands",
+ "Sweden",
+ "Norway",
+ "Switzerland",
+ "Singapore",
+ "New Zealand",
+ "Argentina",
+];
+
+export const ScrollDemo: React.FC = () => {
+ const [value, setValue] = useState("");
+
+ return (
+
+ ({
+ label: c,
+ value: c.toLowerCase().replace(/\s+/g, "-"),
+ }))}
+ />
+
+ );
+};
+
+export const SearchDemo: React.FC = () => {
+ const [value, setValue] = useState("");
+
+ return (
+
+ ({
+ label: c,
+ value: c.toLowerCase().replace(/\s+/g, "-"),
+ }))}
+ />
+
+ );
+};
+
+export const SizesDemo: React.FC = () => (
+
+);
+
+export const StatesDemo: React.FC = () => (
+
+
+
+
+
+);
diff --git a/src/docs/components/InfiniteScrollDemo.tsx b/src/docs/components/InfiniteScrollDemo.tsx
new file mode 100644
index 0000000..8203269
--- /dev/null
+++ b/src/docs/components/InfiniteScrollDemo.tsx
@@ -0,0 +1,101 @@
+import React, { useState, useCallback } from "react";
+import { ScrollArea, ScrollElement } from "@/components/ScrollArea";
+import { useTheme } from "@/theme";
+
+export const InfiniteScrollDemo: React.FC = () => {
+ const { theme } = useTheme();
+ const [items, setItems] = useState(
+ Array.from({ length: 8 }, (_, i) => ({
+ id: i + 1,
+ title: `Item ${i + 1}`,
+ type: (["up", "fade", "scale", "blur", "left", "right"] as const)[i % 6],
+ })),
+ );
+ const [loading, setLoading] = useState(false);
+
+ const loadMore = useCallback(() => {
+ if (loading) return;
+ setLoading(true);
+ setTimeout(() => {
+ setItems((prev) => {
+ const start = prev.length;
+ const next = Array.from({ length: 5 }, (_, i) => ({
+ id: start + i + 1,
+ title: `Item ${start + i + 1}`,
+ type: (["up", "fade", "scale", "blur", "left", "right"] as const)[
+ (start + i) % 6
+ ],
+ }));
+ return [...prev, ...next];
+ });
+ setLoading(false);
+ }, 600);
+ }, [loading]);
+
+ const handleScroll = useCallback(
+ (e: React.UIEvent) => {
+ const el = e.currentTarget;
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 40) {
+ loadMore();
+ }
+ },
+ [loadMore],
+ );
+
+ return (
+
+
+
+ {items.length} items loaded
+
+
+ Scroll to bottom to load more
+
+
+
+
+ {items.map((item) => (
+
+
+ {item.title}
+
+ {item.type}
+
+
+
+ ))}
+ {loading && (
+
+ Loading more...
+
+ )}
+
+
+
+ );
+};
diff --git a/src/docs/components/ModalDemo.tsx b/src/docs/components/ModalDemo.tsx
new file mode 100644
index 0000000..560d8dd
--- /dev/null
+++ b/src/docs/components/ModalDemo.tsx
@@ -0,0 +1,46 @@
+import React, { useState } from "react";
+import { Modal } from "@/components/Modal";
+import { Button } from "@/components/Button";
+
+interface ModalDemoProps {
+ title?: string;
+ size?: "sm" | "md" | "lg" | "xl";
+ showCloseButton?: boolean;
+}
+
+export const ModalDemo: React.FC = ({
+ title = "Sample Modal",
+ size = "md",
+ showCloseButton = true,
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
setIsOpen(true)}>
+ Open {size.toUpperCase()} Modal
+
+
+
setIsOpen(false)}
+ title={title}
+ size={size}
+ showCloseButton={showCloseButton}
+ >
+
+
+ This is an interactive demonstration of the Modal component. It
+ supports smooth transitions, backdrop blur, and theme-aware styling.
+
+
+ setIsOpen(false)}>
+ Cancel
+
+ setIsOpen(false)}>Confirm Action
+
+
+
+
+ );
+};
diff --git a/src/docs/components/PageHeader.tsx b/src/docs/components/PageHeader.tsx
new file mode 100644
index 0000000..72dabe2
--- /dev/null
+++ b/src/docs/components/PageHeader.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import { useTheme } from "@/theme";
+
+interface PageHeaderProps {
+ title: string;
+ description: string;
+}
+
+export const PageHeader: React.FC = ({
+ title,
+ description,
+}) => {
+ const { theme } = useTheme();
+
+ return (
+
+
+
+ UI
+
+
+ Awesome UI
+
+
+
{title}
+
+ {description}
+
+
+ );
+};
diff --git a/src/docs/components/PropsTable.tsx b/src/docs/components/PropsTable.tsx
new file mode 100644
index 0000000..2d78f00
--- /dev/null
+++ b/src/docs/components/PropsTable.tsx
@@ -0,0 +1,106 @@
+import React from "react";
+import { useTheme } from "@/theme";
+
+interface PropItem {
+ name: string;
+ type: string;
+ defaultValue?: string;
+ description: string;
+ required?: boolean;
+}
+
+interface PropsTableProps {
+ props: PropItem[];
+}
+
+export const PropsTable: React.FC = ({ props }) => {
+ const { theme } = useTheme();
+
+ return (
+
+
+
+
+
+ Prop
+
+
+ Type
+
+
+ Default
+
+
+ Description
+
+
+
+
+ {props.map((prop) => (
+
+
+ {prop.name}
+ {prop.required && (
+ *
+ )}
+
+
+
+ {prop.type}
+
+
+
+ {prop.defaultValue || "-"}
+
+
+ {prop.description}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/docs/pages/Accordion.mdx b/src/docs/pages/Accordion.mdx
new file mode 100644
index 0000000..dc1fd43
--- /dev/null
+++ b/src/docs/pages/Accordion.mdx
@@ -0,0 +1,66 @@
+import { Accordion } from '@/components/Accordion';
+import { Typography } from '@/components/Typography';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+Click the header to expand or collapse the content.
+
+
+ A collection of theme-aware React components.
+`}>
+
+
What is this library?}
+ subtitle="FAQ"
+ >
+
+ A collection of accessible, theme-aware React components built with Tailwind CSS.
+
+
+
+
+
+## Borderless
+
+Use `variant="borderless"` for a cleaner inline look.
+
+
+ Content...
+`}>
+
+
+ Adjust theme and layout preferences.
+
+
+ Manage data and notification settings.
+
+
+
+
+## Default Open & Disabled
+
+...
+... `}>
+
+
+ This section starts expanded.
+
+
+ Cannot be toggled.
+
+
+
+
+## Props
+
diff --git a/src/docs/pages/Avatar.mdx b/src/docs/pages/Avatar.mdx
new file mode 100644
index 0000000..d2d567d
--- /dev/null
+++ b/src/docs/pages/Avatar.mdx
@@ -0,0 +1,19 @@
+import { Avatar } from '@/components/Avatar';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+Pass a `src` for an image, `displayName` for initials, or nothing for the default icon.
+
+
+
+ `}>
+
+
diff --git a/src/docs/pages/Badge.mdx b/src/docs/pages/Badge.mdx
new file mode 100644
index 0000000..9ffe242
--- /dev/null
+++ b/src/docs/pages/Badge.mdx
@@ -0,0 +1,53 @@
+import { Badge } from '@/components/Badge';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Variants
+
+Color-coded badges for different statuses.
+
+New
+Online
+Pending
+Critical `}>
+
+ New
+ Online
+ Pending
+ Critical
+
+
+
+## Sizes
+
+Small
+Medium
+Large `}>
+
+ Small
+ Medium
+ Large
+
+
+
+## Outline & Loading
+
+Draft
+Loading `}>
+
+ Draft
+ Archived
+ Loading
+
+
+
+## Props
+
diff --git a/src/docs/pages/Button.mdx b/src/docs/pages/Button.mdx
new file mode 100644
index 0000000..fd0a2e7
--- /dev/null
+++ b/src/docs/pages/Button.mdx
@@ -0,0 +1,74 @@
+import { Button } from '@/components/Button';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Variants
+
+Each variant signals a different level of emphasis.
+
+Primary
+Secondary
+Outline
+Ghost `}>
+
+ Primary
+ Secondary
+ Outline
+ Ghost
+
+
+
+## Loading & Disabled
+
+Shows a spinner during async operations. Disabled prevents interaction.
+
+Saving...
+Unavailable `}>
+
+ Saving...
+ Unavailable
+ Loading
+
+
+
+## Sizes
+
+Three sizes: `sm` for toolbars, `md` for forms, `lg` for landing pages.
+
+Small
+Medium
+Large `}>
+
+ Small
+ Medium
+ Large
+
+
+
+## Semantic Colors
+
+Convey intent at a glance — success, warning, or danger.
+
+Save
+Reset
+Delete `}>
+
+ Save
+ Reset
+ Delete
+
+
+
+## Props
+
diff --git a/src/docs/pages/Checkbox.mdx b/src/docs/pages/Checkbox.mdx
new file mode 100644
index 0000000..2eb9abc
--- /dev/null
+++ b/src/docs/pages/Checkbox.mdx
@@ -0,0 +1,56 @@
+import { Checkbox } from '@/components/Checkbox';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+ `}>
+
+
+
+## With Description
+
+ `}>
+
+
+
+## Indeterminate
+
+Used for "select all" when only some children are checked.
+
+ `}>
+
+
+
+## Sizes
+
+
+
+ `}>
+
+
+
+
+
+
+
+## Props
+
diff --git a/src/docs/pages/Dropdown.mdx b/src/docs/pages/Dropdown.mdx
new file mode 100644
index 0000000..df7accf
--- /dev/null
+++ b/src/docs/pages/Dropdown.mdx
@@ -0,0 +1,140 @@
+import { Dropdown } from '@/components/Dropdown/Dropdown';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+import { BasicDemo, IconDemo, ScrollDemo, SearchDemo, SizesDemo, StatesDemo } from '../components/DropdownDemo';
+
+
+
+## Basic Usage
+
+Use `selectedValue` and `onChange` to control the selected option. Options support `label`, `value`, and `disabled`.
+
+ `}>
+
+
+
+## With Icons
+
+Each option can include an `icon` prop. The icon is displayed in both the trigger and the dropdown list.
+
+ },
+ { label: "Bank Transfer", value: "bank", icon: },
+ { label: "Crypto", value: "crypto", icon: , disabled: true },
+ ]}
+ selectedValue={value}
+ onChange={setValue}
+/>`}>
+
+
+
+## Searchable
+
+Add `searchable` to enable a filter input inside the panel. Options are filtered as you type, with keyboard navigation still fully supported.
+
+ ({ label: c, value: c.toLowerCase() }))}
+ selectedValue={value}
+ onChange={setValue}
+/>`}>
+
+
+
+## Scrollable List
+
+When options exceed the panel height, a themed scrollbar appears. Keyboard navigation (Arrow keys, Home, End) auto-scrolls the highlighted item into view.
+
+ ({ label: c, value: c.toLowerCase() }))}
+ selectedValue={value}
+ onChange={setValue}
+/>`}>
+
+
+
+## Sizes
+
+Three size variants: `sm`, `md` (default), and `lg`.
+
+
+
+ `}>
+
+
+
+## States
+
+Error validation, disabled state, and loading skeleton.
+
+
+
+ `}>
+
+
+
+## Keyboard Navigation
+
+| Key | Action |
+|-----|--------|
+| `Arrow Down` | Open dropdown / highlight next option |
+| `Arrow Up` | Open dropdown / highlight previous option |
+| `Enter` / `Space` | Select highlighted option / open dropdown |
+| `Escape` | Close dropdown |
+| `Home` | Jump to first enabled option |
+| `End` | Jump to last enabled option |
+| Type characters | Filter options (when `searchable` is enabled) |
+
+## Props
+
+ void', description: 'Callback fired when an option is selected.' },
+ { name: 'placeholder', type: 'string', defaultValue: '"Select option"', description: 'Placeholder text when no option is selected.' },
+ { name: 'searchable', type: 'boolean', defaultValue: 'false', description: 'Enables a search input inside the dropdown panel.' },
+ { name: 'searchPlaceholder', type: 'string', defaultValue: '"Search..."', description: 'Placeholder for the search input.' },
+ { name: 'label', type: 'ReactNode', description: 'Label rendered above the trigger.' },
+ { name: 'required', type: 'boolean', defaultValue: 'false', description: 'Shows a required indicator on the label.' },
+ { name: 'size', type: '"sm" | "md" | "lg"', defaultValue: '"md"', description: 'Size variant of the trigger.' },
+ { name: 'error', type: 'string', description: 'Error message displayed below the trigger.' },
+ { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Disables the dropdown.' },
+ { name: 'isLoading', type: 'boolean', defaultValue: 'false', description: 'Shows a skeleton loader.' },
+ { name: 'isOpen', type: 'boolean', description: 'Controlled open state.' },
+ { name: 'defaultOpen', type: 'boolean', defaultValue: 'false', description: 'Initial open state (uncontrolled).' },
+ { name: 'offset', type: 'number', defaultValue: '4', description: 'Gap between trigger and panel in pixels.' },
+ { name: 'zIndex', type: 'number', defaultValue: '50', description: 'CSS z-index of the dropdown panel.' },
+ { name: 'className', type: 'string', description: 'Custom class for the trigger button.' },
+]} />
+
+### DropdownOption
+
+
diff --git a/src/docs/pages/Input.mdx b/src/docs/pages/Input.mdx
new file mode 100644
index 0000000..05672ac
--- /dev/null
+++ b/src/docs/pages/Input.mdx
@@ -0,0 +1,69 @@
+import { Input } from '@/components/Input';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+A labeled input with placeholder and helper text.
+
+ `}>
+
+
+
+
+
+## Icons & Loading
+
+Add context with leading/trailing icons, or show a spinner during validation.
+
+🔍 } placeholder="Search..." />
+ 👁️} type="password" />
+ `}>
+
+ 🔍} placeholder="Search..." />
+ 👁️} type="password" placeholder="••••••••" />
+
+
+
+
+## Validation
+
+Error messages replace helper text and recolor the border.
+
+ `}>
+
+
+
+
+
+## Sizes
+
+
+
+ `}>
+
+
+
+
+
+
+
+## Props
+
diff --git a/src/docs/pages/Modal.mdx b/src/docs/pages/Modal.mdx
new file mode 100644
index 0000000..06488a2
--- /dev/null
+++ b/src/docs/pages/Modal.mdx
@@ -0,0 +1,51 @@
+import { ModalDemo } from '../components/ModalDemo';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+Control visibility with `isOpen` and `onClose`.
+
+ setIsOpen(true)}>Open
+ setIsOpen(false)} title="Confirm">
+ Are you sure?
+ `}>
+
+
+
+## Sizes
+
+Four widths: `sm`, `md`, `lg`, and `xl`.
+
+
+ `}>
+
+
+
+
+
+
+
+## Without Close Button
+
+Set `showCloseButton={false}` to require an explicit action.
+
+ `}>
+
+
+
+## Props
+ void', required: true, description: 'Called on close.' },
+ { name: 'title', type: 'string', description: 'Header text.' },
+ { name: 'size', type: '"sm" | "md" | "lg" | "xl"', defaultValue: '"md"', description: 'Dialog width.' },
+ { name: 'showCloseButton', type: 'boolean', defaultValue: 'true', description: 'Shows the X button.' },
+ { name: 'preventBackdropClose', type: 'boolean', defaultValue: 'false', description: 'Disables backdrop click close.' },
+ { name: 'preventEscapeClose', type: 'boolean', defaultValue: 'false', description: 'Disables Escape key close.' },
+]} />
diff --git a/src/docs/pages/Popover.mdx b/src/docs/pages/Popover.mdx
new file mode 100644
index 0000000..90014db
--- /dev/null
+++ b/src/docs/pages/Popover.mdx
@@ -0,0 +1,213 @@
+import { Popover } from '@/components/Popover';
+import { Button } from '@/components/Button';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Usage
+
+Use popovers for contextual actions, mini-menus, or secondary info. Keep content concise.
+Avoid for critical workflows—use Modals instead. Always ensure a clear trigger.
+
+Account Settings}>
+ Profile
+`}>
+
+
+ Account Settings
+ View and update your profile.
+ Manage Account
+
+ }
+ >
+ Profile
+
+
+
+
+---
+
+## Anatomy
+
+The popover is composed of two parts that live in separate parts of the DOM: a **trigger** (your `children`) and a **floating panel** (your `content`, portalled to ``). An optional arrow bridges the visual gap between them.
+
+---
+
+## Placements & Arrows
+
+You can control alignment and whether an arrow points directly back to the trigger.
+
+Additional detailed info goes here.}>
+ Arrow Attached
+`}>
+
+
Additional detailed info goes here. }
+ >
+ Arrow Attached
+
+
+
+
+---
+
+## Controlled state
+
+Use `isOpen` + `onOpenChange` when you need external control — for instance, to close the
+popover programmatically after a form submission.
+
+
+
+ This popover is fully controlled.
+ setOpen(false)}>Close
+
+ }
+ >
+ Controlled trigger
+
+ setOpen(true)}>
+ Open from outside
+
+`}>
+ {() => {
+ const [open, setOpen] = useState(false);
+ return (
+
+
+ This popover is fully controlled.
+ setOpen(false)}>Close
+
+ }
+ >
+ Controlled trigger
+
+ setOpen(true)}>
+ Open from outside
+
+
+ );
+ }}
+
+
+---
+
+## Disabled state
+
+Set `disabled` to prevent the popover from opening. The trigger is also visually muted
+and non-interactive.
+
+You won't see this.}
+>
+ Disabled trigger
+`}>
+ You won't see this.}
+ >
+ Disabled trigger
+
+
+
+---
+
+## Nested popovers
+
+Popovers can be nested — each manages its own open state independently. Click outside the
+outermost panel to close all of them. Use `zIndex` to stack panels correctly.
+
+
+ Parent popover
+
+ Child popover
+ Nested content here.
+
+ }
+ >
+
+ Hover for child →
+
+
+
+ }
+>
+ Open parent
+`}>
+
+ Parent popover
+
+ Child popover
+ Nested content here.
+
+ }
+ >
+
+ Open child →
+
+
+
+ }
+ >
+ Open parent
+
+
+
+---
+
+## Props
+
+.' },
+ { name: 'content', type: 'ReactNode', description: 'The content rendered inside the floating panel.' },
+ { name: 'placement', type: 'Placement', defaultValue: '"bottom"', description: 'Preferred placement relative to the trigger. Floating UI will flip/shift automatically if there is not enough space.' },
+ { name: 'isOpen', type: 'boolean', description: 'Controlled open state. Requires onOpenChange to be provided.' },
+ { name: 'defaultOpen', type: 'boolean', defaultValue: 'false', description: 'Initial open state for uncontrolled usage.' },
+ { name: 'onOpenChange', type: '(open: boolean) => void', description: 'Callback fired when the open state changes.' },
+ { name: 'offset', type: 'number', defaultValue: '8', description: 'Gap in px between trigger and panel. Arrow height is added automatically when showArrow is true.' },
+ { name: 'showArrow', type: 'boolean', defaultValue: 'false', description: 'Renders a directional arrow pointing from the panel toward the trigger.' },
+ { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Prevents the popover from opening and mutes the trigger.' },
+ { name: 'className', type: 'string', description: 'Extra class names forwarded to the inner panel wrapper.' },
+ { name: 'zIndex', type: 'number', defaultValue: '1000', description: 'CSS z-index of the floating panel. Increase when nesting inside other layered elements.' },
+ { name: 'id', type: 'string', description: 'Optional id forwarded to the trigger. Defaults to a stable React-generated id.' },
+]} />
\ No newline at end of file
diff --git a/src/docs/pages/RadioGroup.mdx b/src/docs/pages/RadioGroup.mdx
new file mode 100644
index 0000000..eb4dc08
--- /dev/null
+++ b/src/docs/pages/RadioGroup.mdx
@@ -0,0 +1,62 @@
+import { RadioGroup } from '@/components/RadioGroup';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+ `}>
+
+
+
+## Card Variant
+
+A more prominent, button-like selection style.
+
+ `}>
+
+
+
+## Horizontal
+
+ `}>
+
+
+
+## Props
+
diff --git a/src/docs/pages/ScrollArea.mdx b/src/docs/pages/ScrollArea.mdx
new file mode 100644
index 0000000..36b14c8
--- /dev/null
+++ b/src/docs/pages/ScrollArea.mdx
@@ -0,0 +1,159 @@
+import { ScrollArea, ScrollElement as ScrollAreaItem } from '@/components/ScrollArea';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+import { InfiniteScrollDemo } from '../components/InfiniteScrollDemo';
+
+
+
+## Infinite Scroll
+
+Use the `onScroll` callback to detect when the user reaches the bottom and load more items.
+
+ {
+ const el = e.currentTarget;
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 40) {
+ loadMore(); // fetch and append items
+ }
+ };
+
+ return (
+
+ {items.map(item => (
+
+ {item.title}
+
+ ))}
+
+ );
+}`}>
+
+
+
+## Vertical Scroll
+
+Items animate as they enter the viewport. Scroll down through the list.
+
+
+ Item 1
+ Item 2
+ Item 3
+`}>
+
+
+
+
+ Scroll down to see each animation
+
+
+
+ Reveal Up
+
+
+ Reveal Down
+
+
+ Fade In
+
+
+ Scale Pop
+
+
+ Blur Entrance
+
+
+ From Left
+
+
+ From Right
+
+
+ Another Up
+
+
+ Another Scale
+
+
+ Another Blur
+
+
+ End of list
+
+
+
+
+
+## Horizontal Scroll
+
+Set a wide inner container to enable horizontal scrolling.
+
+
+
+`}>
+
+
+
+
+
Card 1
+
Scroll right
+
+
+
+
+
Card 2
+
Horizontal reveal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Animation Types
+
+| Type | Effect |
+| :--- | :--- |
+| `up` / `down` | Slide with fade |
+| `left` / `right` | Horizontal slide |
+| `fade` | Opacity only |
+| `scale` | Zoom in |
+| `blur` | Blur to clear |
+
+## Props
+
+### ScrollArea
+
+
+### ScrollElement
+
diff --git a/src/docs/pages/Skeleton.mdx b/src/docs/pages/Skeleton.mdx
new file mode 100644
index 0000000..fa8bd93
--- /dev/null
+++ b/src/docs/pages/Skeleton.mdx
@@ -0,0 +1,62 @@
+import { Skeleton } from '@/components/Skeleton';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Basic Usage
+
+Combine shapes to mock a content card during loading.
+
+
+
+ `}>
+
+
+
+## Animations
+
+Wave sweeps across the surface. Pulse fades in and out. None is static.
+
+
+
+ `}>
+
+
+ Wave
+
+
+
+ Pulse
+
+
+
+ None
+
+
+
+
+
+## Props
+
diff --git a/src/docs/pages/Switch.mdx b/src/docs/pages/Switch.mdx
new file mode 100644
index 0000000..a3db004
--- /dev/null
+++ b/src/docs/pages/Switch.mdx
@@ -0,0 +1,44 @@
+import { Switch } from '@/components/Switch';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Usage
+
+Use for immediate actions, like system preferences or binary settings. Avoid for settings requiring a "Save" action—use Checkboxes instead.
+Ensure labels are clear and phrased positively (e.g. "Enable Notifications").
+
+ `}>
+
+
+
+
+
+## Sizes & States
+
+You can provide physical sizes, disabled states, and contextual errors or loading skeletons.
+
+
+
+ `}>
+
+
+
+
+
+
+
+## Props
+ void', description: 'Callback fired on toggle.' },
+ { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Prevents interaction.' },
+ { name: 'size', type: '"small" | "medium" | "large"', defaultValue: '"medium"', description: 'Physical size of the switch.' },
+ { name: 'label', type: 'ReactNode', description: 'Main label text.' },
+ { name: 'description', type: 'ReactNode', description: 'Contextual sub-label text.' },
+ { name: 'helperText', type: 'ReactNode', description: 'Text below the switch.' },
+ { name: 'error', type: 'ReactNode', description: 'Error state message.' },
+ { name: 'isLoading', type: 'boolean', defaultValue: 'false', description: 'Displays skeleton loader.' },
+]} />
diff --git a/src/docs/pages/Theming.mdx b/src/docs/pages/Theming.mdx
new file mode 100644
index 0000000..b8056b8
--- /dev/null
+++ b/src/docs/pages/Theming.mdx
@@ -0,0 +1,219 @@
+import { Button } from '@/components/Button';
+import { Badge } from '@/components/Badge';
+import { Input } from '@/components/Input';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+
+The library ships with a robust, token-based theming system. Every component reads its colors from a centralized theme, so one change updates everything.
+
+## Quick Start
+
+Wrap your app in `ThemeProvider` to enable theming across all components.
+
+```tsx
+import { ThemeProvider } from 'ui-library';
+
+function App() {
+ return (
+
+
+
+ );
+}
+```
+
+## Architecture
+
+The theme system is built on three layers:
+
+| Layer | Purpose | Example |
+| :--- | :--- | :--- |
+| **Palette** | Raw color scales (50–900) | `primary.500`, `error.600` |
+| **Tokens** | Semantic roles that reference palette values | `background`, `accent`, `border` |
+| **CSS Variables** | Applied to `:root` for zero-JS styling | `--color-background`, `--color-accent` |
+
+This means:
+- **No runtime style calculations** — components read CSS variables directly.
+- **Zero FOUC** — variables are set before first paint.
+- **Full customization** — override any layer to match your brand.
+
+## Color Modes
+
+Toggle between light and dark modes with a single hook call.
+
+
+ Current: {theme.colorMode}
+
+ );
+}`}>
+
+
+ Use the toggle in the top-right header to switch modes.
+
+
+
+
+## Dynamic Overrides
+
+Override the theme at runtime using `overrideTheme`. This is how the color picker on the home page works.
+
+```tsx
+import { useTheme } from 'ui-library';
+
+function BrandSwitcher() {
+ const { overrideTheme, resetTheme } = useTheme();
+
+ const applyBrand = () => {
+ overrideTheme({
+ palette: {
+ primary: { 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8' }
+ },
+ tokens: {
+ accent: '#2563eb',
+ accentHover: '#1d4ed8',
+ }
+ });
+ };
+
+ return (
+ <>
+ Apply Blue Brand
+ Reset
+ >
+ );
+}
+```
+
+Partial overrides are deep-merged — you only need to specify the values you want to change.
+
+## Registering Custom Themes
+
+Create a fully custom theme and register it so it can be activated by name.
+
+```tsx
+import { registerTheme, type ThemeConfig } from 'ui-library';
+
+const corporateTheme: ThemeConfig = {
+ name: 'corporate',
+ colorMode: 'light',
+ palette: {
+ primary: { 50: '#eff6ff', 100: '#dbeafe', /* ...full scale */ },
+ success: { /* ... */ },
+ warning: { /* ... */ },
+ error: { /* ... */ },
+ info: { /* ... */ },
+ },
+ neutral: { /* 50–900 gray scale */ },
+ tokens: {
+ background: '#ffffff',
+ surface: '#f8fafc',
+ foreground: '#0f172a',
+ foregroundMuted: '#64748b',
+ accent: '#2563eb',
+ // ...all semantic tokens
+ },
+ shape: {
+ radiusSm: '0.25rem',
+ radiusMd: '0.5rem',
+ radiusLg: '0.75rem',
+ radiusFull: '9999px',
+ },
+ typography: {
+ fontFamilySans: '"Inter", sans-serif',
+ fontFamilyMono: '"Fira Code", monospace',
+ },
+};
+
+// Register before rendering
+registerTheme(corporateTheme);
+
+// Then activate it
+const { setTheme } = useTheme();
+setTheme('corporate');
+```
+
+## Semantic Tokens
+
+Tokens give colors a **role** instead of a raw value. This makes themes portable — switch the palette and every component adapts.
+
+
+
+## useTheme Hook
+
+The `useTheme` hook provides full access to the theme system.
+
+```tsx
+const {
+ theme, // Current ThemeConfig object
+ setTheme, // Switch to a registered theme by name
+ toggleColorMode, // Toggle light/dark
+ overrideTheme, // Apply partial overrides
+ resetTheme, // Clear all overrides
+} = useTheme();
+```
+
+## useTokens Hook
+
+A convenience hook that returns just the semantic tokens — useful in components that only need color values.
+
+```tsx
+import { useTokens } from 'ui-library';
+
+function Card({ children }) {
+ const tokens = useTokens();
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+## CSS Variables Reference
+
+All theme values are applied as CSS custom properties on `:root`. You can use them directly in your own CSS or Tailwind classes.
+
+```css
+/* Use in your own stylesheets */
+.my-card {
+ background: var(--color-surface);
+ border: 1px solid var(--color-border);
+ color: var(--color-foreground);
+ border-radius: var(--radius-md);
+}
+```
+
+| Variable | Source |
+| :--- | :--- |
+| `--color-background` | `tokens.background` |
+| `--color-surface` | `tokens.surface` |
+| `--color-foreground` | `tokens.foreground` |
+| `--color-border` | `tokens.border` |
+| `--color-accent` | `tokens.accent` |
+| `--color-ring` | `tokens.ring` |
+| `--color-primary-500` | `palette.primary.500` |
+| `--radius-sm` / `md` / `lg` | `shape.radius*` |
+| `--font-sans` / `--font-mono` | `typography.fontFamily*` |
diff --git a/src/docs/pages/ThemingPage.tsx b/src/docs/pages/ThemingPage.tsx
new file mode 100644
index 0000000..4617191
--- /dev/null
+++ b/src/docs/pages/ThemingPage.tsx
@@ -0,0 +1,873 @@
+import React, { useState } from "react";
+import { useTheme } from "@/theme";
+import type { SemanticTokens } from "@/theme";
+import { Button } from "@/components/Button";
+import { Badge } from "@/components/Badge";
+import { Input } from "@/components/Input";
+import { Checkbox } from "@/components/Checkbox";
+import { Dropdown } from "@/components/Dropdown/Dropdown";
+import Switch from "@/components/Switch/Switch";
+import { PageHeader } from "../components/PageHeader";
+
+const BRAND_PRESETS = [
+ { name: "Pink", color: "#ec4899" },
+ { name: "Blue", color: "#3b82f6" },
+ { name: "Violet", color: "#8b5cf6" },
+ { name: "Emerald", color: "#10b981" },
+ { name: "Orange", color: "#f97316" },
+ { name: "Red", color: "#ef4444" },
+ { name: "Cyan", color: "#06b6d4" },
+ { name: "Rose", color: "#f43f5e" },
+ { name: "Indigo", color: "#6366f1" },
+ { name: "Amber", color: "#f59e0b" },
+ { name: "Teal", color: "#14b8a6" },
+ { name: "Lime", color: "#84cc16" },
+];
+
+function hexToHSL(hex: string): [number, number, number] {
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
+ const max = Math.max(r, g, b),
+ min = Math.min(r, g, b);
+ const l = (max + min) / 2;
+ if (max === min) return [0, 0, l];
+ const d = max - min;
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ let h = 0;
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
+ else if (max === g) h = ((b - r) / d + 2) / 6;
+ else h = ((r - g) / d + 4) / 6;
+ return [h * 360, s * 100, l * 100];
+}
+
+function hslToHex(h: number, s: number, l: number): string {
+ s /= 100;
+ l /= 100;
+ const a = s * Math.min(l, 1 - l);
+ const f = (n: number) => {
+ const k = (n + h / 30) % 12;
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
+ return Math.round(255 * Math.max(0, Math.min(1, color)))
+ .toString(16)
+ .padStart(2, "0");
+ };
+ return `#${f(0)}${f(8)}${f(4)}`;
+}
+
+function generateScale(hex: string) {
+ const [h, s] = hexToHSL(hex);
+ return {
+ 50: hslToHex(h, Math.min(100, s * 1.1), 97),
+ 100: hslToHex(h, Math.min(100, s * 1.1), 93),
+ 200: hslToHex(h, Math.min(100, s), 85),
+ 300: hslToHex(h, Math.min(100, s * 0.95), 73),
+ 400: hslToHex(h, Math.min(100, s * 0.9), 60),
+ 500: hex,
+ 600: hslToHex(h, Math.min(100, s * 0.95), 42),
+ 700: hslToHex(h, Math.min(100, s * 0.9), 34),
+ 800: hslToHex(h, Math.min(100, s * 0.85), 26),
+ 900: hslToHex(h, Math.min(100, s * 0.8), 18),
+ };
+}
+
+const CodeBlock: React.FC<{ children: string }> = ({ children }) => {
+ const { theme } = useTheme();
+ const [copied, setCopied] = useState(false);
+
+ return (
+
+
+ {children}
+
+
{
+ navigator.clipboard.writeText(children);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ }}
+ className="absolute top-3 right-3 text-xs font-medium px-2.5 py-1 rounded-md transition-colors"
+ style={{
+ backgroundColor: copied
+ ? "rgba(74, 222, 128, 0.15)"
+ : "rgba(255,255,255,0.08)",
+ color: copied ? "#4ade80" : "#999",
+ }}
+ >
+ {copied ? "Copied!" : "Copy"}
+
+
+ );
+};
+
+const Section: React.FC<{
+ title: string;
+ description?: string;
+ children: React.ReactNode;
+}> = ({ title, description, children }) => {
+ const { theme } = useTheme();
+ return (
+
+ {title}
+ {description && (
+
+ {description}
+
+ )}
+ {children}
+
+ );
+};
+
+// ─── Dark Theme Presets ────────────────────────────────────────────────────
+
+interface ThemePreset {
+ name: string;
+ description: string;
+ preview: [string, string, string]; // 3 colors for the preview swatch
+ neutral: Record;
+ tokens: Partial;
+}
+
+const DARK_PRESETS: ThemePreset[] = [
+ {
+ name: "Midnight",
+ description: "Pure black with neutral grays",
+ preview: ["#080808", "#181818", "#f2f2f2"],
+ neutral: {
+ 50: "#080808",
+ 100: "#101010",
+ 200: "#181818",
+ 300: "#202020",
+ 400: "#383838",
+ 500: "#585858",
+ 600: "#787878",
+ 700: "#a0a0a0",
+ 800: "#c8c8c8",
+ 900: "#f2f2f2",
+ },
+ tokens: {
+ background: "#080808",
+ surface: "#101010",
+ surfaceHover: "#181818",
+ elevated: "#181818",
+ foreground: "#f2f2f2",
+ foregroundMuted: "#a0a0a0",
+ foregroundSubtle: "#787878",
+ foregroundInverse: "#0b0f14",
+ border: "#202020",
+ borderSubtle: "#181818",
+ borderStrong: "#383838",
+ input: "#181818",
+ inputHover: "#202020",
+ inputFocus: "#101010",
+ ringOffset: "#101010",
+ },
+ },
+ {
+ name: "Ocean",
+ description: "Deep blue-tinted dark theme",
+ preview: ["#0a1628", "#111d33", "#e2e8f0"],
+ neutral: {
+ 50: "#0a1628",
+ 100: "#0f1d2e",
+ 200: "#111d33",
+ 300: "#1a2a42",
+ 400: "#2a3f5f",
+ 500: "#4a6182",
+ 600: "#6b82a0",
+ 700: "#8fa3bb",
+ 800: "#c4d1de",
+ 900: "#e2e8f0",
+ },
+ tokens: {
+ background: "#0a1628",
+ surface: "#0f1d2e",
+ surfaceHover: "#111d33",
+ elevated: "#111d33",
+ foreground: "#e2e8f0",
+ foregroundMuted: "#8fa3bb",
+ foregroundSubtle: "#6b82a0",
+ foregroundInverse: "#0a1628",
+ border: "#1a2a42",
+ borderSubtle: "#111d33",
+ borderStrong: "#2a3f5f",
+ input: "#111d33",
+ inputHover: "#1a2a42",
+ inputFocus: "#0f1d2e",
+ ringOffset: "#0f1d2e",
+ },
+ },
+ {
+ name: "Slate",
+ description: "Cool gray with blue undertones",
+ preview: ["#0f172a", "#1e293b", "#f1f5f9"],
+ neutral: {
+ 50: "#0f172a",
+ 100: "#1e293b",
+ 200: "#1e293b",
+ 300: "#334155",
+ 400: "#475569",
+ 500: "#64748b",
+ 600: "#94a3b8",
+ 700: "#cbd5e1",
+ 800: "#e2e8f0",
+ 900: "#f1f5f9",
+ },
+ tokens: {
+ background: "#0f172a",
+ surface: "#1e293b",
+ surfaceHover: "#273549",
+ elevated: "#1e293b",
+ foreground: "#f1f5f9",
+ foregroundMuted: "#94a3b8",
+ foregroundSubtle: "#64748b",
+ foregroundInverse: "#0f172a",
+ border: "#334155",
+ borderSubtle: "#1e293b",
+ borderStrong: "#475569",
+ input: "#1e293b",
+ inputHover: "#334155",
+ inputFocus: "#1e293b",
+ ringOffset: "#1e293b",
+ },
+ },
+ {
+ name: "Violet Night",
+ description: "Dark purple-tinted surfaces",
+ preview: ["#0e0a1a", "#1a1425", "#ede9fe"],
+ neutral: {
+ 50: "#0e0a1a",
+ 100: "#141020",
+ 200: "#1a1425",
+ 300: "#251e35",
+ 400: "#3b3255",
+ 500: "#5b5278",
+ 600: "#7c739a",
+ 700: "#a09abc",
+ 800: "#cbc5de",
+ 900: "#ede9fe",
+ },
+ tokens: {
+ background: "#0e0a1a",
+ surface: "#141020",
+ surfaceHover: "#1a1425",
+ elevated: "#1a1425",
+ foreground: "#ede9fe",
+ foregroundMuted: "#a09abc",
+ foregroundSubtle: "#7c739a",
+ foregroundInverse: "#0e0a1a",
+ border: "#251e35",
+ borderSubtle: "#1a1425",
+ borderStrong: "#3b3255",
+ input: "#1a1425",
+ inputHover: "#251e35",
+ inputFocus: "#141020",
+ ringOffset: "#141020",
+ },
+ },
+ {
+ name: "Emerald Dark",
+ description: "Forest green undertones",
+ preview: ["#071210", "#0d1f1b", "#ecfdf5"],
+ neutral: {
+ 50: "#071210",
+ 100: "#0a1a17",
+ 200: "#0d1f1b",
+ 300: "#162e28",
+ 400: "#264a40",
+ 500: "#3d6b5e",
+ 600: "#5a8c7c",
+ 700: "#86b5a2",
+ 800: "#b8d8ca",
+ 900: "#ecfdf5",
+ },
+ tokens: {
+ background: "#071210",
+ surface: "#0a1a17",
+ surfaceHover: "#0d1f1b",
+ elevated: "#0d1f1b",
+ foreground: "#ecfdf5",
+ foregroundMuted: "#86b5a2",
+ foregroundSubtle: "#5a8c7c",
+ foregroundInverse: "#071210",
+ border: "#162e28",
+ borderSubtle: "#0d1f1b",
+ borderStrong: "#264a40",
+ input: "#0d1f1b",
+ inputHover: "#162e28",
+ inputFocus: "#0a1a17",
+ ringOffset: "#0a1a17",
+ },
+ },
+ {
+ name: "Warm Dark",
+ description: "Cozy dark with warm brown tones",
+ preview: ["#120f0b", "#1c1814", "#faf5ef"],
+ neutral: {
+ 50: "#120f0b",
+ 100: "#181510",
+ 200: "#1c1814",
+ 300: "#2a2520",
+ 400: "#3e3830",
+ 500: "#5c5448",
+ 600: "#7a7165",
+ 700: "#a09688",
+ 800: "#c8bfb2",
+ 900: "#faf5ef",
+ },
+ tokens: {
+ background: "#120f0b",
+ surface: "#181510",
+ surfaceHover: "#1c1814",
+ elevated: "#1c1814",
+ foreground: "#faf5ef",
+ foregroundMuted: "#a09688",
+ foregroundSubtle: "#7a7165",
+ foregroundInverse: "#120f0b",
+ border: "#2a2520",
+ borderSubtle: "#1c1814",
+ borderStrong: "#3e3830",
+ input: "#1c1814",
+ inputHover: "#2a2520",
+ inputFocus: "#181510",
+ ringOffset: "#181510",
+ },
+ },
+];
+
+const LIGHT_PRESETS: ThemePreset[] = [
+ {
+ name: "Default",
+ description: "Clean white with neutral grays",
+ preview: ["#fbfaf9", "#ffffff", "#111827"],
+ neutral: {
+ 50: "#fbfaf9",
+ 100: "#f3f4f6",
+ 200: "#e5e7eb",
+ 300: "#d1d5db",
+ 400: "#9ca3af",
+ 500: "#6b7280",
+ 600: "#4b5563",
+ 700: "#374151",
+ 800: "#1f2937",
+ 900: "#111827",
+ },
+ tokens: {
+ background: "#fbfaf9",
+ surface: "#ffffff",
+ surfaceHover: "#f3f4f6",
+ elevated: "#ffffff",
+ foreground: "#111827",
+ foregroundMuted: "#6b7280",
+ foregroundSubtle: "#9ca3af",
+ foregroundInverse: "#ffffff",
+ border: "#e5e7eb",
+ borderSubtle: "#f3f4f6",
+ borderStrong: "#9ca3af",
+ input: "#ffffff",
+ inputHover: "#fbfaf9",
+ inputFocus: "#ffffff",
+ ringOffset: "#ffffff",
+ },
+ },
+ {
+ name: "Snow Blue",
+ description: "Soft blue-tinted light theme",
+ preview: ["#f0f4f8", "#ffffff", "#1a2b4a"],
+ neutral: {
+ 50: "#f0f4f8",
+ 100: "#e8edf2",
+ 200: "#d5dce6",
+ 300: "#b8c4d4",
+ 400: "#8a9ab5",
+ 500: "#5d7096",
+ 600: "#445a7a",
+ 700: "#344763",
+ 800: "#25344c",
+ 900: "#1a2b4a",
+ },
+ tokens: {
+ background: "#f0f4f8",
+ surface: "#ffffff",
+ surfaceHover: "#e8edf2",
+ elevated: "#ffffff",
+ foreground: "#1a2b4a",
+ foregroundMuted: "#5d7096",
+ foregroundSubtle: "#8a9ab5",
+ foregroundInverse: "#ffffff",
+ border: "#d5dce6",
+ borderSubtle: "#e8edf2",
+ borderStrong: "#8a9ab5",
+ input: "#ffffff",
+ inputHover: "#f0f4f8",
+ inputFocus: "#ffffff",
+ ringOffset: "#ffffff",
+ },
+ },
+ {
+ name: "Warm Sand",
+ description: "Warm cream backgrounds",
+ preview: ["#faf6f0", "#ffffff", "#2d1f10"],
+ neutral: {
+ 50: "#faf6f0",
+ 100: "#f3ede4",
+ 200: "#e5ddd0",
+ 300: "#d1c5b4",
+ 400: "#a89680",
+ 500: "#7a6b58",
+ 600: "#5c4f3e",
+ 700: "#443a2e",
+ 800: "#322b20",
+ 900: "#2d1f10",
+ },
+ tokens: {
+ background: "#faf6f0",
+ surface: "#ffffff",
+ surfaceHover: "#f3ede4",
+ elevated: "#ffffff",
+ foreground: "#2d1f10",
+ foregroundMuted: "#7a6b58",
+ foregroundSubtle: "#a89680",
+ foregroundInverse: "#ffffff",
+ border: "#e5ddd0",
+ borderSubtle: "#f3ede4",
+ borderStrong: "#a89680",
+ input: "#ffffff",
+ inputHover: "#faf6f0",
+ inputFocus: "#ffffff",
+ ringOffset: "#ffffff",
+ },
+ },
+];
+
+// ─── Theme Preset Card ────────────────────────────────────────────────────
+
+const PresetCard: React.FC<{
+ preset: ThemePreset;
+ active: boolean;
+ onSelect: () => void;
+}> = ({ preset, active, onSelect }) => {
+ const { theme } = useTheme();
+ return (
+
+ {/* Preview swatches */}
+
+ {preset.preview.map((c, i) => (
+
+ ))}
+
+ {preset.name}
+
+ {preset.description}
+
+
+ );
+};
+
+// ─── Main Page ────────────────────────────────────────────────────────────
+
+export const ThemingPage: React.FC = () => {
+ const { theme, overrideTheme, resetTheme, toggleColorMode } = useTheme();
+ const [activePreset, setActivePreset] = useState(theme.palette.primary[500]);
+ const [activeVariation, setActiveVariation] = useState(null);
+ const [ddValue, setDdValue] = useState("");
+ const [switchOn, setSwitchOn] = useState(false);
+ const [checked, setChecked] = useState(true);
+
+ const variations = theme.colorMode === "dark" ? DARK_PRESETS : LIGHT_PRESETS;
+
+ const applyColor = (color: string) => {
+ setActivePreset(color);
+ const scale = generateScale(color);
+ overrideTheme({
+ palette: { primary: scale },
+ tokens: { accent: scale[600], accentHover: scale[700], ring: scale[500] },
+ });
+ };
+
+ const applyVariation = (preset: ThemePreset) => {
+ setActiveVariation(preset.name);
+ overrideTheme({
+ neutral: preset.neutral as unknown as typeof theme.neutral,
+ tokens: preset.tokens,
+ });
+ };
+
+ const handleReset = () => {
+ setActivePreset("#ec4899");
+ setActiveVariation(null);
+ resetTheme();
+ };
+
+ return (
+
+
+
+ {/* Installation */}
+
+ {`npm install awesome-ui-library`}
+
+
+ {/* Setup */}
+
+ {`import { ThemeProvider } from 'awesome-ui-library';
+
+function App() {
+ return (
+
+
+
+ );
+}`}
+
+
+ {/* How it works */}
+
+
+ {[
+ {
+ step: "1",
+ layer: "Palette",
+ desc: "Raw color scales from 50 to 900.",
+ code: "palette.primary[500]",
+ },
+ {
+ step: "2",
+ layer: "Tokens",
+ desc: "Semantic roles like accent and border.",
+ code: "tokens.accent",
+ },
+ {
+ step: "3",
+ layer: "CSS Variables",
+ desc: "Injected into :root. No JS at render time.",
+ code: "--color-accent",
+ },
+ ].map((item) => (
+
+
+
+ {item.step}
+
+ {item.layer}
+
+
+ {item.desc}
+
+
+ {item.code}
+
+
+ ))}
+
+
+
+ {/* Color Modes */}
+
+
+
+
+ Active:{" "}
+
+ {theme.colorMode}
+
+
+
+ Try it — the entire site will adapt.
+
+
+
+ Switch to {theme.colorMode === "light" ? "Dark" : "Light"}
+
+
+ {`const { toggleColorMode } = useTheme();
+toggleColorMode(); // light ↔ dark`}
+
+
+ {/* Live Brand Switcher */}
+
+
+ {/* Brand pills */}
+
+ {BRAND_PRESETS.map((p) => (
+ applyColor(p.color)}
+ className="flex items-center gap-2 px-3 py-1.5 rounded-md border text-xs font-medium transition-colors"
+ style={{
+ borderColor:
+ activePreset === p.color ? p.color : theme.tokens.border,
+ backgroundColor:
+ activePreset === p.color ? `${p.color}15` : "transparent",
+ color:
+ activePreset === p.color
+ ? p.color
+ : theme.tokens.foreground,
+ }}
+ >
+
+ {p.name}
+
+ ))}
+
+ Reset
+
+
+
+ {/* Theme Variations */}
+
+
+ {theme.colorMode === "dark" ? "Dark" : "Light"} Theme Variations
+
+
+ {variations.map((preset) => (
+
applyVariation(preset)}
+ />
+ ))}
+
+
+
+ {/* Live preview */}
+
+ Live Preview
+
+
+
+
+ Primary
+
+
+ Secondary
+
+
+ Outline
+
+
+ Ghost
+
+
+
+
+
+
+ Active
+ Online
+ Pending
+
+
+
+
+
+
+
+
+
+
+ {`const { overrideTheme } = useTheme();
+
+overrideTheme({
+ palette: {
+ primary: { 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8' }
+ },
+ tokens: {
+ accent: '#2563eb',
+ accentHover: '#1d4ed8',
+ }
+});`}
+
+
+ {/* Hooks API */}
+
+
+
+
useTheme()
+
+ Full control — read the theme, switch modes, apply overrides, or
+ reset.
+
+
+
+
useTokens()
+
+ Just the semantic tokens. Use when a component only needs color
+ values.
+
+
+
+ {`// Full access
+const { theme, setTheme, toggleColorMode, overrideTheme, resetTheme } = useTheme();
+
+// Just colors
+const tokens = useTokens();
+// tokens.background, tokens.accent, tokens.border, ...`}
+
+
+ {/* CSS Variables */}
+
+ {`.card {
+ background: var(--color-surface);
+ border: 1px solid var(--color-border);
+ color: var(--color-foreground);
+ border-radius: var(--radius-md);
+}
+
+/* Available variables:
+ --color-background, --color-surface, --color-foreground
+ --color-border, --color-accent, --color-ring
+ --color-primary-50 through --color-primary-900
+ --radius-sm, --radius-md, --radius-lg
+ --font-sans, --font-mono
+*/`}
+
+
+ );
+};
diff --git a/src/docs/pages/Typography.mdx b/src/docs/pages/Typography.mdx
new file mode 100644
index 0000000..1259184
--- /dev/null
+++ b/src/docs/pages/Typography.mdx
@@ -0,0 +1,66 @@
+import { Typography } from '@/components/Typography';
+import { ComponentPreview } from '../components/ComponentPreview';
+import { PropsTable } from '../components/PropsTable';
+import { PageHeader } from '../components/PageHeader';
+
+
+
+## Display
+
+Large-scale text for hero sections and landing pages.
+
+Display 1
+Display 2 `}>
+
+ Display 1
+ Display 2
+
+
+
+## Headings
+
+Semantic headings H1 through H3.
+
+Heading 1
+Heading 2
+Heading 3 `}>
+
+ Heading 1
+ Heading 2
+ Heading 3
+
+
+
+## Body & Caption
+
+Paragraph and meta text styles.
+
+Body 1
+Body 2
+Caption `}>
+
+ Body 1 — standard paragraph text.
+ Body 2 — smaller secondary text.
+ Caption — metadata and labels.
+
+
+
+## Weight Overrides
+
+Override the default weight on any variant.
+
+Bold H4
+Thin H4 `}>
+
+ Bold H4
+ Thin H4
+
+
+
+## Props
+
diff --git a/src/mdx.d.ts b/src/mdx.d.ts
new file mode 100644
index 0000000..2e0f342
--- /dev/null
+++ b/src/mdx.d.ts
@@ -0,0 +1,5 @@
+declare module "*.mdx" {
+ import type { ComponentType } from "react";
+ const Component: ComponentType;
+ export default Component;
+}
diff --git a/src/theme/ThemeProvider.tsx b/src/theme/ThemeProvider.tsx
index ae8640e..8da5e99 100644
--- a/src/theme/ThemeProvider.tsx
+++ b/src/theme/ThemeProvider.tsx
@@ -70,7 +70,18 @@ export function ThemeProvider({
return defaultThemeName;
});
- const [override, setOverride] = useState(null);
+ const [override, setOverride] = useState(() => {
+ if (storageKey) {
+ const stored = localStorage.getItem(`${storageKey}-override`);
+ try {
+ return stored ? JSON.parse(stored) : null;
+ } catch (e) {
+ console.error("Failed to parse theme override from storage", e);
+ return null;
+ }
+ }
+ return null;
+ });
const baseTheme = THEME_REGISTRY[activeThemeName] ?? defaultTheme;
const theme = useMemo(
@@ -78,7 +89,6 @@ export function ThemeProvider({
[baseTheme, override],
);
- // Apply CSS variables whenever the theme changes
useEffect(() => {
applyTheme(theme);
}, [theme]);
@@ -91,7 +101,10 @@ export function ThemeProvider({
}
setActiveThemeName(name);
setOverride(null);
- if (storageKey) localStorage.setItem(storageKey, name);
+ if (storageKey) {
+ localStorage.setItem(storageKey, name);
+ localStorage.removeItem(`${storageKey}-override`);
+ }
},
[storageKey],
);
@@ -103,15 +116,38 @@ export function ThemeProvider({
);
if (nextTheme) {
setActiveThemeName(nextTheme.name);
+ // Keep palette (primary color) but clear surface overrides
+ setOverride((prev) => {
+ if (!prev?.palette) return null;
+ const kept = { palette: prev.palette };
+ if (storageKey) {
+ localStorage.setItem(`${storageKey}-override`, JSON.stringify(kept));
+ }
+ return kept;
+ });
if (storageKey) localStorage.setItem(storageKey, nextTheme.name);
}
}, [theme.colorMode, storageKey]);
- const overrideTheme = useCallback((o: ThemeOverride) => {
- setOverride((prev) => (prev ? { ...prev, ...o } : o));
- }, []);
+ const overrideTheme = useCallback(
+ (o: ThemeOverride) => {
+ setOverride((prev) => {
+ const next = prev ? { ...prev, ...o } : o;
+ if (storageKey) {
+ localStorage.setItem(`${storageKey}-override`, JSON.stringify(next));
+ }
+ return next;
+ });
+ },
+ [storageKey],
+ );
- const resetTheme = useCallback(() => setOverride(null), []);
+ const resetTheme = useCallback(() => {
+ setOverride(null);
+ if (storageKey) {
+ localStorage.removeItem(`${storageKey}-override`);
+ }
+ }, [storageKey]);
const value = useMemo(
() => ({ theme, setTheme, toggleColorMode, overrideTheme, resetTheme }),
diff --git a/tailwind.config.js b/tailwind.config.js
index 5bd1efd..6877844 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
export default {
- content: ['./src/**/*.{js,ts,jsx,tsx}'],
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
darkMode: 'class',
theme: {
extend: {
@@ -12,7 +12,18 @@ export default {
border: 'var(--color-border)',
accent: 'var(--color-accent)',
ring: 'var(--color-ring)',
- // ...etc
+ primary: {
+ 50: 'var(--color-primary-50)',
+ 100: 'var(--color-primary-100)',
+ 200: 'var(--color-primary-200)',
+ 300: 'var(--color-primary-300)',
+ 400: 'var(--color-primary-400)',
+ 500: 'var(--color-primary-500)',
+ 600: 'var(--color-primary-600)',
+ 700: 'var(--color-primary-700)',
+ 800: 'var(--color-primary-800)',
+ 900: 'var(--color-primary-900)',
+ },
},
borderRadius: {
sm: 'var(--radius-sm)',
@@ -25,5 +36,7 @@ export default {
},
},
},
- plugins: [],
+ plugins: [
+ require('@tailwindcss/typography'),
+ ],
};
\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
index e1dda2c..428baae 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -3,6 +3,7 @@ import react from "@vitejs/plugin-react";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
import dts from "vite-plugin-dts";
+import mdx from "@mdx-js/rollup";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -10,10 +11,11 @@ const __dirname = dirname(__filename);
// https://vite.dev/config/
export default defineConfig({
plugins: [
+ mdx(),
react(),
dts({
include: ["src"],
- exclude: ["**/*.test.tsx", "**/*.test.ts"],
+ exclude: ["**/*.test.tsx", "**/*.test.ts", "**/*.mdx"],
rollupTypes: false,
}),
],