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"] }