Skip to content

Commit

Permalink
Merge pull request #15138 from thepassle/feat/support-custom-elements…
Browse files Browse the repository at this point in the history
…-manifest-v1

Web-components: Custom Elements Manifest v1 support
  • Loading branch information
gaetanmaisse committed Jun 29, 2021
2 parents a939907 + 56d28eb commit 88e5774
Show file tree
Hide file tree
Showing 17 changed files with 1,022 additions and 309 deletions.
84 changes: 65 additions & 19 deletions addons/docs/src/frameworks/web-components/custom-elements.ts
Expand Up @@ -4,9 +4,10 @@ import { logger } from '@storybook/client-logger';

interface TagItem {
name: string;
type: string;
type: { text: string };
description: string;
default?: any;
kind?: string;
defaultValue?: any;
}

Expand All @@ -17,15 +18,25 @@ interface Tag {
properties?: TagItem[];
events?: TagItem[];
methods?: TagItem[];
members?: TagItem[];
slots?: TagItem[];
cssProperties?: TagItem[];
cssParts?: TagItem[];
}

interface CustomElements {
tags: Tag[];
modules?: [];
}

interface Module {
declarations?: [];
exports?: [];
}

interface Declaration {
tagName: string;
}
interface Sections {
attributes?: any;
properties?: any;
Expand All @@ -38,25 +49,32 @@ interface Sections {
function mapData(data: TagItem[], category: string) {
return (
data &&
data.reduce((acc, item) => {
const type = category === 'properties' ? { name: item.type } : { name: 'void' };
acc[item.name] = {
name: item.name,
required: false,
description: item.description,
type,
table: {
category,
type: { summary: item.type },
defaultValue: { summary: item.default !== undefined ? item.default : item.defaultValue },
},
};
return acc;
}, {} as ArgTypes)
data
.filter((item) => !!item)
.reduce((acc, item) => {
if (item.kind === 'method') return acc;

const type =
category === 'properties' ? { name: item.type?.text || item.type } : { name: 'void' };
acc[item.name] = {
name: item.name,
required: false,
description: item.description,
type,
table: {
category,
type: { summary: item.type?.text || item.type },
defaultValue: {
summary: item.default !== undefined ? item.default : item.defaultValue,
},
},
};
return acc;
}, {} as ArgTypes)
);
}

const getMetaData = (tagName: string, customElements: CustomElements) => {
const getMetaDataExperimental = (tagName: string, customElements: CustomElements) => {
if (!isValidComponent(tagName) || !isValidMetaData(customElements)) {
return null;
}
Expand All @@ -69,23 +87,51 @@ const getMetaData = (tagName: string, customElements: CustomElements) => {
return metaData;
};

const getMetaDataV1 = (tagName: string, customElements: CustomElements) => {
if (!isValidComponent(tagName) || !isValidMetaData(customElements)) {
return null;
}

let metadata;
customElements?.modules?.forEach((_module: Module) => {
_module?.declarations?.forEach((declaration: Declaration) => {
if (declaration.tagName === tagName) {
metadata = declaration;
}
});
});

if (!metadata) {
logger.warn(`Component not found in custom-elements.json: ${tagName}`);
}
return metadata;
};

export const extractArgTypesFromElements = (tagName: string, customElements: CustomElements) => {
const metaData = getMetaData(tagName, customElements);
return (
metaData && {
...mapData(metaData.attributes, 'attributes'),
...mapData(metaData.members, 'properties'),
...mapData(metaData.properties, 'properties'),
...mapData(metaData.events, 'events'),
...mapData(metaData.methods, 'methods'),
...mapData(metaData.slots, 'slots'),
...mapData(metaData.cssProperties, 'css custom properties'),
...mapData(metaData.cssParts, 'css shadow parts'),
}
);
};

const getMetaData = (tagName: string, manifest: any) => {
if (manifest.version === 'experimental') {
return getMetaDataExperimental(tagName, manifest);
}
return getMetaDataV1(tagName, manifest);
};

export const extractArgTypes = (tagName: string) => {
return extractArgTypesFromElements(tagName, getCustomElements());
const cem = getCustomElements();
return extractArgTypesFromElements(tagName, cem);
};

export const extractComponentDescription = (tagName: string) => {
Expand Down
63 changes: 48 additions & 15 deletions addons/docs/web-components/README.md
Expand Up @@ -12,10 +12,10 @@
- Add to your `.storybook/preview.js`

```js
import { setCustomElements } from '@storybook/web-components';
import { setCustomElementsManifest } from '@storybook/web-components';
import customElements from '../custom-elements.json';

setCustomElements(customElements);
setCustomElementsManifest(customElements);
```

- Add to your story files
Expand All @@ -33,8 +33,12 @@ In order to get [Props tables](..docs/../../docs/props-tables.md) documentation

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`:
Known analyzers that output `custom-elements.json` v1.0.0:

- [@custom-elements-manifest/analyzer](https://github.com/open-wc/custom-elements-manifest)
- Supports Vanilla, LitElement, FASTElement, Stencil, Catalyst, Atomico

Known analyzers that output older versions of `custom-elements.json`:
- [web-component-analyzer](https://github.com/runem/web-component-analyzer)
- Supports LitElement, Polymer, Vanilla, (Stencil)
- [stenciljs](https://stenciljs.com/)
Expand All @@ -53,22 +57,51 @@ The file looks something like this:

```json
{
"version": 2,
"tags": [
"schemaVersion": "1.0.0",
"readme": "",
"modules": [
{
"name": "demo-wc-card",
"properties": [
"kind": "javascript-module",
"path": "src/my-element.js",
"declarations": [
{
"name": "header",
"type": "String",
"attribute": "header",
"description": "Shown at the top of the card",
"default": "Your Message"
"kind": "class",
"description": "",
"name": "MyElement",
"members": [
{
"kind": "field",
"name": "disabled"
},
{
"kind": "method",
"name": "fire"
}
],
"events": [
{
"name": "disabled-changed",
"type": {
"text": "Event"
}
}
],
"superclass": {
"name": "HTMLElement"
},
"tagName": "my-element"
}
],
"events": [],
"slots": [],
"cssProperties": []
"exports": [
{
"kind": "custom-element-definition",
"name": "my-element",
"declaration": {
"name": "MyElement",
"module": "src/my-element.js"
}
}
]
}
]
}
Expand Down
13 changes: 11 additions & 2 deletions app/web-components/src/client/customElements.ts
Expand Up @@ -15,7 +15,11 @@ export function isValidMetaData(customElements: any) {
if (!customElements) {
return false;
}
if (customElements.tags && Array.isArray(customElements.tags)) {

if (
(customElements.tags && Array.isArray(customElements.tags)) ||
(customElements.modules && Array.isArray(customElements.modules))
) {
return true;
}
throw new Error(`You need to setup valid meta data in your config.js via setCustomElements().
Expand All @@ -30,7 +34,12 @@ export function setCustomElements(customElements: any) {
window.__STORYBOOK_CUSTOM_ELEMENTS__ = customElements;
}

export function setCustomElementsManifest(customElements: any) {
// @ts-ignore
window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ = customElements;
}

export function getCustomElements() {
// @ts-ignore
return window.__STORYBOOK_CUSTOM_ELEMENTS__;
return window.__STORYBOOK_CUSTOM_ELEMENTS__ || window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__;
}
1 change: 1 addition & 0 deletions app/web-components/src/client/index.ts
Expand Up @@ -16,6 +16,7 @@ export {
export {
getCustomElements,
setCustomElements,
setCustomElementsManifest,
isValidComponent,
isValidMetaData,
} from './customElements';
Expand Down
6 changes: 3 additions & 3 deletions docs/essentials/auto-generated-controls/web-components.mdx
Expand Up @@ -12,10 +12,10 @@ Storybook uses this to auto-generate the `ArgTypes` for your component using you
You'll need to register that in `.storybook/preview.js`:

```js
import { setCustomElements } from '@storybook/web-components';
import { setCustomElementsManifest } from '@storybook/web-components';
import customElements from '../custom-elements.json';

setCustomElements(customElements);
setCustomElementsManifest(customElements);
```

You can generate a `custom-elements.json` using either [web-component-analyzer](https://github.com/runem/web-component-analyzer) or [stenciljs](https://stenciljs.com/) (if you're using Stencil).
You can generate a `custom-elements.json` using [@custom-elements-manifest/analyzer](https://github.com/open-wc/custom-elements-manifest). If you're using the pre-v1.0.0 version of `custom-elements.json` you can use either [web-component-analyzer](https://github.com/runem/web-component-analyzer) or [stenciljs](https://stenciljs.com/) (if you're using Stencil).
10 changes: 7 additions & 3 deletions examples/web-components-kitchen-sink/.storybook/preview.js
@@ -1,8 +1,12 @@
import { setCustomElements } from '@storybook/web-components';
import { setCustomElementsManifest } from '@storybook/web-components';

import customElements from '../custom-elements.json';
import customElementsManifest from '../custom-elements.json';

setCustomElements(customElements);
setCustomElementsManifest(customElementsManifest);

// TODO: Remove support of 0.x CustomElementManifest format in 7.0
// import customElements from '../custom-elements-experimental.json';
// setCustomElements(customElements);

export const parameters = {
a11y: {
Expand Down
@@ -0,0 +1,95 @@
{
"version": "experimental",
"tags": [
{
"name": "demo-wc-card",
"path": "./demo-wc-card.js",
"description": "This is a container looking like a card with a back and front side you can switch",
"attributes": [
{
"name": "back-side",
"description": "Indicates that the back of the card is shown",
"type": "boolean",
"default": "false"
},
{
"name": "header",
"description": "Header message",
"type": "string",
"default": "\"Your Message\""
},
{
"name": "rows",
"description": "Data rows",
"type": "object",
"default": "[]"
}
],
"properties": [
{
"name": "backSide",
"attribute": "back-side",
"description": "Indicates that the back of the card is shown",
"type": "boolean",
"default": "false"
},
{
"name": "header",
"attribute": "header",
"description": "Header message",
"type": "string",
"default": "\"Your Message\""
},
{
"name": "rows",
"attribute": "rows",
"description": "Data rows",
"type": "object",
"default": "[]"
}
],
"events": [
{
"name": "side-changed",
"description": "Fires whenever it switches between front/back"
}
],
"methods": [
{
"name": "testMethod",
"description": "Some web component frameworks like Stencil generate extra docs for methods. These are also displayed in the ArgsTable."
}
],
"slots": [
{
"name": "",
"description": "This is an unnamed slot (the default slot)"
}
],
"cssProperties": [
{
"name": "--demo-wc-card-header-font-size",
"description": "Header font size"
},
{
"name": "--demo-wc-card-front-color",
"description": "Font color for front"
},
{
"name": "--demo-wc-card-back-color",
"description": "Font color for back"
}
],
"cssParts": [
{
"name": "front",
"description": "Front of the card"
},
{
"name": "back",
"description": "Back of the card"
}
]
}
]
}

0 comments on commit 88e5774

Please sign in to comment.