Skip to content
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

Web-components: Custom Elements Manifest v1 support #15138

Merged
merged 14 commits into from Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 48 additions & 6 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 @@ -39,15 +50,18 @@ function mapData(data: TagItem[], category: string) {
return (
data &&
data.reduce((acc, item) => {
const type = category === 'properties' ? { name: item.type } : { name: 'void' };
if (item?.kind === 'method') return acc;
gaetanmaisse marked this conversation as resolved.
Show resolved Hide resolved

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 },
type: { summary: item?.type?.text || item.type },
defaultValue: { summary: item.default !== undefined ? item.default : item.defaultValue },
},
};
Expand All @@ -56,7 +70,7 @@ function mapData(data: TagItem[], category: string) {
);
}

const getMetaData = (tagName: string, customElements: CustomElements) => {
const getMetaDataExperimental = (tagName: string, customElements: CustomElements) => {
if (!isValidComponent(tagName) || !isValidMetaData(customElements)) {
return null;
}
Expand All @@ -69,23 +83,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
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
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"
}
]
}
]
}