diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/custom-elements.json b/addons/docs/src/frameworks/web-components/__testfixtures__/custom-elements.json new file mode 100644 index 00000000000..aaba478e95e --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/custom-elements.json @@ -0,0 +1,448 @@ +{ + "schemaVersion": "1.0.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "demo-wc-card.js", + "declarations": [], + "exports": [ + { + "kind": "custom-element-definition", + "name": "demo-wc-card", + "declaration": { + "name": "DemoWcCard", + "module": "/src/stories/misc/to-update/DemoWcCard.js" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/typings.d.ts", + "declarations": [], + "exports": [] + }, + { + "kind": "javascript-module", + "path": "src/components/sb-button.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "SbButton", + "cssProperties": [ + { + "description": "Controls the color of bar", + "name": "--sb-primary-color", + "default": "#1ea7fd" + } + ], + "members": [ + { + "kind": "field", + "name": "primary", + "type": { + "text": "boolean" + }, + "description": "Set button in primary mode", + "privacy": "public" + }, + { + "kind": "field", + "name": "backgroundColor", + "type": { + "text": "string" + }, + "privacy": "public" + }, + { + "kind": "field", + "name": "size", + "type": { + "text": "'small' | 'medium' | 'large'" + }, + "default": "'medium'", + "privacy": "public" + }, + { + "kind": "field", + "name": "label", + "default": "''", + "privacy": "public" + }, + { + "kind": "method", + "name": "onClick", + "privacy": "private" + } + ], + "events": [ + { + "name": "sb-button:click", + "type": { + "text": "CustomEvent" + }, + "description": "Custom event send when the button is clicked" + } + ], + "attributes": [ + { + "type": { + "text": "string" + }, + "description": "Label of the button", + "name": "label" + }, + { + "type": { + "text": "string" + }, + "description": "Size of the button, can be \"small\", \"medium\" or \"large\"; default is \"medium\".", + "name": "size" + }, + { + "type": { + "text": "string" + }, + "description": "Color of the button's background", + "name": "backgroundColor" + }, + { + "name": "label", + "fieldName": "label" + }, + { + "name": "primary", + "fieldName": "primary" + }, + { + "name": "size", + "fieldName": "size" + }, + { + "name": "backgroundColor", + "fieldName": "backgroundColor" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "sb-button", + "summary": "This is a simple Storybook Button", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "SbButton", + "declaration": { + "name": "SbButton", + "module": "src/components/sb-button.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "sb-button", + "declaration": { + "name": "SbButton", + "module": "src/components/sb-button.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/components/sb-header.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "SbHeader", + "members": [ + { + "kind": "field", + "name": "user", + "type": { + "text": "{}" + }, + "privacy": "public" + }, + { + "kind": "method", + "name": "dispatchCustomEvent", + "privacy": "private", + "parameters": [ + { + "name": "eventName", + "type": { + "text": "string" + } + } + ] + }, + { + "kind": "method", + "name": "logInOutButton", + "privacy": "private" + } + ], + "events": [ + { + "type": { + "text": "CustomEvent" + } + }, + { + "type": { + "text": "CustomEvent" + }, + "description": "Event send when user clicks on create account button", + "name": "sb-header:createAccount" + }, + { + "type": { + "text": "CustomEvent" + }, + "description": "Event send when user clicks on login button", + "name": "sb-header:login" + }, + { + "type": { + "text": "CustomEvent" + }, + "description": "Event send when user clicks on logout button", + "name": "sb-header:logout" + } + ], + "attributes": [ + { + "type": { + "text": "Object" + }, + "description": "User of the app", + "name": "user" + }, + { + "name": "user", + "fieldName": "user" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "sb-header", + "summary": "This is a simple Storybook Header", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "SbHeader", + "declaration": { + "name": "SbHeader", + "module": "src/components/sb-header.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "sb-header", + "declaration": { + "name": "SbHeader", + "module": "src/components/sb-header.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/components/sb-page.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "SbPage", + "members": [ + { + "kind": "field", + "name": "user", + "type": { + "text": "{}" + }, + "privacy": "public" + } + ], + "attributes": [ + { + "type": { + "text": "Object" + }, + "description": "User of the app", + "name": "user" + }, + { + "name": "user", + "fieldName": "user" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "sb-page", + "summary": "This is a simple Storybook Page", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "SbPage", + "declaration": { + "name": "SbPage", + "module": "src/components/sb-page.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "sb-page", + "declaration": { + "name": "SbPage", + "module": "src/components/sb-page.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/stories/misc/to-update/DemoWcCard.js", + "declarations": [ + { + "kind": "class", + "description": "This is a container looking like a card with a back and front side you can switch", + "name": "DemoWcCard", + "cssProperties": [ + { + "description": "Header font size", + "name": "--demo-wc-card-header-font-size" + }, + { + "description": "Font color for front", + "name": "--demo-wc-card-front-color" + }, + { + "description": "Font color for back", + "name": "--demo-wc-card-back-color" + } + ], + "cssParts": [ + { + "description": "Front of the card", + "name": "front" + }, + { + "description": "Back of the card", + "name": "back" + } + ], + "slots": [ + { + "description": "This is an unnamed slot (the default slot)", + "name": "" + } + ], + "members": [ + { + "kind": "method", + "name": "toggle" + }, + { + "kind": "field", + "name": "backSide", + "privacy": "public", + "description": "Indicates that the back of the card is shown", + "default": "false" + }, + { + "kind": "field", + "name": "header", + "privacy": "public", + "description": "Header message", + "default": "'Your Message'" + }, + { + "kind": "field", + "name": "rows", + "privacy": "public", + "description": "Data rows", + "default": "[]" + } + ], + "events": [ + { + "name": "side-changed", + "type": { + "text": "CustomEvent" + }, + "description": "Fires whenever it switches between front/back" + } + ], + "attributes": [ + { + "name": "back-side", + "fieldName": "backSide" + }, + { + "name": "header", + "fieldName": "header" + }, + { + "name": "rows", + "fieldName": "rows" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "demo-wc-card", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "DemoWcCard", + "declaration": { + "name": "DemoWcCard", + "module": "src/stories/misc/to-update/DemoWcCard.js" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/stories/misc/to-update/demoWcCardStyle.css.js", + "declarations": [ + { + "kind": "variable", + "name": "demoWcCardStyle" + } + ], + "exports": [ + { + "kind": "js", + "name": "demoWcCardStyle", + "declaration": { + "name": "demoWcCardStyle", + "module": "src/stories/misc/to-update/demoWcCardStyle.css.js" + } + } + ] + } + ] +} diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot index 57a2c58e886..127bc8704a4 100644 --- a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot @@ -2,23 +2,6 @@ exports[`web-components component properties lit-element-demo-card 1`] = ` Object { - "": Object { - "description": "This is an unnamed slot (the default slot)", - "name": "", - "required": false, - "table": Object { - "category": "slots", - "defaultValue": Object { - "summary": undefined, - }, - "type": Object { - "summary": undefined, - }, - }, - "type": Object { - "name": "void", - }, - }, "--demo-wc-card-back-color": Object { "description": "Font color for back", "name": "--demo-wc-card-back-color", @@ -155,6 +138,15 @@ Object { "name": "string", }, }, + "onSideChanged": Object { + "action": Object { + "name": "side-changed", + }, + "name": "onSideChanged", + "table": Object { + "disable": true, + }, + }, "rows": Object { "description": "Data rows", "name": "rows", diff --git a/addons/docs/src/frameworks/web-components/custom-elements.test.ts b/addons/docs/src/frameworks/web-components/custom-elements.test.ts new file mode 100644 index 00000000000..0d19418e757 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/custom-elements.test.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-underscore-dangle */ +import global from 'global'; +import { extractArgTypes } from './custom-elements'; +import customElementsManifest from './__testfixtures__/custom-elements.json'; + +declare global { + interface Window { + __STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__: any; + } +} + +const { window } = global; + +describe('extractArgTypes', () => { + beforeEach(() => { + window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ = customElementsManifest; + }); + + afterEach(() => { + window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ = undefined; + }); + + describe('events', () => { + it('should map to an action event handler', () => { + const { onSbHeaderCreateAccount } = extractArgTypes('sb-header'); + + expect(onSbHeaderCreateAccount).toEqual({ + name: 'onSbHeaderCreateAccount', + action: { name: 'sb-header:createAccount' }, + table: { disable: true }, + }); + }); + + it('should map to a regular item', () => { + const { 'sb-header:createAccount': item } = extractArgTypes('sb-header'); + + expect(item).toEqual({ + name: 'sb-header:createAccount', + required: false, + description: 'Event send when user clicks on create account button', + type: { name: 'void' }, + table: { + category: 'events', + type: { summary: 'CustomEvent' }, + defaultValue: { summary: undefined }, + }, + }); + }); + }); +}); diff --git a/addons/docs/src/frameworks/web-components/custom-elements.ts b/addons/docs/src/frameworks/web-components/custom-elements.ts index 38d83a8f56a..3c17aa7fd7d 100644 --- a/addons/docs/src/frameworks/web-components/custom-elements.ts +++ b/addons/docs/src/frameworks/web-components/custom-elements.ts @@ -1,10 +1,10 @@ import { getCustomElements, isValidComponent, isValidMetaData } from '@storybook/web-components'; -import { ArgTypes } from '@storybook/api'; +import { ArgType, ArgTypes } from '@storybook/api'; import { logger } from '@storybook/client-logger'; interface TagItem { name: string; - type: { text: string }; + type: { [key: string]: any }; description: string; default?: any; kind?: string; @@ -50,30 +50,57 @@ function mapData(data: TagItem[], category: string) { return ( data && data - .filter((item) => !!item) + .filter((item) => item && item.name) .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, - }, - }, - }; + switch (category) { + case 'events': + mapEvent(item).forEach((argType) => { + acc[argType.name] = argType; + }); + break; + default: + acc[item.name] = mapItem(item, category); + break; + } + return acc; }, {} as ArgTypes) ); } +function mapItem(item: TagItem, category: string): ArgType { + const type = + category === 'properties' ? { name: item.type?.text || item.type } : { name: 'void' }; + + return { + 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, + }, + }, + }; +} + +function mapEvent(item: TagItem): ArgType[] { + let name = item.name + .replace(/(-|_|:|\.|\s)+(.)?/g, (_match, _separator, chr: string) => { + return chr ? chr.toUpperCase() : ''; + }) + .replace(/^([A-Z])/, (match) => match.toLowerCase()); + + name = `on${name.charAt(0).toUpperCase() + name.substr(1)}`; + + return [{ name, action: { name: item.name }, table: { disable: true } }, mapItem(item, 'events')]; +} + const getMetaDataExperimental = (tagName: string, customElements: CustomElements) => { if (!isValidComponent(tagName) || !isValidMetaData(customElements)) { return null; diff --git a/examples/web-components-kitchen-sink/src/components/sb-header.stories.ts b/examples/web-components-kitchen-sink/src/components/sb-header.stories.ts index 02240685746..a3ce9a2e2af 100644 --- a/examples/web-components-kitchen-sink/src/components/sb-header.stories.ts +++ b/examples/web-components-kitchen-sink/src/components/sb-header.stories.ts @@ -9,12 +9,30 @@ export default { component: 'sb-header', } as Meta; -const Template: Story = ({ user }) => html``; +interface SbHeaderProps extends SbHeader { + onSbHeaderCreateAccount: (event: Event) => void; + onSbHeaderLogin: (event: Event) => void; + onSbHeaderLogout: (event: Event) => void; +} -export const LoggedIn: Story = Template.bind({}); +const Template: Story = ({ + user, + onSbHeaderCreateAccount, + onSbHeaderLogin, + onSbHeaderLogout, +}) => { + return html``; +}; + +export const LoggedIn: Story = Template.bind({}); LoggedIn.args = { user: {}, }; -export const LoggedOut: Story = Template.bind({}); +export const LoggedOut: Story = Template.bind({}); LoggedOut.args = {}; diff --git a/examples/web-components-kitchen-sink/src/components/sb-header.ts b/examples/web-components-kitchen-sink/src/components/sb-header.ts index f151d033ee9..3325d21d1e6 100644 --- a/examples/web-components-kitchen-sink/src/components/sb-header.ts +++ b/examples/web-components-kitchen-sink/src/components/sb-header.ts @@ -104,10 +104,16 @@ export class SbHeader extends LitElement { private logInOutButton() { return this.user - ? html`` - : html``; + ? html`` + : html``; } }