From c6c01580512a7ef97649d65cfc18526747e07ad7 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 13:01:43 +0100 Subject: [PATCH 01/10] feat: props table for web-components --- .../src/frameworks/web-components/config.js | 39 ++++++++++++ .../.storybook/config.js | 5 ++ .../custom-elements.json | 61 +++++++++++++++++++ .../stories/card.stories.js | 1 + 4 files changed, 106 insertions(+) create mode 100644 examples/web-components-kitchen-sink/custom-elements.json diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index ac38618a0329..63098ec520de 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -1,10 +1,49 @@ +/* global window */ /* eslint-disable import/no-extraneous-dependencies */ import { addParameters } from '@storybook/web-components'; import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +function mapData(data) { + return data.map(item => ({ + name: item.name, + type: { name: item.type }, + required: '', + description: item.description, + defaultValue: item.default, + })); +} + +function isEmpty(obj) { + return Object.entries(obj).length === 0 && obj.constructor === Object; +} + addParameters({ docs: { container: DocsContainer, page: DocsPage, + extractProps: tagName => { + // eslint-disable-next-line no-underscore-dangle + const customElements = window.__STORYBOOK_CUSTOM_ELEMENTS__; + if (customElements) { + const metaData = customElements.tags.find( + tag => tag.name.toUpperCase() === tagName.toUpperCase() + ); + const sections = {}; + if (metaData.properties) { + sections.props = mapData(metaData.properties); + } + if (metaData.events) { + sections.events = mapData(metaData.events); + } + if (metaData.slots) { + sections.slots = mapData(metaData.slots); + } + if (metaData.cssProperties) { + sections.css = mapData(metaData.cssProperties); + } + return isEmpty(sections) ? false : { sections }; + } + return false; + }, }, }); diff --git a/examples/web-components-kitchen-sink/.storybook/config.js b/examples/web-components-kitchen-sink/.storybook/config.js index 91d26a8d5d21..c76842076693 100644 --- a/examples/web-components-kitchen-sink/.storybook/config.js +++ b/examples/web-components-kitchen-sink/.storybook/config.js @@ -3,6 +3,11 @@ import { configure, addParameters, addDecorator } from '@storybook/web-components'; import { withA11y } from '@storybook/addon-a11y'; +import customElements from '../custom-elements.json'; + +// eslint-disable-next-line no-underscore-dangle +window.__STORYBOOK_CUSTOM_ELEMENTS__ = customElements; + addDecorator(withA11y); addParameters({ diff --git a/examples/web-components-kitchen-sink/custom-elements.json b/examples/web-components-kitchen-sink/custom-elements.json new file mode 100644 index 000000000000..4ae1a1000c27 --- /dev/null +++ b/examples/web-components-kitchen-sink/custom-elements.json @@ -0,0 +1,61 @@ +{ + "version": 2, + "tags": [ + { + "name": "demo-wc-card", + "properties": [ + { + "name": "header", + "type": "String", + "attribute": "header", + "description": "Shown at the top of the card", + "default": "Your Message" + }, + { + "name": "rows", + "type": "Array", + "attribute": "rows", + "description": "Tabular data shown on the back of the card", + "default": [] + }, + { + "name": "backSide", + "type": "Boolean", + "attribute": "back-side", + "reflect": true, + "description": "Indicates that the back of the card is shown", + "default": false + } + ], + "events": [ + { + "name": "side-changed", + "description": "Fires whenever it switches between front/back" + } + ], + "slots": [ + { + "name": "", + "description": "Content inside the card gets displayed on the front page" + } + ], + "cssProperties": [ + { + "name": "--demo-wc-card-header-font-size", + "description": "Header Font size", + "type": "Length" + }, + { + "name": "--demo-wc-card-front-color", + "description": "Font color for the front", + "type": "Color" + }, + { + "name": "--demo-wc-card-back-color", + "description": "Font color for the back", + "type": "Color" + } + ] + } + ] +} diff --git a/examples/web-components-kitchen-sink/stories/card.stories.js b/examples/web-components-kitchen-sink/stories/card.stories.js index 01166d61d064..4fbb46d78a78 100644 --- a/examples/web-components-kitchen-sink/stories/card.stories.js +++ b/examples/web-components-kitchen-sink/stories/card.stories.js @@ -4,6 +4,7 @@ import '../demo-wc-card.js'; export default { title: 'Demo Card', + component: 'demo-wc-card', }; export const Front = () => html` From 60c8e1533dd60698d3a0742cf43dcfe43a724ba8 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 15:27:29 +0100 Subject: [PATCH 02/10] chore: demo api table --- .../stories/addon-docs.stories.mdx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx b/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx index aa43d5f3c54a..bae689aa4a5a 100644 --- a/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx +++ b/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx @@ -1,4 +1,4 @@ -import { Story, Preview, Meta } from '@storybook/addon-docs/blocks'; +import { Story, Preview, Meta, Props } from '@storybook/addon-docs/blocks'; import { action } from '@storybook/addon-actions'; import { html } from 'lit-html'; import '../demo-wc-card.js'; @@ -17,6 +17,12 @@ A story can be a simple return of `html` `} +## API + +You can show the api table of a web component at any point in your documentation. + + + ## Function stories Or a function From b5b149ec517a43623070a8912d69cfc1c9d0c9df Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 16:26:23 +0100 Subject: [PATCH 03/10] chore: add set/getCustomElements to hide implementation details --- addons/docs/README.md | 32 +++++++++---------- .../src/frameworks/web-components/config.js | 4 +-- .../web-components/customElements.ts | 15 +++++++++ addons/docs/src/public_api.ts | 1 + .../.storybook/config.js | 4 +-- 5 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 addons/docs/src/frameworks/web-components/customElements.ts diff --git a/addons/docs/README.md b/addons/docs/README.md index 34f938b2b44c..be914e4d5588 100644 --- a/addons/docs/README.md +++ b/addons/docs/README.md @@ -75,16 +75,16 @@ For more information on `MDX`, see the [`MDX` reference](./docs/mdx.md). Storybook Docs supports all view layers that Storybook supports except for React Native (currently). There are some framework-specific features as well, such as props tables and inline story rendering. This chart captures the current state of support: -| | React | Vue | Angular | HTML | Svelte | Polymer | Marko | Mithril | Riot | Ember | Preact | -| ----------------- | :---: | :-: | :-----: | :--: | :----: | :-----: | :---: | :-----: | :--: | :---: | :----: | -| MDX stories | + | + | + | + | + | + | + | + | + | + | + | -| CSF stories | + | + | + | + | + | + | + | + | + | + | + | -| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | -| Source | + | + | + | + | + | + | + | + | + | + | + | -| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | -| Props table | + | + | # | | | | | | | | | -| Docgen | + | + | # | | | | | | | | | -| Inline stories | + | + | | | | | | | | | | +| | React | Vue | Angular | HTML | Web Components | Svelte | Polymer | Marko | Mithril | Riot | Ember | Preact | +| ----------------- | :---: | :-: | :-----: | :--: | :------------: | :----: | :-----: | :---: | :-----: | :--: | :---: | :----: | +| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + | +| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + | +| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + | +| Source | + | + | + | + | + | + | + | + | + | + | + | + | +| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + | +| Props table | + | + | # | | + | | | | | | | | +| Docgen | + | + | # | | | | | | | | | | +| Inline stories | + | + | | | | | | | | | | | **Note:** `#` = WIP support @@ -122,17 +122,17 @@ configure(require.context('../src', true, /\.stories\.(js|mdx)$/), module); For more information on the new `configure`, see ["Loading stories"](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/basics/writing-stories/index.md#loading-stories) in the Storybook documentation. -If using in conjunction with the [storyshots add-on](../storyshots/storyshots-core/README.md), you will need to +If using in conjunction with the [storyshots add-on](../storyshots/storyshots-core/README.md), you will need to configure Jest to transform MDX stories into something Storyshots can understand: - + Add the following to your Jest configuration: ```json { - "transform": { - "^.+\\.[tj]sx?$": "babel-jest", - "^.+\\.mdx$": "@storybook/addon-docs/jest-transform-mdx" - } + "transform": { + "^.+\\.[tj]sx?$": "babel-jest", + "^.+\\.mdx$": "@storybook/addon-docs/jest-transform-mdx" + } } ``` diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 63098ec520de..11426e5f90e8 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -2,6 +2,7 @@ /* eslint-disable import/no-extraneous-dependencies */ import { addParameters } from '@storybook/web-components'; import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +import { getCustomElements } from './customElements'; function mapData(data) { return data.map(item => ({ @@ -22,8 +23,7 @@ addParameters({ container: DocsContainer, page: DocsPage, extractProps: tagName => { - // eslint-disable-next-line no-underscore-dangle - const customElements = window.__STORYBOOK_CUSTOM_ELEMENTS__; + const customElements = getCustomElements(); if (customElements) { const metaData = customElements.tags.find( tag => tag.name.toUpperCase() === tagName.toUpperCase() diff --git a/addons/docs/src/frameworks/web-components/customElements.ts b/addons/docs/src/frameworks/web-components/customElements.ts new file mode 100644 index 000000000000..bff84600e76e --- /dev/null +++ b/addons/docs/src/frameworks/web-components/customElements.ts @@ -0,0 +1,15 @@ +/* eslint-disable no-underscore-dangle */ +/* global window */ + +/** + * @param customElements any for now as spec is not super stable yet + */ +export function setCustomElements(customElements: any) { + // @ts-ignore + window.__STORYBOOK_CUSTOM_ELEMENTS__ = customElements; +} + +export function getCustomElements() { + // @ts-ignore + return window.__STORYBOOK_CUSTOM_ELEMENTS__; +} diff --git a/addons/docs/src/public_api.ts b/addons/docs/src/public_api.ts index e69de29bb2d1..9c04c3e2d8a7 100644 --- a/addons/docs/src/public_api.ts +++ b/addons/docs/src/public_api.ts @@ -0,0 +1 @@ +export { setCustomElements, getCustomElements } from './frameworks/web-components/customElements'; diff --git a/examples/web-components-kitchen-sink/.storybook/config.js b/examples/web-components-kitchen-sink/.storybook/config.js index c76842076693..5bfb1626a402 100644 --- a/examples/web-components-kitchen-sink/.storybook/config.js +++ b/examples/web-components-kitchen-sink/.storybook/config.js @@ -2,11 +2,11 @@ import { configure, addParameters, addDecorator } from '@storybook/web-components'; import { withA11y } from '@storybook/addon-a11y'; +import { setCustomElements } from '@storybook/addon-docs'; import customElements from '../custom-elements.json'; -// eslint-disable-next-line no-underscore-dangle -window.__STORYBOOK_CUSTOM_ELEMENTS__ = customElements; +setCustomElements(customElements); addDecorator(withA11y); From 4632d65552a6ccc773a74727893373fda1b6ab53 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 17:02:51 +0100 Subject: [PATCH 04/10] chore: add installation documentation --- addons/docs/README.md | 24 ++++--- addons/docs/web-components/README.md | 63 +++++++++++++++++++ .../stories/addon-docs.stories.mdx | 2 +- 3 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 addons/docs/web-components/README.md diff --git a/addons/docs/README.md b/addons/docs/README.md index be914e4d5588..02d88dd20830 100644 --- a/addons/docs/README.md +++ b/addons/docs/README.md @@ -75,16 +75,16 @@ For more information on `MDX`, see the [`MDX` reference](./docs/mdx.md). Storybook Docs supports all view layers that Storybook supports except for React Native (currently). There are some framework-specific features as well, such as props tables and inline story rendering. This chart captures the current state of support: -| | React | Vue | Angular | HTML | Web Components | Svelte | Polymer | Marko | Mithril | Riot | Ember | Preact | -| ----------------- | :---: | :-: | :-----: | :--: | :------------: | :----: | :-----: | :---: | :-----: | :--: | :---: | :----: | -| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + | -| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + | -| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + | -| Source | + | + | + | + | + | + | + | + | + | + | + | + | -| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + | -| Props table | + | + | # | | + | | | | | | | | -| Docgen | + | + | # | | | | | | | | | | -| Inline stories | + | + | | | | | | | | | | | +| | React | Vue | Angular | HTML | [Web Components](./web-components) | Svelte | Polymer | Marko | Mithril | Riot | Ember | Preact | +| ----------------- | :---: | :-: | :-----: | :--: | :--------------------------------: | :----: | :-----: | :---: | :-----: | :--: | :---: | :----: | +| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + | +| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + | +| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + | +| Source | + | + | + | + | + | + | + | + | + | + | + | + | +| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + | +| Props table | + | + | # | | + | | | | | | | | +| Docgen | + | + | # | | | | | | | | | | +| Inline stories | + | + | | | | | | | | | | | **Note:** `#` = WIP support @@ -136,6 +136,10 @@ Add the following to your Jest configuration: } ``` +### Be sure to check framework specific installation needs + +- [Web Components](./web-components) + ## Preset options The `addon-docs` preset has a few configuration options that can be used to configure its babel/webpack loading behavior. Here's an example of how to use the preset with options: diff --git a/addons/docs/web-components/README.md b/addons/docs/web-components/README.md new file mode 100644 index 000000000000..40d5317118b8 --- /dev/null +++ b/addons/docs/web-components/README.md @@ -0,0 +1,63 @@ +# Storybook Docs for Web Components + +## Installation + +- Be sure to check the [installation section of the general addon-docs page](../README.md) before proceeding. +- Be sure to have a [custom-elements.json](./#custom-elementsjson) file. +- Add to your `.storybook/config.js` + + ```js + import { setCustomElements } from '@storybook/addon-docs'; + import customElements from '../custom-elements.json'; + + setCustomElements(customElements); + ``` + +- Add to your story files + + ```js + export default { + title: 'Demo Card', + component: 'your-component-name', // which is also found in the `custom-elements.json` + }; + ``` + +### custom-elements.json + +In order to get documentation for web-components you will need to have a [custom-elements.json](https://github.com/webcomponents/custom-elements-json) file. + +You can hand write it or better generate it. Depending on the web components sugar you are choosing your milage may vary. + +Known analyzers that output `custom-elements.json`: + +- [web-component-analyzer](https://github.com/runem/web-component-analyzer) + - Supports LitElement, Polymer, Vanilla, (Stencil) +- [stenciljs](https://stenciljs.com/) + - Supports Stencil (but does not have all metadata) + +It basically looks like this: + +```json +{ + "version": 2, + "tags": [ + { + "name": "demo-wc-card", + "properties": [ + { + "name": "header", + "type": "String", + "attribute": "header", + "description": "Shown at the top of the card", + "default": "Your Message" + } + ], + "events": [], + "slots": [], + "cssProperties": [] + } + ] +} +``` + +For a full example see the [web-components-kitchen-sink/custom-elements.json](../../../examples/web-components-kitchen-sink/custom-elements.json). diff --git a/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx b/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx index bae689aa4a5a..f05978435e89 100644 --- a/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx +++ b/examples/web-components-kitchen-sink/stories/addon-docs.stories.mdx @@ -21,7 +21,7 @@ A story can be a simple return of `html` You can show the api table of a web component at any point in your documentation. - + ## Function stories From 6bfd5489fd34a11d38a0c652cd7ddba75c5d0118 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 18:28:28 +0100 Subject: [PATCH 05/10] chore: extract component description --- addons/docs/src/frameworks/web-components/config.js | 12 ++++++++++++ .../web-components-kitchen-sink/custom-elements.json | 1 + 2 files changed, 13 insertions(+) diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 11426e5f90e8..3719010941db 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -45,5 +45,17 @@ addParameters({ } return false; }, + extractComponentDescription: tagName => { + const customElements = getCustomElements(); + if (customElements) { + const metaData = customElements.tags.find( + tag => tag.name.toUpperCase() === tagName.toUpperCase() + ); + if (metaData && metaData.description) { + return metaData.description; + } + } + return false; + }, }, }); diff --git a/examples/web-components-kitchen-sink/custom-elements.json b/examples/web-components-kitchen-sink/custom-elements.json index 4ae1a1000c27..3934eba7c9a4 100644 --- a/examples/web-components-kitchen-sink/custom-elements.json +++ b/examples/web-components-kitchen-sink/custom-elements.json @@ -3,6 +3,7 @@ "tags": [ { "name": "demo-wc-card", + "description": "This is a container looking like a card with a back and front side you can switch", "properties": [ { "name": "header", From 37f4e9633ccdb2989545cc6c62396838c769e17b Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 18:38:51 +0100 Subject: [PATCH 06/10] chore: do not fail if there is no component --- addons/docs/src/frameworks/web-components/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 3719010941db..99adb15b5cfe 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -24,7 +24,7 @@ addParameters({ page: DocsPage, extractProps: tagName => { const customElements = getCustomElements(); - if (customElements) { + if (typeof tagName === 'string' && customElements && customElements.tags) { const metaData = customElements.tags.find( tag => tag.name.toUpperCase() === tagName.toUpperCase() ); @@ -47,7 +47,7 @@ addParameters({ }, extractComponentDescription: tagName => { const customElements = getCustomElements(); - if (customElements) { + if (typeof tagName === 'string' && customElements && customElements.tags) { const metaData = customElements.tags.find( tag => tag.name.toUpperCase() === tagName.toUpperCase() ); From 66f5a30df313a1abdc42b2caf0f1d1aff983af8d Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 19:08:34 +0100 Subject: [PATCH 07/10] chore: throw if wrong data is provided --- .../src/frameworks/web-components/config.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 99adb15b5cfe..173808a0d9c7 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -18,13 +18,34 @@ function isEmpty(obj) { return Object.entries(obj).length === 0 && obj.constructor === Object; } +function isValidComponent(tagName, customElements) { + if (!tagName) { + return false; + } + if (tagName && typeof tagName === 'string') { + return true; + } + throw new Error('Provided component needs to be a string. e.g. component: "my-element"'); +} + +function isValidMetaData(customElements) { + if (!customElements) { + return false; + } + if (customElements && customElements.tags && Array.isArray(customElements.tags)) { + return true; + } + throw new Error(`You need to setup valid meta data in your config.js via setCustomElements(). + See the readme of addon-docs for web components for more details.`); +} + addParameters({ docs: { container: DocsContainer, page: DocsPage, extractProps: tagName => { const customElements = getCustomElements(); - if (typeof tagName === 'string' && customElements && customElements.tags) { + if (isValidComponent(tagName) && isValidMetaData(customElements)) { const metaData = customElements.tags.find( tag => tag.name.toUpperCase() === tagName.toUpperCase() ); @@ -47,7 +68,7 @@ addParameters({ }, extractComponentDescription: tagName => { const customElements = getCustomElements(); - if (typeof tagName === 'string' && customElements && customElements.tags) { + if (isValidComponent(tagName) && isValidMetaData(customElements)) { const metaData = customElements.tags.find( tag => tag.name.toUpperCase() === tagName.toUpperCase() ); From 88a7ad5036d3a46ca18539e302eba3fffe9adfe3 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 19:11:16 +0100 Subject: [PATCH 08/10] chore: update readme as web components support description --- addons/docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/docs/README.md b/addons/docs/README.md index 02d88dd20830..93a29ff4fe53 100644 --- a/addons/docs/README.md +++ b/addons/docs/README.md @@ -83,7 +83,7 @@ Storybook Docs supports all view layers that Storybook supports except for React | Source | + | + | + | + | + | + | + | + | + | + | + | + | | Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + | | Props table | + | + | # | | + | | | | | | | | -| Docgen | + | + | # | | | | | | | | | | +| Description | + | + | # | | + | | | | | | | | | Inline stories | + | + | | | | | | | | | | | **Note:** `#` = WIP support From be61332e2c56ab6621eac96a0b569135266cf763 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Sun, 27 Oct 2019 19:17:59 +0100 Subject: [PATCH 09/10] chore: small code improvements --- addons/docs/src/frameworks/web-components/config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 173808a0d9c7..19229a5510a9 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -18,11 +18,11 @@ function isEmpty(obj) { return Object.entries(obj).length === 0 && obj.constructor === Object; } -function isValidComponent(tagName, customElements) { +function isValidComponent(tagName) { if (!tagName) { return false; } - if (tagName && typeof tagName === 'string') { + if (typeof tagName === 'string') { return true; } throw new Error('Provided component needs to be a string. e.g. component: "my-element"'); @@ -32,7 +32,7 @@ function isValidMetaData(customElements) { if (!customElements) { return false; } - if (customElements && customElements.tags && Array.isArray(customElements.tags)) { + if (customElements.tags && Array.isArray(customElements.tags)) { return true; } throw new Error(`You need to setup valid meta data in your config.js via setCustomElements(). From 783103a38bb1bc0469d954d2b0716cb3e485fa40 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 28 Oct 2019 02:34:35 +0800 Subject: [PATCH 10/10] Merge branch 'next' into feat/docsWebComponentsProps --- addons/docs/html/config.js | 1 - .../frameworks/{angular => common}/config.js | 2 +- .../docs/src/frameworks/common/makePreset.ts | 9 +- addons/docs/src/frameworks/html/config.js | 10 --- .../frameworks/react/{config.js => config.ts} | 8 +- .../frameworks/vue/{config.js => config.tsx} | 8 +- .../src/frameworks/web-components/config.js | 5 +- addons/docs/src/typings.d.ts | 1 + addons/docs/vue/config.js | 1 - addons/docs/web-components/config.js | 1 - addons/knobs/src/typings.d.ts | 1 - lib/addons/src/typings.d.ts | 2 - lib/client-api/src/client_api.test.ts | 84 ++++++++++++------- lib/client-api/src/client_api.ts | 54 +++++++----- lib/client-api/src/index.ts | 4 +- lib/ui/src/settings/typings.d.ts | 1 - 16 files changed, 106 insertions(+), 86 deletions(-) delete mode 100644 addons/docs/html/config.js rename addons/docs/src/frameworks/{angular => common}/config.js (79%) delete mode 100644 addons/docs/src/frameworks/html/config.js rename addons/docs/src/frameworks/react/{config.js => config.ts} (68%) rename addons/docs/src/frameworks/vue/{config.js => config.tsx} (66%) delete mode 100644 addons/docs/vue/config.js delete mode 100644 addons/docs/web-components/config.js diff --git a/addons/docs/html/config.js b/addons/docs/html/config.js deleted file mode 100644 index c637f0d959cd..000000000000 --- a/addons/docs/html/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/html/config'); diff --git a/addons/docs/src/frameworks/angular/config.js b/addons/docs/src/frameworks/common/config.js similarity index 79% rename from addons/docs/src/frameworks/angular/config.js rename to addons/docs/src/frameworks/common/config.js index bf960fad550d..1d34169c1e74 100644 --- a/addons/docs/src/frameworks/angular/config.js +++ b/addons/docs/src/frameworks/common/config.js @@ -1,5 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/angular'; +import { addParameters } from '@storybook/client-api'; import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; addParameters({ diff --git a/addons/docs/src/frameworks/common/makePreset.ts b/addons/docs/src/frameworks/common/makePreset.ts index 89eac7be9990..420144b5ef42 100644 --- a/addons/docs/src/frameworks/common/makePreset.ts +++ b/addons/docs/src/frameworks/common/makePreset.ts @@ -2,10 +2,13 @@ import fs from 'fs'; import * as common from './preset'; const makePreset = (framework: string) => { - const frameworkConfig = `${__dirname}/../../../${framework}/config.js`; - const preConfig = fs.existsSync(frameworkConfig) ? [frameworkConfig] : []; + const docsConfig = [`${__dirname}/config.js`]; + const frameworkConfig = `${__dirname}/../../../dist/frameworks/${framework}/config.js`; + if (fs.existsSync(frameworkConfig)) { + docsConfig.push(frameworkConfig); + } function config(entry: any[] = []) { - return [...preConfig, ...entry]; + return [...docsConfig, ...entry]; } const configureJSX = framework !== 'react'; diff --git a/addons/docs/src/frameworks/html/config.js b/addons/docs/src/frameworks/html/config.js deleted file mode 100644 index f4c6dd942e33..000000000000 --- a/addons/docs/src/frameworks/html/config.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/html'; -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; - -addParameters({ - docs: { - container: DocsContainer, - page: DocsPage, - }, -}); diff --git a/addons/docs/src/frameworks/react/config.js b/addons/docs/src/frameworks/react/config.ts similarity index 68% rename from addons/docs/src/frameworks/react/config.js rename to addons/docs/src/frameworks/react/config.ts index 9f5839bb96a6..765e2813e984 100644 --- a/addons/docs/src/frameworks/react/config.js +++ b/addons/docs/src/frameworks/react/config.ts @@ -1,16 +1,14 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/react'; -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +import { addParameters } from '@storybook/client-api'; +import { StoryFn } from '@storybook/addons'; import { extractProps } from './extractProps'; import { extractComponentDescription } from '../../lib/docgenUtils'; addParameters({ docs: { - container: DocsContainer, - page: DocsPage, // react is Storybook's "native" framework, so it's stories are inherently prepared to be rendered inline // NOTE: that the result is a react element. Hooks support is provided by the outer code. - prepareForInline: storyFn => storyFn(), + prepareForInline: (storyFn: StoryFn) => storyFn(), extractProps, extractComponentDescription, }, diff --git a/addons/docs/src/frameworks/vue/config.js b/addons/docs/src/frameworks/vue/config.tsx similarity index 66% rename from addons/docs/src/frameworks/vue/config.js rename to addons/docs/src/frameworks/vue/config.tsx index a4b00f750cb2..0f395e0f47a6 100644 --- a/addons/docs/src/frameworks/vue/config.js +++ b/addons/docs/src/frameworks/vue/config.tsx @@ -1,16 +1,14 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; import toReact from '@egoist/vue-to-react'; -import { addParameters } from '@storybook/vue'; -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +import { StoryFn } from '@storybook/addons'; +import { addParameters } from '@storybook/client-api'; import { extractProps } from './extractProps'; import { extractComponentDescription } from '../../lib/docgenUtils'; addParameters({ docs: { - container: DocsContainer, - page: DocsPage, - prepareForInline: storyFn => { + prepareForInline: (storyFn: StoryFn) => { const Story = toReact(storyFn()); return ; }, diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 19229a5510a9..02c571ce0380 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -1,7 +1,6 @@ /* global window */ /* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/web-components'; -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +import { addParameters } from '@storybook/client-api'; import { getCustomElements } from './customElements'; function mapData(data) { @@ -41,8 +40,6 @@ function isValidMetaData(customElements) { addParameters({ docs: { - container: DocsContainer, - page: DocsPage, extractProps: tagName => { const customElements = getCustomElements(); if (isValidComponent(tagName) && isValidMetaData(customElements)) { diff --git a/addons/docs/src/typings.d.ts b/addons/docs/src/typings.d.ts index 7250049e8436..1843064a1887 100644 --- a/addons/docs/src/typings.d.ts +++ b/addons/docs/src/typings.d.ts @@ -2,3 +2,4 @@ declare module '@mdx-js/react'; declare module '@storybook/addon-docs/mdx-compiler-plugin'; declare module 'global'; declare module 'react-is'; +declare module '@egoist/vue-to-react'; diff --git a/addons/docs/vue/config.js b/addons/docs/vue/config.js deleted file mode 100644 index 3133ddabd103..000000000000 --- a/addons/docs/vue/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/vue/config'); diff --git a/addons/docs/web-components/config.js b/addons/docs/web-components/config.js deleted file mode 100644 index e5a449e3d5b9..000000000000 --- a/addons/docs/web-components/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/web-components/config'); diff --git a/addons/knobs/src/typings.d.ts b/addons/knobs/src/typings.d.ts index b0b788b35a7e..2f4eb9cf4fd9 100644 --- a/addons/knobs/src/typings.d.ts +++ b/addons/knobs/src/typings.d.ts @@ -1,2 +1 @@ declare module 'global'; -declare module '@storybook/client-api'; diff --git a/lib/addons/src/typings.d.ts b/lib/addons/src/typings.d.ts index 6c38e9bdc489..2f4eb9cf4fd9 100644 --- a/lib/addons/src/typings.d.ts +++ b/lib/addons/src/typings.d.ts @@ -1,3 +1 @@ declare module 'global'; -declare module '@storybook/client-logger'; -declare module '@storybook/addons'; \ No newline at end of file diff --git a/lib/client-api/src/client_api.test.ts b/lib/client-api/src/client_api.test.ts index a4cd692c1842..87755a5f80ed 100644 --- a/lib/client-api/src/client_api.test.ts +++ b/lib/client-api/src/client_api.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-underscore-dangle */ import { logger } from '@storybook/client-logger'; import addons, { mockChannel } from '@storybook/addons'; import ClientApi from './client_api'; @@ -26,6 +25,11 @@ jest.mock('@storybook/client-logger', () => ({ })); describe('preview.client_api', () => { + afterEach(() => { + const { clientApi } = getContext(undefined); + clientApi.clearDecorators(); + clientApi.clearParameters(); + }); describe('setAddon', () => { it('should register addons', () => { const { clientApi } = getContext(undefined); @@ -116,63 +120,85 @@ describe('preview.client_api', () => { describe('addParameters', () => { it('should add parameters', () => { - const { clientApi } = getContext(undefined); + const { clientApi, storyStore } = getContext(undefined); + const { storiesOf } = clientApi; - clientApi.addParameters({ a: '1' }); + clientApi.addParameters({ a: 1 }); + storiesOf('kind', module).add('name', ({ parameters }) => parameters); + const result = storyStore.fromId('kind--name').storyFn(); // @ts-ignore - expect(clientApi._globalParameters).toEqual({ a: '1', options: {}, docs: {} }); + const { docs, fileName, options, ...rest } = result; + + expect(rest).toEqual({ a: 1 }); }); it('should merge options', () => { - const { clientApi } = getContext(undefined); + const { clientApi, storyStore } = getContext(undefined); + const { storiesOf } = clientApi; clientApi.addParameters({ options: { a: '1' } }); clientApi.addParameters({ options: { b: '2' } }); + storiesOf('kind', module).add('name', ({ parameters }) => parameters); // @ts-ignore - expect(clientApi._globalParameters).toEqual({ options: { a: '1', b: '2' }, docs: {} }); + const { + options: { hierarchyRootSeparator, hierarchySeparator, ...rest }, + } = storyStore.fromId('kind--name').storyFn(); + + expect(rest).toEqual({ a: '1', b: '2' }); }); it('should override specific properties in options', () => { - const { clientApi } = getContext(undefined); + const { clientApi, storyStore } = getContext(undefined); + const { storiesOf } = clientApi; clientApi.addParameters({ backgrounds: ['value'], options: { a: '1', b: '3' } }); clientApi.addParameters({ options: { a: '2' } }); + storiesOf('kind', module).add('name', ({ parameters }) => parameters); // @ts-ignore - expect(clientApi._globalParameters).toEqual({ - backgrounds: ['value'], - options: { a: '2', b: '3' }, - docs: {}, - }); + const { + options: { hierarchyRootSeparator, hierarchySeparator, ...rest }, + backgrounds, + } = storyStore.fromId('kind--name').storyFn(); + + expect(backgrounds).toEqual(['value']); + expect(rest).toEqual({ a: '2', b: '3' }); }); it('should replace top level properties and override specific properties in options', () => { - const { clientApi } = getContext(undefined); + const { clientApi, storyStore } = getContext(undefined); + const { storiesOf } = clientApi; clientApi.addParameters({ backgrounds: ['value'], options: { a: '1', b: '3' } }); clientApi.addParameters({ backgrounds: [], options: { a: '2' } }); + storiesOf('kind', module).add('name', ({ parameters }) => parameters); // @ts-ignore - expect(clientApi._globalParameters).toEqual({ - backgrounds: [], - options: { a: '2', b: '3' }, - docs: {}, - }); + const { + options: { hierarchyRootSeparator, hierarchySeparator, ...rest }, + backgrounds, + } = storyStore.fromId('kind--name').storyFn(); + + expect(backgrounds).toEqual([]); + expect(rest).toEqual({ a: '2', b: '3' }); }); it('should deep merge in options', () => { - const { clientApi } = getContext(undefined); + const { clientApi, storyStore } = getContext(undefined); + const { storiesOf } = clientApi; clientApi.addParameters({ options: { a: '1', b: '2', theming: { c: '3' } } }); clientApi.addParameters({ options: { theming: { c: '4', d: '5' } } }); + storiesOf('kind', module).add('name', ({ parameters }) => parameters); // @ts-ignore - expect(clientApi._globalParameters).toEqual({ - options: { a: '1', b: '2', theming: { c: '4', d: '5' } }, - docs: {}, - }); + const { + options: { hierarchyRootSeparator, hierarchySeparator, ...rest }, + } = storyStore.fromId('kind--name').storyFn(); + + expect(rest).toEqual({ a: '1', b: '2', theming: { c: '4', d: '5' } }); }); }); @@ -250,14 +276,16 @@ describe('preview.client_api', () => { describe('clearDecorators', () => { it('should remove all global decorators', () => { - const { clientApi } = getContext(undefined); + const { clientApi, storyStore } = getContext(undefined); + const { storiesOf } = clientApi; - // @ts-ignore - clientApi._globalDecorators = 1234; + clientApi.addDecorator(() => 'foo'); clientApi.clearDecorators(); - // @ts-ignore - expect(clientApi._globalDecorators).toEqual([]); + storiesOf('kind', module).add('name', () => 'bar'); + + const result = storyStore.fromId('kind--name').storyFn(); + expect(result).toBe(`bar`); }); }); diff --git a/lib/client-api/src/client_api.ts b/lib/client-api/src/client_api.ts index 83be73620112..0b93951b7e6c 100644 --- a/lib/client-api/src/client_api.ts +++ b/lib/client-api/src/client_api.ts @@ -82,23 +82,39 @@ const withSubscriptionTracking = (storyFn: StoryFn) => { return result; }; +let _globalDecorators: DecoratorFunction[] = []; + +let _globalParameters: Parameters = {}; + +export const addDecorator = (decoratorFn: DecoratorFunction) => { + _globalDecorators.push(decoratorFn); +}; + +export const addParameters = (parameters: Parameters) => { + _globalParameters = { + ..._globalParameters, + ...parameters, + options: { + ...merge(get(_globalParameters, 'options', {}), get(parameters, 'options', {})), + }, + // FIXME: https://github.com/storybookjs/storybook/issues/7872 + docs: { + ...merge(get(_globalParameters, 'docs', {}), get(parameters, 'docs', {})), + }, + }; +}; + export default class ClientApi { private _storyStore: StoryStore; private _addons: ClientApiAddons; - private _globalDecorators: DecoratorFunction[]; - - private _globalParameters: Parameters; - private _decorateStory: (storyFn: StoryFn, decorators: DecoratorFunction[]) => any; constructor({ storyStore, decorateStory = defaultDecorateStory }: ClientApiParams) { this._storyStore = storyStore; this._addons = {}; - this._globalDecorators = []; - this._globalParameters = {}; this._decorateStory = decorateStory; if (!storyStore) { @@ -116,29 +132,24 @@ export default class ClientApi { getSeparators = () => ({ hierarchyRootSeparator: '|', hierarchySeparator: /\/|\./, - ...this._globalParameters.options, + ..._globalParameters.options, }); addDecorator = (decorator: DecoratorFunction) => { - this._globalDecorators.push(decorator); + addDecorator(decorator); }; - addParameters = (parameters: Parameters | { globalParameter: 'string' }) => { - this._globalParameters = { - ...this._globalParameters, - ...parameters, - options: { - ...merge(get(this._globalParameters, 'options', {}), get(parameters, 'options', {})), - }, - // FIXME: https://github.com/storybookjs/storybook/issues/7872 - docs: { - ...merge(get(this._globalParameters, 'docs', {}), get(parameters, 'docs', {})), - }, - }; + addParameters = (parameters: Parameters) => { + addParameters(parameters); }; clearDecorators = () => { - this._globalDecorators = []; + _globalDecorators = []; + }; + + clearParameters = () => { + // Utility function FOR TESTING USE ONLY + _globalParameters = {}; }; // what are the occasions that "m" is a boolean vs an obj @@ -195,7 +206,6 @@ export default class ClientApi { api.add = (storyName, storyFn, parameters) => { hasAdded = true; - const { _globalParameters, _globalDecorators } = this; const id = toId(kind, storyName); diff --git a/lib/client-api/src/index.ts b/lib/client-api/src/index.ts index 575cccde1e91..1ee7795e0aab 100644 --- a/lib/client-api/src/index.ts +++ b/lib/client-api/src/index.ts @@ -1,4 +1,4 @@ -import ClientApi, { defaultDecorateStory } from './client_api'; +import ClientApi, { defaultDecorateStory, addDecorator, addParameters } from './client_api'; import StoryStore from './story_store'; import ConfigApi from './config_api'; import subscriptionsStore from './subscriptions_store'; @@ -17,4 +17,6 @@ export { pathToId, getQueryParams, getQueryParam, + addDecorator, + addParameters, }; diff --git a/lib/ui/src/settings/typings.d.ts b/lib/ui/src/settings/typings.d.ts index 36aa11f86bc7..e388e48d9c30 100644 --- a/lib/ui/src/settings/typings.d.ts +++ b/lib/ui/src/settings/typings.d.ts @@ -1,3 +1,2 @@ declare module 'global'; -declare module '@storybook/client-logger'; declare module '@storybook/components/src/treeview/utils';