-
Notifications
You must be signed in to change notification settings - Fork 26
Story #2205: Storybook Implementation #2277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
6237a70
2d026ed
0469a5f
15532b8
bc76d84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /** @type { import('@storybook/react-webpack5').StorybookConfig } */ | ||
| const config = { | ||
| stories: ["../storybook/**/*.stories.@(js|jsx)"], | ||
| addons: ["@storybook/addon-essentials"], | ||
| framework: { | ||
| name: "@storybook/react-webpack5", | ||
| options: {}, | ||
| }, | ||
| webpackFinal: (config) => { | ||
| config.module.rules = config.module.rules.concat([ | ||
| { | ||
| test: /\.html$/, | ||
| type: "asset/source", | ||
| }, | ||
| ]); | ||
| return config; | ||
| }, | ||
| }; | ||
|
|
||
| module.exports = config; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| const { createProxyMiddleware } = require("http-proxy-middleware"); | ||
| const { | ||
| createDjangoAPIMiddleware, | ||
| } = require("storybook-django/src/middleware"); | ||
|
|
||
| const djangoOrigin = process.env.DJANGO_ORIGIN || "http://localhost:8000"; | ||
|
|
||
| // storybook-django middleware for the pattern-library API (handles POST body restreaming) | ||
| const djangoAPI = createDjangoAPIMiddleware({ | ||
| origin: djangoOrigin, | ||
| apiPath: ["/pattern-library/"], | ||
| }); | ||
|
|
||
| module.exports = function expressMiddleware(router) { | ||
| // Pattern library API proxy (POST requests with JSON body) | ||
| djangoAPI(router); | ||
|
|
||
| // Static files proxy (CSS, JS, images, fonts) | ||
| router.use( | ||
| "/static/", | ||
| createProxyMiddleware({ | ||
| target: djangoOrigin, | ||
| changeOrigin: true, | ||
| }) | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| <!-- Fonts and icons (external CDNs — loaded directly) --> | ||
| <link rel="preconnect" href="https://fonts.googleapis.com"> | ||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | ||
|
Comment on lines
+2
to
+3
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these 2 can be removed since we are not using Google Font |
||
| <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"> | ||
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"> | ||
|
Comment on lines
+4
to
+5
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to me like Font Awesome is only being used in |
||
|
|
||
| <!-- Project CSS (proxied to Django via middleware) --> | ||
| <link href="/static/css/styles.css" rel="stylesheet"> | ||
| <link href="/static/css/components.css" rel="stylesheet"> | ||
| <link href="/static/css/boostlook.css" rel="stylesheet"> | ||
| <link href="/static/css/v3/components.css" rel="stylesheet"> | ||
|
|
||
| <!-- Alpine.js --> | ||
| <script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-clipboard@2.x.x/dist/alpine-clipboard.js" defer></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js" defer></script> | ||
| <script src="//unpkg.com/alpinejs" defer></script> | ||
|
|
||
| <!-- Project JS — scripts that only need to load once (utilities, global listeners) --> | ||
| <script src="/static/js/theme_handling.js"></script> | ||
| <script src="/static/js/utils.js"></script> | ||
| <script src="/static/js/highlight.js"></script> | ||
| <script src="/static/js/highlight-block.js"></script> | ||
| <script src="/static/js/dialog.js" defer></script> | ||
| <script src="/static/js/install-card.js" defer></script> | ||
|
|
||
| <!-- Theme meta tags (same as base.html) --> | ||
| <meta class="meta-theme" name="theme-color" content="#FAFAFA" data-dark="#18181B" data-light="#FAFAFA"> | ||
| <meta class="meta-theme" name="color-scheme" content="light" data-dark="dark" data-light="light"> | ||
|
|
||
| <!-- Hide carousel scrollbar (the track scrolls via JS, scrollbar is unwanted) --> | ||
| <style> | ||
| .post-cards--horizontal .post-cards__list { | ||
| -ms-overflow-style: none; | ||
| scrollbar-width: none; | ||
| } | ||
| .post-cards--horizontal .post-cards__list::-webkit-scrollbar { | ||
| display: none; | ||
| } | ||
| </style> | ||
|
|
||
| <!-- | ||
| Prevent demo links (href="#", href="#_", href="#someId") from navigating | ||
| the Storybook iframe. In a real page these are harmless hash changes, | ||
| but Storybook intercepts them as route changes. | ||
| --> | ||
| <script> | ||
| document.addEventListener('click', function(e) { | ||
| var link = e.target.closest('a[href]'); | ||
| if (!link) return; | ||
| var href = link.getAttribute('href'); | ||
| if (href && (href === '#' || href.charAt(0) === '#')) { | ||
| e.preventDefault(); | ||
| if (href !== '#') { | ||
| window.location.hash = href.substring(1); | ||
| } | ||
| } | ||
| }, true); | ||
| </script> | ||
|
|
||
| <!-- | ||
| Scripts that self-initialize via readyState check (carousel.js, code-block.js) | ||
| must be re-loaded AFTER storybook-django injects Pattern HTML. | ||
| storybook-django fires a synthetic DOMContentLoaded after each Pattern render, | ||
| so we dynamically load these scripts on each DOMContentLoaded event. | ||
| --> | ||
| <script> | ||
| (function() { | ||
| var scriptsToReinit = [ | ||
| '/static/js/carousel.js', | ||
| '/static/js/code-block.js' | ||
| ]; | ||
| document.addEventListener('DOMContentLoaded', function() { | ||
| scriptsToReinit.forEach(function(src) { | ||
| var old = document.querySelector('script[data-storybook-reinit="' + src + '"]'); | ||
| if (old) old.parentNode.removeChild(old); | ||
| var s = document.createElement('script'); | ||
| s.src = src + '?_=' + Date.now(); | ||
| s.setAttribute('data-storybook-reinit', src); | ||
| document.body.appendChild(s); | ||
| }); | ||
| }); | ||
| })(); | ||
| </script> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /** @type { import('@storybook/react').Preview } */ | ||
| const preview = { | ||
| parameters: { | ||
| controls: { | ||
| matchers: { | ||
| color: /(background|color)$/i, | ||
| date: /Date$/i, | ||
| }, | ||
| }, | ||
| backgrounds: { | ||
| default: 'light', | ||
| values: [ | ||
| { | ||
| name: 'light', | ||
| value: '#FAFAFA', | ||
| }, | ||
| { | ||
| name: 'dark', | ||
| value: '#18181B', | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| decorators: [ | ||
| (Story, context) => { | ||
| // Add v3 class to both html and body for v3 component styles | ||
| document.documentElement.classList.add("v3"); | ||
| document.body.classList.add("v3"); | ||
|
|
||
| // Sync Storybook's background selector with project's theme system | ||
| const bgValue = context.globals.backgrounds?.value; | ||
| if (bgValue) { | ||
| const bgConfig = context.parameters.backgrounds?.values?.find((b) => b.value === bgValue); | ||
| const theme = bgConfig?.name === 'dark' ? 'dark' : 'light'; | ||
| // Use the project's saveColorMode function if available | ||
| if (typeof window.saveColorMode === 'function') { | ||
| window.saveColorMode(theme); | ||
| } else { | ||
| // Fallback: directly set localStorage and dispatch event | ||
| localStorage.setItem('colorMode', theme); | ||
| window.dispatchEvent(new StorageEvent('storage', { | ||
| key: 'colorMode', | ||
| oldValue: localStorage.getItem('colorMode'), | ||
| newValue: theme, | ||
| })); | ||
| } | ||
| } | ||
|
|
||
| return Story(); | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| export default preview; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| FROM node:22-slim | ||
|
|
||
| WORKDIR /code | ||
|
|
||
| COPY package.json yarn.lock ./ | ||
| RUN yarn install | ||
|
|
||
| COPY .storybook/ .storybook/ | ||
| COPY storybook/ storybook/ | ||
| COPY templates/ templates/ | ||
| COPY static/ static/ | ||
|
|
||
| EXPOSE 6006 | ||
|
|
||
| CMD ["npx", "storybook", "dev", "-p", "6006", "--host", "0.0.0.0"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the explicit imports on this file, I'm concerned that this leaves room for unexpected drifts between
base.htmland this file when we load new files.One idea to consider – perhaps we can create a small
.htmlthat takes care of loading these scripts/stylesheet, then bothbase.htmland this file can share it. It definitely calls for a refactor onbase.htmlthough, I'm not sure if we really want to touch it 😅. What do you think of this?