Upgrade Storybook setup, with latest release, less boilerplate

pull/8041/head
Thibaud Colas 2022-02-18 16:07:09 +00:00 zatwierdzone przez LB (Ben Johnston)
rodzic b3e5f751e4
commit d149a27392
21 zmienionych plików z 837 dodań i 8592 usunięć

Wyświetl plik

@ -92,7 +92,15 @@ module.exports = {
overrides: [
{
// Rules we dont want to enforce for test and tooling code.
files: ['*.test.ts', '*.test.tsx', '*.test.js', 'webpack.config.js'],
files: [
'*.test.ts',
'*.test.tsx',
'*.test.js',
'webpack.config.js',
'*.stories.js',
'*.stories.tsx',
'storybook/**/*',
],
rules: {
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-var-requires': 'off',

Wyświetl plik

@ -3,3 +3,6 @@ graft wagtail
prune wagtail/wagtailadmin/static_src
global-exclude __pycache__
global-exclude *.py[co]
global-exclude *.stories.js
global-exclude *.stories.tsx
recursive-exclude wagtail/ *.md

Wyświetl plik

@ -2,18 +2,44 @@ import React, { useRef, useEffect } from 'react';
import { renderPattern, simulateLoading } from 'storybook-django';
const getTemplateName = (template?: string, filename?: string) =>
const getTemplateName = (template?: string, filename?: string): string =>
template ||
filename?.replace(/.+\/templates\//, '').replace(/\.stories\..+$/, '.html');
filename?.replace(/.+\/templates\//, '').replace(/\.stories\..+$/, '.html') ||
'template-not-found';
type ContextMapping = { [key: string]: any };
type TagsMapping = { [key: string]: any };
interface TemplatePatternProps {
element?: 'div' | 'span';
// Path to the template file.
template?: string;
// Path to a Storybook `stories` file, which should be placed next to and named the same as the HTML template.
filename?: string;
context?: { [key: string]: any };
tags?: { [key: string]: any };
context?: ContextMapping;
tags?: TagsMapping;
}
const PATTERN_LIBRARY_RENDER_URL = '/pattern-library/api/v1/render-pattern';
/**
* Retrieves a template patterns HTML (or error response) from the server.
*/
export const getTemplatePattern = (
templateName: string,
context: ContextMapping,
tags: TagsMapping,
callback: (html: string) => void,
) =>
renderPattern(PATTERN_LIBRARY_RENDER_URL, templateName, context, tags)
.catch(callback)
.then((res) => res.text())
.then(callback);
/**
* Renders one of our Django templates as if it was a React component.
* All props are marked as optional, but either template or filename should be provided.
*/
const TemplatePattern = ({
element = 'div',
template,
@ -22,14 +48,13 @@ const TemplatePattern = ({
tags = {},
}: TemplatePatternProps) => {
const ref = useRef(null);
const templateName = getTemplateName(template, filename);
useEffect(() => {
renderPattern(window.PATTERN_LIBRARY_API, templateName, context, tags)
.catch((err) => simulateLoading(ref.current, err))
.then((res) => res.text())
.then((html) => simulateLoading(ref.current, html));
}, []);
const templateName = getTemplateName(template, filename);
getTemplatePattern(templateName, context, tags, (html) =>
simulateLoading(ref.current, html),
);
});
return React.createElement(element, { ref });
};

Wyświetl plik

@ -0,0 +1,7 @@
import { Meta, Story, Canvas } from '@storybook/addon-docs';
<Meta title="Welcome" />
# Welcome to Wagtails design system
Wagtails admin interface is built with reusable UI components, structured as a design system for ease of maintenance and third-party reuse.

Wyświetl plik

@ -1,12 +1,21 @@
module.exports = {
stories: ['../../client/**/*.stories.*', '../../wagtail/**/*.stories.*'],
addons: ['@storybook/addon-docs'],
stories: [
'../../client/**/*.stories.mdx',
'../../client/**/*.stories.@(js|tsx)',
{
directory: '../../wagtail/admin/templates/wagtailadmin/shared/',
titlePrefix: 'Shared',
files: '*.stories.*',
},
'../../wagtail/**/*.stories.*',
],
addons: ['@storybook/addon-docs', '@storybook/addon-controls'],
framework: '@storybook/react',
core: {
builder: 'webpack5',
},
webpackFinal: (config) => {
/* eslint-disable no-param-reassign */
config.resolve.fallback.crypto = false;
const rules = [
{
@ -33,6 +42,7 @@ module.exports = {
config.module.rules = config.module.rules.concat(rules);
// Allow using path magic variables to reduce boilerplate in stories.
config.node = {
__filename: true,
__dirname: true,

Wyświetl plik

@ -5,6 +5,6 @@ const origin = process.env.TEST_ORIGIN ?? 'http://localhost:8000';
module.exports = middleware.createDjangoAPIMiddleware({
origin,
// Must match the urls.py pattern for the pattern library.
apiPath: '/pattern-library/',
// Must match the patterns in urls.py.
apiPath: ['/pattern-library/', '/static/wagtailadmin/'],
});

Wyświetl plik

@ -1,7 +0,0 @@
<div data-sprite></div>
<script>
// Load icon sprite into the DOM, just like in the Wagtail admin.
window.fetch('/pattern-library/api/v1/sprite')
.then((res) => res.text())
.then((html) => document.querySelector('[data-sprite]').innerHTML = html);
</script>

Wyświetl plik

@ -4,4 +4,45 @@ import '../../wagtail/admin/static_src/wagtailadmin/scss/core.scss';
import '../../wagtail/admin/static_src/wagtailadmin/scss/sidebar.scss';
import './preview.scss';
window.PATTERN_LIBRARY_API = '/pattern-library/api/v1/render-pattern';
export const parameters = {
controls: {
hideNoControlsWarning: true,
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
const cachedIcons = sessionStorage.getItem('WAGTAIL_ICONS');
window.WAGTAIL_ICONS = cachedIcons ? JSON.parse(cachedIcons) : [];
/**
* Loads Wagtails icon sprite into the DOM, similarly to the admin.
*/
const loadIconSprite = () => {
const PATTERN_LIBRARY_SPRITE_URL = '/pattern-library/api/v1/sprite';
window
.fetch(PATTERN_LIBRARY_SPRITE_URL)
.then((res) => res.text())
.then((html) => {
const sprite = document.createElement('div');
sprite.innerHTML = html;
const symbols = Array.from(sprite.querySelectorAll('symbol'));
const icons = symbols.map((elt) => elt.id.replace('icon-', ''));
window.WAGTAIL_ICONS = icons;
sessionStorage.setItem('WAGTAIL_ICONS', JSON.stringify(icons));
if (document.body) {
document.body.appendChild(sprite);
} else {
window.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(sprite);
});
}
});
};
loadIconSprite();

Wyświetl plik

@ -5,14 +5,13 @@ body {
font-size: 1rem;
}
h1,
h2 {
text-transform: initial;
}
// Fix compatibility issue with Wagtails tag implementation.
// Fix compatibility issue with Wagtail styles.
.sbdocs .tag,
.sbdocs .tag::before {
.sbdocs .tag::before,
h1,
h2,
code,
textarea {
all: revert;
}

Wyświetl plik

@ -3,5 +3,5 @@ declare module '*.md';
declare module '*.html';
interface Window {
PATTERN_LIBRARY_API: string;
WAGTAIL_ICONS: string[];
}

Wyświetl plik

@ -282,6 +282,23 @@ This must be done after every change to the source files. To watch the source fi
$ npm start
Using the pattern library
~~~~~~~~~~~~~~~~~~~~~~~~~
Wagtails UI component library is built with `Storybook <https://storybook.js.org/>`_ and `django-pattern-library <https://github.com/torchbox/django-pattern-library>`_. To run it locally,
.. code-block:: console
$ export DJANGO_SETTINGS_MODULE=wagtail.tests.settings_ui
$ # Assumes the current environment contains a valid installation of Wagtail for local development.
$ ./wagtail/tests/manage.py migrate
$ ./wagtail/tests/manage.py createcachetable
$ ./wagtail/tests/manage.py runserver 0:8000
$ # In a separate terminal:
$ npm run storybook
The last command will start Storybook at ``http://localhost:6006/``. It will proxy specific requests to Django at ``http://localhost:8000`` by default. Use the ``TEST_ORIGIN`` environment variable to use a different port for Django: ``TEST_ORIGIN=http://localhost:9000 npm run storybook``.
Compiling the documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~

9055
package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -50,10 +50,11 @@
},
"devDependencies": {
"@babel/core": "^7.16.7",
"@storybook/addon-docs": "^6.3.12",
"@storybook/builder-webpack5": "^6.3.12",
"@storybook/manager-webpack5": "^6.3.12",
"@storybook/react": "^6.3.12",
"@storybook/addon-controls": "^6.4.19",
"@storybook/addon-docs": "^6.4.19",
"@storybook/builder-webpack5": "^6.4.19",
"@storybook/manager-webpack5": "^6.4.19",
"@storybook/react": "^6.4.19",
"@types/draft-js": "^0.10.45",
"@types/jest": "^26.0.24",
"@types/react": "^16.14.21",
@ -111,9 +112,9 @@
"scripts": {
"start": "webpack --config ./client/webpack.config.js --mode development --progress --watch",
"build": "webpack --config ./client/webpack.config.js --mode production",
"fix:js": "eslint --ext .js,.ts,.tsx --fix ./",
"fix:js": "eslint --ext .js,.ts,.tsx --fix .",
"format": "prettier --write '**/?(.)*.{css,scss,js,ts,tsx,json,yaml,yml}'",
"lint:js": "eslint --ext .js,.ts,.tsx --report-unused-disable-directives ./client",
"lint:js": "eslint --ext .js,.ts,.tsx --report-unused-disable-directives .",
"lint:css": "stylelint **/*.scss",
"lint:format": "prettier --check '**/?(.)*.{css,scss,js,ts,tsx,json,yaml,yml}'",
"lint": "npm run lint:js && npm run lint:css && npm run lint:format",

Wyświetl plik

@ -10,6 +10,17 @@
"allowJs": true,
"downlevelIteration": true
},
"files": [
"client/src/index.ts",
"client/src/custom.d.ts",
"client/storybook/stories.d.ts"
],
"include": ["src", "wagtail"],
"exclude": ["mode_modules", "static"]
"exclude": [
"node_modules",
"static",
// Files with template syntax.
"wagtail/admin/templates/wagtailadmin/edit_handlers/inline_panel.js",
"wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js"
]
}

Wyświetl plik

@ -0,0 +1,14 @@
import React from 'react';
import TemplatePattern from '../../../../../client/storybook/TemplatePattern';
import template from './animated_logo.html';
export default {
parameters: {
docs: {
source: { code: template },
},
},
};
export const AnimatedLogo = () => <TemplatePattern filename={__filename} />;

Wyświetl plik

@ -0,0 +1 @@
The breadcrumb component is reused across a lot of Wagtails headers.

Wyświetl plik

@ -0,0 +1,40 @@
import React from 'react';
import TemplatePattern from '../../../../../client/storybook/TemplatePattern';
import template from './breadcrumb.html';
import docs from './breadcrumb.md';
export default {
parameters: {
docs: {
source: { code: template },
extractComponentDescription: () => docs,
},
},
};
const Template = (args) => (
<TemplatePattern filename={__filename} context={args} />
);
export const Base = Template.bind({});
Base.args = {
pages: [
{
is_root: true,
id: 2,
get_admin_display_title: 'First item',
},
{
id: 3,
get_admin_display_title: 'Second item',
},
{
id: 4,
get_admin_display_title: 'Third item',
},
],
trailing_arrow: true,
show_header_buttons: false,
};

Wyświetl plik

@ -3,20 +3,16 @@
Variables accepted by this template:
title
subtitle
search_url - if present, display a search box. This is a URL route name (taking no parameters) to be used as the action for that search box
query_parameters - a query string (without the '?') to be placed after the search URL
icon - name of an icon to place against the title
tabbed - if true, add the classname 'tab-merged'
merged - if true, add the classname 'merged'
action_url - if present, display an 'action' button. This is the URL to be used as the link URL for the button
action_text - text for the 'action' button
action_icon - icon for the 'action' button, default is 'icon-plus'
- `title`
- `subtitle`
- `search_url` - if present, display a search box. This is a URL route name (taking no parameters) to be used as the action for that search box
- `query_parameters` - a query string (without the '?') to be placed after the search URL
- `icon` - name of an icon to place against the title
- `tabbed` - if true, add the classname 'tab-merged'
- `merged` - if true, add the classname 'merged'
- `action_url` - if present, display an 'action' button. This is the URL to be used as the link URL for the button
- `action_text` - text for the 'action' button
- `action_icon` - icon for the 'action' button, default is 'icon-plus'
{% endcomment %}
<header class="{% if merged %}merged{% endif %} {% if tabbed %}tab-merged{% endif %} {% if search_form %}hasform{% endif %}">

Wyświetl plik

@ -0,0 +1,45 @@
import React from 'react';
import TemplatePattern from '../../../../../client/storybook/TemplatePattern';
import template from './header.html';
export default {
parameters: {
docs: {
source: { code: template },
// Trial generating documentation from comment within the template. To be replaced by a better pattern.
extractComponentDescription: () =>
template
.match(/{% comment %}\n((.|\n)+){% endcomment %}/m)[1]
.replace(/ {4}/g, ''),
},
},
argTypes: {
icon: {
options: window.WAGTAIL_ICONS,
control: { type: 'select' },
description: 'name of an icon to place against the title',
},
},
};
const Template = (args) => (
<TemplatePattern filename={__filename} context={args} />
);
export const Base = Template.bind({});
Base.args = {
title: 'Calendar',
icon: 'date',
};
export const Action = Template.bind({});
Action.args = {
title: 'Users',
subtitle: 'Editors',
icon: 'user',
action_url: '/test/',
action_text: 'Add',
};

Wyświetl plik

@ -1,52 +1,49 @@
import React, { useState, useEffect } from 'react';
import { renderPattern } from 'storybook-django';
const fetchIconTemplate = () =>
renderPattern(
window.PATTERN_LIBRARY_API,
'wagtailadmin/shared/icon.html',
{
name: '__icon__',
},
{},
).then((res) => res.text());
import { getTemplatePattern } from '../../../../../client/storybook/TemplatePattern';
/**
* Displays all icons within our sprite.
*/
const Icons = () => {
const Icons = ({ color }: { color: string }) => {
const [template, setTemplate] = useState<string>('');
const [icons, setIcons] = useState<string[]>([]);
useEffect(() => {
const sprite = document.querySelector('[data-sprite]');
if (sprite) {
const symbols = Array.from(sprite.querySelectorAll('symbol'));
setIcons(symbols.map((s) => s.id.replace('icon-', '')));
}
fetchIconTemplate().then((html) => setTemplate(html));
getTemplatePattern(
'wagtailadmin/shared/icon.html',
{ name: '__icon__' },
{},
(html) => setTemplate(html),
);
}, []);
return (
<ul>
{icons.map((icon) => (
<li key={icon}>
<>
{window.WAGTAIL_ICONS.map((icon) => (
<div key={icon}>
<span
dangerouslySetInnerHTML={{
__html: template.replace(/__icon__/g, icon),
__html: template
.replace(/__icon__/g, icon)
.replace(/<svg/, `<svg style="fill: ${color};"`),
}}
/>
<code>{`{% icon name="${icon}" %}`}</code>
</li>
</div>
))}
</ul>
</>
);
};
export default {
title: 'Shared / Icons',
component: Icons,
argTypes: {
color: {
description: 'Only intended for demo purposes',
},
},
};
export const All = () => <Icons />;
export const icons = (args) => <Icons {...args} />;
icons.args = {
color: 'currentColor',
};

Wyświetl plik

@ -13,7 +13,7 @@ from wagtail import __semver__
class assets_mixin:
def compile_assets(self):
try:
subprocess.check_call(["npm", "run", "dist"])
subprocess.check_call(["npm", "run", "build"])
except (OSError, subprocess.CalledProcessError) as e:
print("Error compiling assets: " + str(e)) # noqa
raise SystemExit(1)