├── .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 |
19 | Vite + React
20 |
21 |
setCount((count) => count + 1)}>
22 | count is {count}
23 |
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 |
17 | {label}
18 |
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 |
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 |
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 |
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 |
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 |
93 |
Autodocs
94 |
Auto-generate living,
95 | interactive reference documentation from your components and stories.
96 |
Learn more
100 |
101 |
102 |
103 |
Publish to Chromatic
104 |
Publish your Storybook to review and collaborate with your entire team.
105 |
Learn more
109 |
110 |
111 |
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 |
122 |
Testing
123 |
Use stories to test a component in all its variations, no matter how
124 | complex.
125 |
Learn more
129 |
130 |
131 |
132 |
Accessibility
133 |
Automatically test your components for a11y issues as you develop.
134 |
Learn more
138 |
139 |
140 |
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 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | Join our contributors building the future of UI development.
169 |
170 |
Star on GitHub
174 |
175 |
176 |
177 |
185 |
186 |
187 |
188 |
189 | Watch tutorials, feature previews and interviews.
190 |
191 |
Watch on YouTube
195 |
196 |
197 |
198 |
199 |
Follow guided walkthroughs on for key workflows.
200 |
201 |
Discover tutorials
205 |
206 |
207 |
208 |
365 |
--------------------------------------------------------------------------------