Merge pull request #10946 from storybookjs/10583-controls-for-angular

Addon-controls: Angular support
This commit is contained in:
Michael Shilman 2020-05-28 12:27:00 +08:00 committed by GitHub
commit 93658d5fed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 178 additions and 67 deletions

View File

@ -3,19 +3,19 @@ import addons, { types } from '@storybook/addons';
import { AddonPanel } from '@storybook/components';
import { API } from '@storybook/api';
import { ControlsPanel } from './components/ControlsPanel';
import { ADDON_ID } from './constants';
import { ADDON_ID, PARAM_KEY } from './constants';
addons.register(ADDON_ID, (api: API) => {
addons.addPanel(ADDON_ID, {
title: 'Controls',
type: types.PANEL,
render: ({ active }) => {
paramKey: PARAM_KEY,
render: ({ key, active }) => {
if (!active || !api.getCurrentStoryData()) {
return null;
}
return (
<AddonPanel active={active}>
{false}
<AddonPanel key={key} active={active}>
<ControlsPanel />
</AddonPanel>
);

View File

@ -51,6 +51,7 @@
"@storybook/addons": "6.0.0-beta.16",
"@storybook/api": "6.0.0-beta.16",
"@storybook/client-api": "6.0.0-beta.16",
"@storybook/client-logger": "6.0.0-beta.16",
"@storybook/components": "6.0.0-beta.16",
"@storybook/core": "6.0.0-beta.16",
"@storybook/core-events": "6.0.0-beta.16",

View File

@ -3,9 +3,7 @@
exports[`angular component properties doc-button 1`] = `
Object {
"_inputValue": Object {
"defaultValue": Object {
"summary": "'some value'",
},
"defaultValue": "some value",
"description": "",
"name": "_inputValue",
"table": Object {
@ -15,11 +13,12 @@ Object {
"summary": "string",
},
},
"type": Object {
"name": "void",
},
},
"_value": Object {
"defaultValue": Object {
"summary": "'Private hello'",
},
"defaultValue": "Private hello",
"description": "<p>Private value. </p>
",
"name": "_value",
@ -30,11 +29,12 @@ Object {
"summary": "string",
},
},
"type": Object {
"name": "void",
},
},
"appearance": Object {
"defaultValue": Object {
"summary": "'secondary'",
},
"defaultValue": "secondary",
"description": "<p>Appearance style of the button. </p>
",
"name": "appearance",
@ -45,11 +45,16 @@ Object {
"summary": "\\"primary\\" | \\"secondary\\"",
},
},
"type": Object {
"name": "enum",
"value": Array [
"primary",
"secondary",
],
},
},
"buttonRef": Object {
"defaultValue": Object {
"summary": undefined,
},
"defaultValue": undefined,
"description": "",
"name": "buttonRef",
"table": Object {
@ -59,11 +64,12 @@ Object {
"summary": "ElementRef",
},
},
"type": Object {
"name": "void",
},
},
"calc": Object {
"defaultValue": Object {
"summary": "",
},
"defaultValue": undefined,
"description": "<p>An internal calculation method which adds <code>x</code> and <code>y</code> together.</p>
",
"name": "calc",
@ -74,11 +80,12 @@ Object {
"summary": "(x: number, y: string | number) => number",
},
},
"type": Object {
"name": "void",
},
},
"inputValue": Object {
"defaultValue": Object {
"summary": undefined,
},
"defaultValue": undefined,
"description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p>
",
"name": "inputValue",
@ -89,11 +96,12 @@ Object {
"summary": "string",
},
},
"type": Object {
"name": "string",
},
},
"internalProperty": Object {
"defaultValue": Object {
"summary": "'Public hello'",
},
"defaultValue": "Public hello",
"description": "<p>Public value. </p>
",
"name": "internalProperty",
@ -104,11 +112,12 @@ Object {
"summary": "string",
},
},
"type": Object {
"name": "void",
},
},
"isDisabled": Object {
"defaultValue": Object {
"summary": "false",
},
"defaultValue": false,
"description": "<p>Sets the button to a disabled state. </p>
",
"name": "isDisabled",
@ -119,11 +128,12 @@ Object {
"summary": undefined,
},
},
"type": Object {
"name": "boolean",
},
},
"item": Object {
"defaultValue": Object {
"summary": undefined,
},
"defaultValue": undefined,
"description": undefined,
"name": "item",
"table": Object {
@ -133,11 +143,12 @@ Object {
"summary": "[]",
},
},
"type": Object {
"name": "object",
},
},
"label": Object {
"defaultValue": Object {
"summary": undefined,
},
"defaultValue": undefined,
"description": "<p>The inner text of the button.</p>
",
"name": "label",
@ -148,11 +159,12 @@ Object {
"summary": "string",
},
},
"type": Object {
"name": "string",
},
},
"onClick": Object {
"defaultValue": Object {
"summary": "new EventEmitter<Event>()",
},
"defaultValue": undefined,
"description": "<p>Handler to be called when the button is clicked by a user.</p>
<p>Will also block the emission of the event if <code>isDisabled</code> is true.</p>
",
@ -164,11 +176,12 @@ Object {
"summary": "EventEmitter",
},
},
"type": Object {
"name": "void",
},
},
"privateMethod": Object {
"defaultValue": Object {
"summary": "",
},
"defaultValue": undefined,
"description": "<p>A private method.</p>
",
"name": "privateMethod",
@ -179,11 +192,12 @@ Object {
"summary": "(password: string) => void",
},
},
"type": Object {
"name": "void",
},
},
"processedItem": Object {
"defaultValue": Object {
"summary": undefined,
},
"defaultValue": undefined,
"description": "",
"name": "processedItem",
"table": Object {
@ -193,11 +207,12 @@ Object {
"summary": "T[]",
},
},
"type": Object {
"name": "void",
},
},
"protectedMethod": Object {
"defaultValue": Object {
"summary": "",
},
"defaultValue": undefined,
"description": "<p>A protected method.</p>
",
"name": "protectedMethod",
@ -208,11 +223,12 @@ Object {
"summary": "(id?: number) => void",
},
},
"type": Object {
"name": "void",
},
},
"publicMethod": Object {
"defaultValue": Object {
"summary": "",
},
"defaultValue": undefined,
"description": "<p>A public method using an interface. </p>
",
"name": "publicMethod",
@ -223,11 +239,12 @@ Object {
"summary": "(things: ISomeInterface) => void",
},
},
"type": Object {
"name": "void",
},
},
"showKeyAlias": Object {
"defaultValue": Object {
"summary": undefined,
},
"defaultValue": undefined,
"description": undefined,
"name": "showKeyAlias",
"table": Object {
@ -237,11 +254,12 @@ Object {
"summary": "",
},
},
"type": Object {
"name": "void",
},
},
"size": Object {
"defaultValue": Object {
"summary": "'medium'",
},
"defaultValue": "medium",
"description": "<p>Size of the button. </p>
",
"name": "size",
@ -252,11 +270,12 @@ Object {
"summary": "ButtonSize",
},
},
"type": Object {
"name": "object",
},
},
"somethingYouShouldNotUse": Object {
"defaultValue": Object {
"summary": "false",
},
"defaultValue": false,
"description": "<p>Some input you shouldn&#39;t use.</p>
",
"name": "somethingYouShouldNotUse",
@ -267,6 +286,9 @@ Object {
"summary": undefined,
},
},
"type": Object {
"name": "boolean",
},
},
}
`;

View File

@ -0,0 +1,41 @@
import { extractType } from './compodoc';
import { Decorator } from './types';
const makeProperty = (compodocType?: string) => ({
type: compodocType,
name: 'dummy',
decorators: [] as Decorator[],
optional: true,
});
describe('extractType', () => {
describe('with compodoc type', () => {
it.each([
['string', { name: 'string' }],
['boolean', { name: 'boolean' }],
['number', { name: 'number' }],
['object', { name: 'object' }],
['foo', { name: 'object' }],
[null, { name: 'void' }],
[undefined, { name: 'void' }],
['T[]', { name: 'object' }],
['[]', { name: 'object' }],
['"primary" | "secondary"', { name: 'enum', value: ['primary', 'secondary'] }],
])('%s', (compodocType, expected) => {
expect(extractType(makeProperty(compodocType), null)).toEqual(expected);
});
});
describe('without compodoc type', () => {
it.each([
['string', { name: 'string' }],
[false, { name: 'boolean' }],
[10, { name: 'number' }],
[['abc'], { name: 'object' }],
[{ foo: 1 }, { name: 'object' }],
[undefined, { name: 'void' }],
])('%s', (defaultValue, expected) => {
expect(extractType(makeProperty(null), defaultValue)).toEqual(expected);
});
});
});

View File

@ -3,6 +3,8 @@
import { PropDef } from '@storybook/components';
import { ArgType, ArgTypes } from '@storybook/api';
import { logger } from '@storybook/client-logger';
import { string } from 'prop-types';
import { Argument, CompodocJson, Component, Method, Property, Directive } from './types';
type Sections = Record<string, PropDef[]>;
@ -90,6 +92,46 @@ const displaySignature = (item: Method): string => {
return `(${args.join(', ')}) => ${item.returnType}`;
};
const extractTypeFromValue = (defaultValue: any) => {
const valueType = typeof defaultValue;
return defaultValue || valueType === 'boolean' ? valueType : null;
};
const extractEnumValues = (compodocType: any) => {
if (typeof compodocType !== 'string' || compodocType.indexOf('|') === -1) {
return null;
}
return compodocType.split('|').map((value) => JSON.parse(value));
};
export const extractType = (property: Property, defaultValue: any) => {
const compodocType = property.type || extractTypeFromValue(defaultValue);
switch (compodocType) {
case 'string':
case 'boolean':
case 'number':
return { name: compodocType };
case undefined:
case null:
return { name: 'void' };
default: {
const enumValues = extractEnumValues(compodocType);
return enumValues ? { name: 'enum', value: enumValues } : { name: 'object' };
}
}
};
const extractDefaultValue = (property: Property) => {
try {
// eslint-disable-next-line no-eval
const value = eval(property.defaultValue);
return value;
} catch (err) {
logger.info(`Error extracting ${property.name}: $ {property.defaultValue}`);
return undefined;
}
};
export const extractArgTypesFromData = (componentData: Directive) => {
const sectionToItems: Record<string, ArgType[]> = {};
const compodocClasses = ['propertiesClass', 'methodsClass', 'inputsClass', 'outputsClass'];
@ -99,10 +141,16 @@ export const extractArgTypesFromData = (componentData: Directive) => {
const data = componentData[key] || [];
data.forEach((item: Method | Property) => {
const section = mapItemToSection(key, item);
const defaultValue = isMethod(item) ? undefined : extractDefaultValue(item as Property);
const type =
isMethod(item) || section !== 'inputs'
? { name: 'void' }
: extractType(item as Property, defaultValue);
const argType = {
name: item.name,
description: item.description,
defaultValue: { summary: isMethod(item) ? '' : item.defaultValue },
defaultValue,
type,
table: {
category: section,
type: {

View File

@ -2,6 +2,7 @@ module.exports = {
stories: ['../src/stories/**/*.stories.(ts|mdx)'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-controls',
'@storybook/addon-storysource',
'@storybook/addon-actions',
'@storybook/addon-links',

View File

@ -40,6 +40,7 @@
"@storybook/addon-a11y": "6.0.0-beta.16",
"@storybook/addon-actions": "6.0.0-beta.16",
"@storybook/addon-backgrounds": "6.0.0-beta.16",
"@storybook/addon-controls": "6.0.0-beta.16",
"@storybook/addon-docs": "6.0.0-beta.16",
"@storybook/addon-jest": "6.0.0-beta.16",
"@storybook/addon-knobs": "6.0.0-beta.16",

View File

@ -15,7 +15,7 @@ exports[`Storyshots DocButton Basic 1`] = `
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo="
width="100"
/>
Docs Test
Args test
</button>
</my-button>

View File

@ -1,4 +1,4 @@
<button [ngClass]="classes" #buttonRef>
<button [disabled]="isDisabled" [ngClass]="classes" #buttonRef>
<img
width="100"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyNTAgMjUwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNTAgMjUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojREQwMDMxO30NCgkuc3Qxe2ZpbGw6I0MzMDAyRjt9DQoJLnN0MntmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTI1LDMwIDEyNSwzMCAxMjUsMzAgMzEuOSw2My4yIDQ2LjEsMTg2LjMgMTI1LDIzMCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAJIi8+DQoJPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIxMjUsMzAgMTI1LDUyLjIgMTI1LDUyLjEgMTI1LDE1My40IDEyNSwxNTMuNCAxMjUsMjMwIDEyNSwyMzAgMjAzLjksMTg2LjMgMjE4LjEsNjMuMiAxMjUsMzAgCSIvPg0KCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMjUsNTIuMUw2Ni44LDE4Mi42aDBoMjEuN2gwbDExLjctMjkuMmg0OS40bDExLjcsMjkuMmgwaDIxLjdoMEwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMUwxMjUsNTIuMQ0KCQlMMTI1LDUyLjF6IE0xNDIsMTM1LjRIMTA4bDE3LTQwLjlMMTQyLDEzNS40eiIvPg0KPC9nPg0KPC9zdmc+DQo="

View File

@ -6,9 +6,8 @@ export default {
parameters: { docs: { iframeHeight: 120 } },
};
export const Basic = () => ({
export const Basic = (args) => ({
component: ButtonComponent,
props: {
label: 'Docs Test',
},
props: args,
});
Basic.args = { label: 'Args test', isDisabled: false };

View File

@ -1,5 +1,4 @@
export const parameters = {
passArgsFirst: true,
docs: {
iframeHeight: '60px',
},

View File

@ -7,7 +7,6 @@ Vue.component('my-button', MyButton);
Vue.use(Vuex);
export const parameters = {
passArgsFirst: true,
docs: {
iframeHeight: '60px',
},