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

Addon-docs: Vue slots/events props table + generalization #8489

Merged
merged 12 commits into from Oct 24, 2019
Merged
2 changes: 1 addition & 1 deletion __mocks__/inject-decorator.stories.txt
Expand Up @@ -3,7 +3,7 @@ import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import { action } from '@storybook/addon-actions';

import DocgenButton from '../components/DocgenButton';
import { DocgenButton } from '../components/DocgenButton';
import FlowTypeButton from '../components/FlowTypeButton';
import BaseButton from '../components/BaseButton';
import TableComponent from '../components/TableComponent';
Expand Down
4 changes: 2 additions & 2 deletions addons/docs/README.md
Expand Up @@ -97,10 +97,10 @@ First add the package. Make sure that the versions for your `@storybook/*` packa
yarn add -D @storybook/addon-docs
```

Docs has peer dependencies on `react` and `babel-loader`. If you want to write stories in MDX, you may need to add these dependencies as well:
Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you want to write stories in MDX, you may need to add these dependencies as well:

```sh
yarn add -D react babel-loader
yarn add -D react react-is babel-loader
```

Then add the following to your `.storybook/presets.js` exports:
Expand Down
3 changes: 1 addition & 2 deletions addons/docs/common/index.js
@@ -1,2 +1 @@
// FIXME: move this to typescript and src/react folder
module.exports = require('../dist/lib/getPropDefs');
module.exports = require('../dist/frameworks/common');
3 changes: 2 additions & 1 deletion addons/docs/package.json
Expand Up @@ -61,7 +61,8 @@
},
"peerDependencies": {
"babel-loader": "^8.0.0",
"react": "^16.8.0"
"react": "^16.8.0",
"react-is": "^16.8.0"
},
"publishConfig": {
"access": "public"
Expand Down
21 changes: 12 additions & 9 deletions addons/docs/src/blocks/Props.tsx
@@ -1,19 +1,24 @@
import React, { FunctionComponent } from 'react';
import { PropsTable, PropsTableError, PropsTableProps, PropDef } from '@storybook/components';
import { PropsTable, PropsTableError, PropsTableProps } from '@storybook/components';
import { DocsContext, DocsContextProps } from './DocsContext';
import { Component, CURRENT_SELECTION } from './shared';
import { getPropDefs as autoPropDefs, PropDefGetter } from '../lib/getPropDefs';

import { PropsExtractor } from '../lib/propsUtils';
import { extractProps as reactExtractProps } from '../frameworks/react/extractProps';
import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps';

interface PropsProps {
exclude?: string[];
of: '.' | Component;
}

const inferPropDefs = (framework: string): PropDefGetter | null => {
// FIXME: remove in SB6.0 & require config
const inferPropsExtractor = (framework: string): PropsExtractor | null => {
switch (framework) {
case 'react':
return reactExtractProps;
case 'vue':
return autoPropDefs;
return vueExtractProps;
default:
return null;
}
Expand All @@ -32,13 +37,11 @@ export const getPropsTableProps = (
throw new Error(PropsTableError.NO_COMPONENT);
}

const { getPropDefs = inferPropDefs(framework) } = params.docs || {};
if (!getPropDefs) {
const { extractProps = inferPropsExtractor(framework) } = params.docs || {};
if (!extractProps) {
throw new Error(PropsTableError.PROPS_UNSUPPORTED);
}
const allRows = getPropDefs(target);
const rows = !exclude ? allRows : allRows.filter((row: PropDef) => !exclude.includes(row.name));
return { rows };
return extractProps(target, { exclude });
} catch (err) {
return { error: err.message };
}
Expand Down
2 changes: 1 addition & 1 deletion addons/docs/src/frameworks/common/index.ts
@@ -1 +1 @@
export * from '../../lib/getPropDefs';
export * from '../../lib/propsUtils';
2 changes: 2 additions & 0 deletions addons/docs/src/frameworks/react/config.js
@@ -1,6 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import { addParameters } from '@storybook/react';
import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks';
import { extractProps } from './extractProps';

addParameters({
docs: {
Expand All @@ -9,5 +10,6 @@ addParameters({
// 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(),
extractProps,
},
});
@@ -1,15 +1,13 @@
/* eslint-disable no-underscore-dangle,react/forbid-foreign-prop-types */

import PropTypes from 'prop-types';
import { isForwardRef, isMemo } from 'react-is';
import { PropDef } from '@storybook/components';
import { Component } from '../blocks/shared';
import { PropDefGetter, PropsExtractor, propsFromDocgen, hasDocgen } from '../../lib/propsUtils';

interface PropDefMap {
export interface PropDefMap {
[p: string]: PropDef;
}

export type PropDefGetter = (type: Component) => PropDef[] | null;

const propTypesMap = new Map();

Object.keys(PropTypes).forEach(typeName => {
Expand All @@ -20,38 +18,15 @@ Object.keys(PropTypes).forEach(typeName => {
propTypesMap.set(type.isRequired, typeName);
});

const hasDocgen = (obj: any) => obj && obj.props && Object.keys(obj.props).length > 0;

const propsFromDocgen: PropDefGetter = type => {
const props: PropDefMap = {};
const docgenInfoProps = type.__docgenInfo.props;

Object.keys(docgenInfoProps).forEach(property => {
const docgenInfoProp = docgenInfoProps[property];
const defaultValueDesc = docgenInfoProp.defaultValue || {};
const propType = docgenInfoProp.flowType || docgenInfoProp.type || 'other';

props[property] = {
name: property,
type: propType,
required: docgenInfoProp.required,
description: docgenInfoProp.description,
defaultValue: defaultValueDesc.value,
};
});

return Object.values(props);
};

const propsFromPropTypes: PropDefGetter = type => {
const propsFromPropTypes: PropDefGetter = (type, section) => {
const props: PropDefMap = {};

if (type.propTypes) {
Object.keys(type.propTypes).forEach(property => {
const typeInfo = type.propTypes[property];
const required = typeInfo.isRequired === undefined;
const docgenInfo =
type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property];
type.__docgenInfo && type.__docgenInfo[section] && type.__docgenInfo[section][property];
const description = docgenInfo ? docgenInfo.description : null;
let propType = propTypesMap.get(typeInfo) || 'other';

Expand Down Expand Up @@ -84,15 +59,22 @@ const propsFromPropTypes: PropDefGetter = type => {
return Object.values(props);
};

export const getPropDefs: PropDefGetter = type => {
export const getPropDefs: PropDefGetter = (type, section) => {
let processedType = type;
if (type.render) {
processedType = type.render().type;
if (!hasDocgen(type)) {
if (isForwardRef(type) || type.render) {
processedType = type.render().type;
}
if (isMemo(type)) {
// (typeof type.type === 'function')?
processedType = type.type().type;
}
}
if (typeof type.type === 'function') {
processedType = type.type().type;
}
return hasDocgen(processedType.__docgenInfo)
? propsFromDocgen(processedType)
: propsFromPropTypes(processedType);
return hasDocgen(processedType)
? propsFromDocgen(processedType, section)
: propsFromPropTypes(processedType, section);
};

export const extractProps: PropsExtractor = component => ({
rows: getPropDefs(component, 'props'),
});
2 changes: 2 additions & 0 deletions addons/docs/src/frameworks/vue/config.js
Expand Up @@ -3,6 +3,7 @@ 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 { extractProps } from './extractProps';

addParameters({
docs: {
Expand All @@ -12,5 +13,6 @@ addParameters({
const Story = toReact(storyFn());
return <Story />;
},
extractProps,
},
});
15 changes: 15 additions & 0 deletions addons/docs/src/frameworks/vue/extractProps.ts
@@ -0,0 +1,15 @@
import { PropDef } from '@storybook/components';
import { PropsExtractor, propsFromDocgen, hasDocgen } from '../../lib/propsUtils';

const SECTIONS = ['props', 'events', 'slots'];

export const extractProps: PropsExtractor = component => {
if (!hasDocgen(component)) {
return null;
}
const sections: Record<string, PropDef[]> = {};
SECTIONS.forEach(section => {
sections[section] = propsFromDocgen(component, section);
});
return { sections };
};
38 changes: 38 additions & 0 deletions addons/docs/src/lib/propsUtils.ts
@@ -0,0 +1,38 @@
/* eslint-disable no-underscore-dangle */
import { PropDef, PropsTableProps } from '@storybook/components';
import { Component } from '../blocks/shared';

export type PropsExtractor = (component: Component) => PropsTableProps | null;

export type PropDefGetter = (type: Component, section: string) => PropDef[] | null;

export const hasDocgen = (obj: any) => !!obj.__docgenInfo;

export const hasDocgenSection = (obj: any, section: string) =>
obj.__docgenInfo &&
obj.__docgenInfo[section] &&
Object.keys(obj.__docgenInfo[section]).length > 0;

export const propsFromDocgen: PropDefGetter = (type, section) => {
const props: Record<string, PropDef> = {};
const docgenInfoProps = type.__docgenInfo[section];
if (!docgenInfoProps) {
return null;
}

Object.keys(docgenInfoProps).forEach(property => {
const docgenInfoProp = docgenInfoProps[property];
const defaultValueDesc = docgenInfoProp.defaultValue || {};
const propType = docgenInfoProp.flowType || docgenInfoProp.type || 'other';

props[property] = {
name: property,
type: propType,
required: docgenInfoProp.required,
description: docgenInfoProp.description,
defaultValue: defaultValueDesc.value,
};
});

return Object.values(props);
};
1 change: 1 addition & 0 deletions addons/docs/src/typings.d.ts
@@ -1,3 +1,4 @@
declare module '@mdx-js/react';
declare module '@storybook/addon-docs/mdx-compiler-plugin';
declare module 'global';
declare module 'react-is';
4 changes: 1 addition & 3 deletions examples/official-storybook/components/DocgenButton.js
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';

/** DocgenButton component description imported from comments inside the component file */
const DocgenButton = ({ disabled, label, onClick }) => (
export const DocgenButton = ({ disabled, label, onClick }) => (
<button type="button" disabled={disabled} onClick={onClick}>
{label}
</button>
Expand Down Expand Up @@ -145,5 +145,3 @@ DocgenButton.propTypes = {
*/
optionalString: PropTypes.string,
};

export default DocgenButton;
6 changes: 6 additions & 0 deletions examples/official-storybook/components/ForwardRefButton.js
@@ -0,0 +1,6 @@
import React from 'react';
import { DocgenButton } from './DocgenButton';

export const ForwardRefButton = React.forwardRef((props, ref) => (
<DocgenButton ref={ref} {...props} />
));
@@ -1,5 +1,5 @@
import React from 'react';
import DocgenButton from './DocgenButton';
import { DocgenButton } from './DocgenButton';

/** Button component description */
const ImportedPropsButton = ({ disabled, label, onClick }) => (
Expand Down
4 changes: 4 additions & 0 deletions examples/official-storybook/components/MemoButton.js
@@ -0,0 +1,4 @@
import React from 'react';
import { DocgenButton } from './DocgenButton';

export const MemoButton = React.memo(props => <DocgenButton {...props} />);
@@ -1,7 +1,7 @@
import React from 'react';
import notes from '../notes/notes.md';
import mdxNotes from '../notes/notes.mdx';
import DocgenButton from '../../components/DocgenButton';
import { DocgenButton } from '../../components/DocgenButton';

export default {
title: 'Addons|Docs/stories',
Expand Down
Expand Up @@ -11,7 +11,7 @@ import {
import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
import FlowTypeButton from '../../components/FlowTypeButton';
import DocgenButton from '../../components/DocgenButton';
import { DocgenButton } from '../../components/DocgenButton';

<Meta
title="Addons|Docs/mdx"
Expand Down

This file was deleted.

18 changes: 18 additions & 0 deletions examples/official-storybook/stories/addon-docs/props.stories.mdx
@@ -0,0 +1,18 @@
import { Meta, Props } from '@storybook/addon-docs/blocks';
import { DocgenButton } from '../../components/DocgenButton';
import { ForwardRefButton } from '../../components/ForwardRefButton';
import { MemoButton } from '../../components/MemoButton';

<Meta title="Addons|Docs/props" />

## Docgen

<Props of={DocgenButton} />

## React.forwardRef

<Props of={ForwardRefButton} />

## React.memo

<Props of={MemoButton} />

This file was deleted.

Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { withInfo } from '@storybook/addon-info';
import { action } from '@storybook/addon-actions';

import DocgenButton from '../../components/DocgenButton';
import { DocgenButton } from '../../components/DocgenButton';
import FlowTypeButton from '../../components/FlowTypeButton';
import BaseButton from '../../components/BaseButton';
import { NamedExportButton } from '../../components/NamedExportButton';
Expand Down
2 changes: 1 addition & 1 deletion examples/vue-kitchen-sink/.storybook/config.js
Expand Up @@ -14,7 +14,7 @@ addParameters({
hierarchyRootSeparator: /\|/,
},
docs: {
// inlineStories: true,
inlineStories: true,
iframeHeight: '60px',
},
});
Expand Down