├── .nvmrc ├── src ├── stories │ ├── assets │ │ ├── assets.png │ │ ├── docs.png │ │ ├── share.png │ │ ├── context.png │ │ ├── styling.png │ │ ├── testing.png │ │ ├── theming.png │ │ ├── accessibility.png │ │ ├── addon-library.png │ │ ├── figma-plugin.png │ │ ├── avif-test-image.avif │ │ ├── youtube.svg │ │ ├── tutorials.svg │ │ ├── accessibility.svg │ │ ├── discord.svg │ │ └── github.svg │ ├── Header.stories.js │ ├── header.css │ ├── button.css │ ├── Page.stories.js │ ├── Button.jsx │ ├── page.css │ ├── Button.stories.js │ ├── Header.jsx │ ├── Page.jsx │ └── Configure.mdx ├── components │ └── title │ │ ├── title.yml │ │ ├── title.twig │ │ └── title.stories.jsx ├── main.jsx ├── App.css ├── App.jsx ├── index.css └── assets │ └── react.svg ├── storybook.libraries.yml ├── .gitignore ├── index.html ├── storybook.info.yml ├── vite.config.js ├── .storybook ├── preview.js └── main.js ├── .eslintrc.cjs ├── README.md ├── public └── vite.svg └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.5.1 2 | -------------------------------------------------------------------------------- /src/stories/assets/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/assets.png -------------------------------------------------------------------------------- /src/stories/assets/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/docs.png -------------------------------------------------------------------------------- /src/stories/assets/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/share.png -------------------------------------------------------------------------------- /storybook.libraries.yml: -------------------------------------------------------------------------------- 1 | global: 2 | version: VERSION 3 | css: 4 | base: 5 | dist/css/global.css: {} 6 | -------------------------------------------------------------------------------- /src/stories/assets/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/context.png -------------------------------------------------------------------------------- /src/stories/assets/styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/styling.png -------------------------------------------------------------------------------- /src/stories/assets/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/testing.png -------------------------------------------------------------------------------- /src/stories/assets/theming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/theming.png -------------------------------------------------------------------------------- /src/stories/assets/accessibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/accessibility.png -------------------------------------------------------------------------------- /src/stories/assets/addon-library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/addon-library.png -------------------------------------------------------------------------------- /src/stories/assets/figma-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/figma-plugin.png -------------------------------------------------------------------------------- /src/stories/assets/avif-test-image.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariohernandez/storybook/HEAD/src/stories/assets/avif-test-image.avif -------------------------------------------------------------------------------- /src/components/title/title.yml: -------------------------------------------------------------------------------- 1 | --- 2 | level: 1 3 | modifier: 'my-title' 4 | text: 'Welcome to your new Drupal theme with Storybook!' 5 | url: '' 6 | -------------------------------------------------------------------------------- /src/components/title/title.twig: -------------------------------------------------------------------------------- 1 | 2 | {% if url %} 3 | {{ text }} 4 | {% else %} 5 | {{ text }} 6 | {% endif %} 7 | 8 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /storybook.info.yml: -------------------------------------------------------------------------------- 1 | name: Storybook 2 | type: theme 3 | description: 'A bare bones Drupal 10+ theme using Storybook 8 and ViteJS.' 4 | package: Core 5 | core_version_requirement: ^9 || ^10 6 | base theme: false 7 | 8 | libraries: 9 | - storybook/global 10 | 11 | regions: 12 | header: Header 13 | highlighted: Highlighted 14 | breadcrumb: Breadcrumb 15 | content_above: Content Above 16 | content: Content 17 | content_below: Content Below 18 | footer: Footer 19 | theme: Theme 20 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { defineConfig } from "vite" 4 | import yml from '@modyfi/vite-plugin-yaml'; 5 | import twig from 'vite-plugin-twig-drupal'; 6 | import { join } from "node:path" 7 | export default defineConfig({ 8 | plugins: [ 9 | twig({ 10 | namespaces: { 11 | components: join(__dirname, "./src/components"), 12 | // Other namespaces maybe be added. 13 | }, 14 | }), 15 | // Allows Storybook to read data from YAML files. 16 | yml(), 17 | ], 18 | }) 19 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react').Preview } */ 2 | import Twig from 'twig'; 3 | import drupalFilters from 'twig-drupal-filters'; 4 | 5 | function setupFilters(twig) { 6 | twig.cache(); 7 | drupalFilters(twig); 8 | return twig; 9 | } 10 | 11 | setupFilters(Twig); 12 | 13 | const preview = { 14 | parameters: { 15 | controls: { 16 | matchers: { 17 | color: /(background|color)$/i, 18 | date: /Date$/i, 19 | }, 20 | }, 21 | }, 22 | }; 23 | 24 | export default preview; 25 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react-vite').StorybookConfig } */ 2 | const config = { 3 | stories: [ 4 | "../src/components/**/*.mdx", 5 | "../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)", 6 | ], 7 | addons: [ 8 | "@storybook/addon-onboarding", 9 | "@storybook/addon-links", 10 | "@storybook/addon-essentials", 11 | "@chromatic-com/storybook", 12 | "@storybook/addon-interactions", 13 | ], 14 | framework: { 15 | name: "@storybook/react-vite", 16 | options: {}, 17 | }, 18 | docs: { 19 | autodocs: "tag", 20 | }, 21 | }; 22 | export default config; 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], 5 | ignorePatterns: ['dist', '.eslintrc.cjs'], 6 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 7 | settings: { react: { version: '18.2' } }, 8 | plugins: ['react-refresh'], 9 | rules: { 10 | 'react/jsx-no-target-blank': 'off', 11 | 'react-refresh/only-export-components': [ 12 | 'warn', 13 | { allowConstantExport: true }, 14 | ], 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /src/stories/assets/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/stories/Header.stories.js: -------------------------------------------------------------------------------- 1 | import { Header } from './Header'; 2 | import { fn } from '@storybook/test'; 3 | 4 | export default { 5 | title: 'Example/Header', 6 | component: Header, 7 | // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 8 | tags: ['autodocs'], 9 | parameters: { 10 | // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout 11 | layout: 'fullscreen', 12 | }, 13 | args: { 14 | onLogin: fn(), 15 | onLogout: fn(), 16 | onCreateAccount: fn(), 17 | }, 18 | }; 19 | 20 | export const LoggedIn = { 21 | args: { 22 | user: { 23 | name: 'Jane Doe', 24 | }, 25 | }, 26 | }; 27 | 28 | export const LoggedOut = {}; 29 | -------------------------------------------------------------------------------- /src/stories/header.css: -------------------------------------------------------------------------------- 1 | .storybook-header { 2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 4 | padding: 15px 20px; 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | } 9 | 10 | .storybook-header svg { 11 | display: inline-block; 12 | vertical-align: top; 13 | } 14 | 15 | .storybook-header h1 { 16 | font-weight: 700; 17 | font-size: 20px; 18 | line-height: 1; 19 | margin: 6px 0 6px 10px; 20 | display: inline-block; 21 | vertical-align: top; 22 | } 23 | 24 | .storybook-header button + button { 25 | margin-left: 10px; 26 | } 27 | 28 | .storybook-header .welcome { 29 | color: #333; 30 | font-size: 14px; 31 | margin-right: 10px; 32 | } 33 | -------------------------------------------------------------------------------- /src/stories/button.css: -------------------------------------------------------------------------------- 1 | .storybook-button { 2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | font-weight: 700; 4 | border: 0; 5 | border-radius: 3em; 6 | cursor: pointer; 7 | display: inline-block; 8 | line-height: 1; 9 | } 10 | .storybook-button--primary { 11 | color: white; 12 | background-color: #1ea7fd; 13 | } 14 | .storybook-button--secondary { 15 | color: #333; 16 | background-color: transparent; 17 | box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; 18 | } 19 | .storybook-button--small { 20 | font-size: 12px; 21 | padding: 10px 16px; 22 | } 23 | .storybook-button--medium { 24 | font-size: 14px; 25 | padding: 11px 20px; 26 | } 27 | .storybook-button--large { 28 | font-size: 16px; 29 | padding: 12px 24px; 30 | } 31 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /src/stories/Page.stories.js: -------------------------------------------------------------------------------- 1 | import { within, userEvent, expect } from '@storybook/test'; 2 | 3 | import { Page } from './Page'; 4 | 5 | export default { 6 | title: 'Example/Page', 7 | component: Page, 8 | parameters: { 9 | // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout 10 | layout: 'fullscreen', 11 | }, 12 | }; 13 | 14 | export const LoggedOut = {}; 15 | 16 | // More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing 17 | export const LoggedIn = { 18 | play: async ({ canvasElement }) => { 19 | const canvas = within(canvasElement); 20 | const loginButton = canvas.getByRole('button', { name: /Log in/i }); 21 | await expect(loginButton).toBeInTheDocument(); 22 | await userEvent.click(loginButton); 23 | await expect(loginButton).not.toBeInTheDocument(); 24 | 25 | const logoutButton = canvas.getByRole('button', { name: /Log out/i }); 26 | await expect(logoutButton).toBeInTheDocument(); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import reactLogo from './assets/react.svg' 3 | import viteLogo from '/vite.svg' 4 | import './App.css' 5 | 6 | function App() { 7 | const [count, setCount] = useState(0) 8 | 9 | return ( 10 | <> 11 |
12 | 13 | Vite logo 14 | 15 | 16 | React logo 17 | 18 |
19 |

Vite + React

20 |
21 | 24 |

25 | Edit src/App.jsx and save to test HMR 26 |

27 |
28 |

29 | Click on the Vite and React logos to learn more 30 |

31 | 32 | ) 33 | } 34 | 35 | export default App 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drupal 10+ theme with Storybook support 2 | 3 | ## About this repo 4 | 5 | * The `main` branch only includes the code for [Building a modern Drupal theme with Storybook](https://mariohernandez.io/blog/building-a-modern-drupal-theme-with-storybook/). 6 | * The `card` branch includes the code for [Integrating Drupal with Storybook components](https://mariohernandez.io/blog/integrating-drupal-with-storybook-components/). 7 | 8 | This is a minimal setup of a Drupal 10 theme with Storybook support. It uses [ViteJS](https://vitejs.dev/) as its build tool. 9 | The project still lacks many of the automation found in most modern front-end projects. The main objective of this project is to show how Storybook is able to render components that were written in Twig. 10 | 11 | ## Running this project 12 | 13 | **Note**: You need NodeJS 18+ or 20+. 14 | 15 | 1. Clone this repo 16 | 1. Run `cd storybook` (or the directory name you used when cloning the repo) 17 | 1. Run `nvm install` - You need to have nvm installed. This project uses NodeJS v20.5 18 | 1. Run `npm install` - This will install all the npm packages in `package.json` 19 | 1. Run `npm run storybook` - Will build and launch Storybook in the browser. 20 | 21 | by: [Mario Hernandez](https://mariohernandez.io) 22 | -------------------------------------------------------------------------------- /src/stories/assets/tutorials.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/stories/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './button.css'; 4 | 5 | /** 6 | * Primary UI component for user interaction 7 | */ 8 | export const Button = ({ primary, backgroundColor, size, label, ...props }) => { 9 | const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; 10 | return ( 11 | 19 | ); 20 | }; 21 | 22 | Button.propTypes = { 23 | /** 24 | * Is this the principal call to action on the page? 25 | */ 26 | primary: PropTypes.bool, 27 | /** 28 | * What background color to use 29 | */ 30 | backgroundColor: PropTypes.string, 31 | /** 32 | * How large should the button be? 33 | */ 34 | size: PropTypes.oneOf(['small', 'medium', 'large']), 35 | /** 36 | * Button contents 37 | */ 38 | label: PropTypes.string.isRequired, 39 | /** 40 | * Optional click handler 41 | */ 42 | onClick: PropTypes.func, 43 | }; 44 | 45 | Button.defaultProps = { 46 | backgroundColor: null, 47 | primary: false, 48 | size: 'medium', 49 | onClick: undefined, 50 | }; 51 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/stories/page.css: -------------------------------------------------------------------------------- 1 | .storybook-page { 2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | font-size: 14px; 4 | line-height: 24px; 5 | padding: 48px 20px; 6 | margin: 0 auto; 7 | max-width: 600px; 8 | color: #333; 9 | } 10 | 11 | .storybook-page h2 { 12 | font-weight: 700; 13 | font-size: 32px; 14 | line-height: 1; 15 | margin: 0 0 4px; 16 | display: inline-block; 17 | vertical-align: top; 18 | } 19 | 20 | .storybook-page p { 21 | margin: 1em 0; 22 | } 23 | 24 | .storybook-page a { 25 | text-decoration: none; 26 | color: #1ea7fd; 27 | } 28 | 29 | .storybook-page ul { 30 | padding-left: 30px; 31 | margin: 1em 0; 32 | } 33 | 34 | .storybook-page li { 35 | margin-bottom: 8px; 36 | } 37 | 38 | .storybook-page .tip { 39 | display: inline-block; 40 | border-radius: 1em; 41 | font-size: 11px; 42 | line-height: 12px; 43 | font-weight: 700; 44 | background: #e7fdd8; 45 | color: #66bf3c; 46 | padding: 4px 12px; 47 | margin-right: 10px; 48 | vertical-align: top; 49 | } 50 | 51 | .storybook-page .tip-wrapper { 52 | font-size: 13px; 53 | line-height: 20px; 54 | margin-top: 40px; 55 | margin-bottom: 40px; 56 | } 57 | 58 | .storybook-page .tip-wrapper svg { 59 | display: inline-block; 60 | height: 12px; 61 | width: 12px; 62 | margin-right: 4px; 63 | vertical-align: top; 64 | margin-top: 3px; 65 | } 66 | 67 | .storybook-page .tip-wrapper svg path { 68 | fill: #1ea7fd; 69 | } 70 | -------------------------------------------------------------------------------- /src/stories/Button.stories.js: -------------------------------------------------------------------------------- 1 | import { fn } from '@storybook/test'; 2 | import { Button } from './Button'; 3 | 4 | // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 5 | export default { 6 | title: 'Example/Button', 7 | component: Button, 8 | parameters: { 9 | // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout 10 | layout: 'centered', 11 | }, 12 | // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 13 | tags: ['autodocs'], 14 | // More on argTypes: https://storybook.js.org/docs/api/argtypes 15 | argTypes: { 16 | backgroundColor: { control: 'color' }, 17 | }, 18 | // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args 19 | args: { onClick: fn() }, 20 | }; 21 | 22 | // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args 23 | export const Primary = { 24 | args: { 25 | primary: true, 26 | label: 'Button', 27 | }, 28 | }; 29 | 30 | export const Secondary = { 31 | args: { 32 | label: 'Button', 33 | }, 34 | }; 35 | 36 | export const Large = { 37 | args: { 38 | size: 'large', 39 | label: 'Button', 40 | }, 41 | }; 42 | 43 | export const Small = { 44 | args: { 45 | size: 'small', 46 | label: 'Button', 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/components/title/title.stories.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * First we import the `html-react-parser` extension to be able to 3 | * parse HTML into react. 4 | */ 5 | import parse from 'html-react-parser'; 6 | 7 | /** 8 | * Next we import the component's markup and logic (twig), data schema (yml), 9 | * as well as any styles or JS the component may use. 10 | */ 11 | import title from './title.twig'; 12 | import data from './title.yml'; 13 | 14 | /** 15 | * Next we define a default configuration for the component to use. 16 | * These settings will be inherited by all stories of the component, 17 | * shall the component have multiple variations. 18 | * `component` is an arbitrary name assigned to the default configuration. 19 | * `title` determines the location and name of the story in Storybook's sidebar. 20 | * `render` uses the parser extension to render the component's html to react. 21 | * `args` uses the variables defined in title.yml as react arguments. 22 | */ 23 | const component = { 24 | title: 'Components/Title', 25 | }; 26 | 27 | /** 28 | * Export the Title and render it in Storybook as a Story. 29 | * The `name` key allows you to assign a name to each story of the component. 30 | * For example: `Title`, `Title dark`, `Title light`, etc. 31 | */ 32 | export const TitleElement = { 33 | name: 'Title', 34 | render: (args) => parse(title(args)), 35 | args: { ...data }, 36 | }; 37 | 38 | /** 39 | * Finally export the default object, `component`. Storybook/React requires this step. 40 | */ 41 | export default component; 42 | -------------------------------------------------------------------------------- /src/stories/assets/accessibility.svg: -------------------------------------------------------------------------------- 1 | 2 | Accessibility 3 | 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "storybook": "storybook dev -p 6006", 12 | "build-storybook": "storybook build" 13 | }, 14 | "dependencies": { 15 | "@storybook/addon-onboarding": "^8.0.6", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@chromatic-com/storybook": "^1.3.2", 21 | "@modyfi/vite-plugin-yaml": "^1.1.0", 22 | "@storybook/addon-essentials": "^8.0.6", 23 | "@storybook/addon-interactions": "^8.0.6", 24 | "@storybook/addon-links": "^8.0.6", 25 | "@storybook/blocks": "^8.0.6", 26 | "@storybook/react": "^8.0.6", 27 | "@storybook/react-vite": "^8.0.6", 28 | "@storybook/test": "^8.0.6", 29 | "@types/react": "^18.2.66", 30 | "@types/react-dom": "^18.2.22", 31 | "@vitejs/plugin-react": "^4.2.1", 32 | "drupal-attribute": "^1.0.2", 33 | "eslint": "^8.57.0", 34 | "eslint-plugin-react": "^7.34.1", 35 | "eslint-plugin-react-hooks": "^4.6.0", 36 | "eslint-plugin-react-refresh": "^0.4.6", 37 | "eslint-plugin-storybook": "^0.8.0", 38 | "html-react-parser": "^5.1.10", 39 | "prop-types": "^15.8.1", 40 | "storybook": "^8.0.6", 41 | "twig-drupal-filters": "^3.2.0", 42 | "vite": "^5.2.0", 43 | "vite-plugin-twig-drupal": "^1.2.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/stories/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Button } from './Button'; 5 | import './header.css'; 6 | 7 | export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => ( 8 |
9 |
10 |
11 | 12 | 13 | 17 | 21 | 25 | 26 | 27 |

Acme

28 |
29 |
30 | {user ? ( 31 | <> 32 | 33 | Welcome, {user.name}! 34 | 35 |
44 |
45 |
46 | ); 47 | 48 | Header.propTypes = { 49 | user: PropTypes.shape({ 50 | name: PropTypes.string.isRequired, 51 | }), 52 | onLogin: PropTypes.func.isRequired, 53 | onLogout: PropTypes.func.isRequired, 54 | onCreateAccount: PropTypes.func.isRequired, 55 | }; 56 | 57 | Header.defaultProps = { 58 | user: null, 59 | }; 60 | -------------------------------------------------------------------------------- /src/stories/assets/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/stories/assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/stories/Page.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Header } from './Header'; 4 | import './page.css'; 5 | 6 | export const Page = () => { 7 | const [user, setUser] = React.useState(); 8 | 9 | return ( 10 |
11 |
setUser({ name: 'Jane Doe' })} 14 | onLogout={() => setUser(undefined)} 15 | onCreateAccount={() => setUser({ name: 'Jane Doe' })} 16 | /> 17 | 18 |
19 |

Pages in Storybook

20 |

21 | We recommend building UIs with a{' '} 22 | 23 | component-driven 24 | {' '} 25 | process starting with atomic components and ending with pages. 26 |

27 |

28 | Render pages with mock data. This makes it easy to build and review page states without 29 | needing to navigate to them in your app. Here are some handy patterns for managing page 30 | data in Storybook: 31 |

32 |
    33 |
  • 34 | Use a higher-level connected component. Storybook helps you compose such data from the 35 | "args" of child component stories 36 |
  • 37 |
  • 38 | Assemble data in the page component from your services. You can mock these services out 39 | using Storybook. 40 |
  • 41 |
42 |

43 | Get a guided tutorial on component-driven development at{' '} 44 | 45 | Storybook tutorials 46 | 47 | . Read more in the{' '} 48 | 49 | docs 50 | 51 | . 52 |

53 |
54 | Tip Adjust the width of the canvas with the{' '} 55 | 56 | 57 | 62 | 63 | 64 | Viewports addon in the toolbar 65 |
66 |
67 |
68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stories/Configure.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/blocks"; 2 | 3 | import Github from "./assets/github.svg"; 4 | import Discord from "./assets/discord.svg"; 5 | import Youtube from "./assets/youtube.svg"; 6 | import Tutorials from "./assets/tutorials.svg"; 7 | import Styling from "./assets/styling.png"; 8 | import Context from "./assets/context.png"; 9 | import Assets from "./assets/assets.png"; 10 | import Docs from "./assets/docs.png"; 11 | import Share from "./assets/share.png"; 12 | import FigmaPlugin from "./assets/figma-plugin.png"; 13 | import Testing from "./assets/testing.png"; 14 | import Accessibility from "./assets/accessibility.png"; 15 | import Theming from "./assets/theming.png"; 16 | import AddonLibrary from "./assets/addon-library.png"; 17 | 18 | export const RightArrow = () => 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 | # Configure your project 39 | 40 | Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community. 41 |
42 |
43 |
44 | A wall of logos representing different styling technologies 48 |

Add styling and CSS

49 |

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

50 | Learn more 54 |
55 |
56 | An abstraction representing the composition of data for a component 60 |

Provide context and mocking

61 |

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

62 | Learn more 66 |
67 |
68 | A representation of typography and image assets 69 |
70 |

Load assets and resources

71 |

To link static files (like fonts) to your projects and stories, use the 72 | `staticDirs` configuration option to specify folders to load when 73 | starting Storybook.

74 | Learn more 78 |
79 |
80 |
81 |
82 |
83 |
84 | # Do more with Storybook 85 | 86 | Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs. 87 |
88 | 89 |
90 |
91 |
92 | A screenshot showing the autodocs tag being set, pointing a docs page being generated 93 |

Autodocs

94 |

Auto-generate living, 95 | interactive reference documentation from your components and stories.

96 | Learn more 100 |
101 |
102 | A browser window showing a Storybook being published to a chromatic.com URL 103 |

Publish to Chromatic

104 |

Publish your Storybook to review and collaborate with your entire team.

105 | Learn more 109 |
110 |
111 | Windows showing the Storybook plugin in Figma 112 |

Figma Plugin

113 |

Embed your stories into Figma to cross-reference the design and live 114 | implementation in one place.

115 | Learn more 119 |
120 |
121 | Screenshot of tests passing and failing 122 |

Testing

123 |

Use stories to test a component in all its variations, no matter how 124 | complex.

125 | Learn more 129 |
130 |
131 | Screenshot of accessibility tests passing and failing 132 |

Accessibility

133 |

Automatically test your components for a11y issues as you develop.

134 | Learn more 138 |
139 |
140 | Screenshot of Storybook in light and dark mode 141 |

Theming

142 |

Theme Storybook's UI to personalize it to your project.

143 | Learn more 147 |
148 |
149 |
150 |
151 |
152 |
153 |

Addons

154 |

Integrate your tools with Storybook to connect workflows.

155 | Discover all addons 159 |
160 |
161 | Integrate your tools with Storybook to connect workflows. 162 |
163 |
164 | 165 |
166 |
167 | Github logo 168 | Join our contributors building the future of UI development. 169 | 170 | Star on GitHub 174 |
175 |
176 | Discord logo 177 |
178 | Get support and chat with frontend developers. 179 | 180 | Join Discord server 184 |
185 |
186 |
187 | Youtube logo 188 |
189 | Watch tutorials, feature previews and interviews. 190 | 191 | Watch on YouTube 195 |
196 |
197 |
198 | A book 199 |

Follow guided walkthroughs on for key workflows.

200 | 201 | Discover tutorials 205 |
206 |
207 | 208 | 365 | --------------------------------------------------------------------------------