diff --git a/.gitignore b/.gitignore
index bf9ddadb..a7b0e809 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,5 @@ package-lock.json
/coverage/
/lib/
-tsconfig.tsbuildinfo
\ No newline at end of file
+tsconfig.tsbuildinfo
+*storybook.log
diff --git a/.storybook/global.css b/.storybook/global.css
new file mode 100644
index 00000000..cb50d95a
--- /dev/null
+++ b/.storybook/global.css
@@ -0,0 +1,83 @@
+/* From https://www.joshwcomeau.com/css/custom-css-reset/ */
+
+/* 1. Use a more-intuitive box-sizing model */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+/* 2. Remove default margin */
+* {
+ margin: 0;
+}
+
+/* 3. Enable keyword animations */
+@media (prefers-reduced-motion: no-preference) {
+ html {
+ interpolate-size: allow-keywords;
+ }
+}
+
+body {
+ /* 4. Add accessible line-height */
+ line-height: 1.5;
+ /* 5. Improve text rendering */
+ -webkit-font-smoothing: antialiased;
+}
+
+/* 6. Improve media defaults */
+img,
+picture,
+video,
+canvas,
+svg {
+ display: block;
+ max-width: 100%;
+}
+
+/* 7. Inherit fonts for form controls */
+input,
+button,
+textarea,
+select {
+ font: inherit;
+}
+
+/* 8. Avoid text overflows */
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ overflow-wrap: break-word;
+}
+
+/* 9. Improve line wrapping */
+p {
+ text-wrap: pretty;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ text-wrap: balance;
+}
+
+/*
+ 10. Create a root stacking context
+*/
+#root,
+#__next {
+ isolation: isolate;
+}
+
+/* Storybook */
+
+* {
+ font-family: "Mulish", "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
diff --git a/.storybook/main.ts b/.storybook/main.ts
new file mode 100644
index 00000000..14de2082
--- /dev/null
+++ b/.storybook/main.ts
@@ -0,0 +1,25 @@
+import type { StorybookConfig } from '@storybook/react-vite'
+
+const config: StorybookConfig = {
+ stories: [
+ '../src/**/*.mdx',
+ '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
+ ],
+ addons: [
+ {
+ name: '@storybook/addon-essentials',
+ options: {
+ docs: false,
+ },
+ },
+ '@storybook/addon-interactions',
+ ],
+ framework: {
+ name: '@storybook/react-vite',
+ options: {},
+ },
+ core: {
+ disableTelemetry: true,
+ },
+}
+export default config
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
new file mode 100644
index 00000000..a31a91af
--- /dev/null
+++ b/.storybook/preview-head.html
@@ -0,0 +1,5 @@
+
+
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
new file mode 100644
index 00000000..7fbd1f33
--- /dev/null
+++ b/.storybook/preview.ts
@@ -0,0 +1,15 @@
+import type { Preview } from '@storybook/react'
+import './global.css'
+
+const preview: Preview = {
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i,
+ },
+ },
+ },
+}
+
+export default preview
diff --git a/eslint.config.js b/eslint.config.js
index b85e85c6..0f089139 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -2,6 +2,7 @@ import javascript from '@eslint/js'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
+import storybook from 'eslint-plugin-storybook'
import globals from 'globals'
import typescript from 'typescript-eslint'
@@ -105,5 +106,11 @@ export default typescript.config(
...globals.node,
},
},
+ },
+ {
+ extends: [
+ ...storybook.configs['flat/recommended'],
+ ],
+ files: ['**/*.stories.tsx'],
}
)
diff --git a/package.json b/package.json
index 1d8d9c33..8943f2cd 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"prepublishOnly": "npm run build",
"serve": "node bin/cli.js",
"preserve": "npm run build",
+ "storybook": "storybook dev -p 6006",
"test": "vitest run",
"typecheck": "tsc --noEmit",
"url": "run-p -l watch:ts watch:vite watch:url",
@@ -59,6 +60,12 @@
},
"devDependencies": {
"@eslint/js": "9.23.0",
+ "@storybook/addon-essentials": "8.6.12",
+ "@storybook/addon-interactions": "8.6.12",
+ "@storybook/blocks": "8.6.12",
+ "@storybook/react": "8.6.12",
+ "@storybook/react-vite": "8.6.12",
+ "@storybook/test": "8.6.12",
"@testing-library/react": "16.3.0",
"@types/node": "22.14.0",
"@types/react": "19.0.12",
@@ -69,13 +76,20 @@
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.19",
+ "eslint-plugin-storybook": "0.12.0",
"globals": "16.0.0",
"jsdom": "26.0.0",
"nodemon": "3.1.9",
"npm-run-all": "4.1.5",
+ "storybook": "8.6.12",
"typescript": "5.8.3",
"typescript-eslint": "8.29.0",
"vite": "6.2.5",
"vitest": "3.1.1"
+ },
+ "eslintConfig": {
+ "extends": [
+ "plugin:storybook/recommended"
+ ]
}
}
diff --git a/src/components/Dropdown/Dropdown.stories.tsx b/src/components/Dropdown/Dropdown.stories.tsx
new file mode 100644
index 00000000..cdaef080
--- /dev/null
+++ b/src/components/Dropdown/Dropdown.stories.tsx
@@ -0,0 +1,38 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import Dropdown from './Dropdown.js'
+
+const meta: Meta = {
+ component: Dropdown,
+}
+export default meta
+type Story = StoryObj;
+export const Default: Story = {
+ args: {
+ children: <>
+
+
+ >,
+ },
+}
+
+export const LeftAlign: Story = {
+ args: {
+ label: 'Menu',
+ align: 'left',
+ children: <>
+
+
+ >,
+ },
+}
+
+export const RightAlign: Story = {
+ args: {
+ label: 'Very long label for the menu',
+ align: 'right',
+ children: <>
+
+
+ >,
+ },
+}
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
index 1126b701..c423778c 100644
--- a/tsconfig.eslint.json
+++ b/tsconfig.eslint.json
@@ -1,4 +1,4 @@
{
"extends": "./tsconfig.json",
- "include": ["src", "test", "bin", "**/*.js", "**/*.ts", "**/*.tsx"]
+ "include": ["src", "test", "bin", "**/*.js", "**/*.ts", "**/*.tsx", "**/.storybook/*.ts"]
}