diff --git a/__mocks__/inject-decorator.stories.txt b/__mocks__/inject-decorator.stories.txt index c4f0c4d53e5d..a8eb61d2f221 100644 --- a/__mocks__/inject-decorator.stories.txt +++ b/__mocks__/inject-decorator.stories.txt @@ -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'; diff --git a/addons/docs/README.md b/addons/docs/README.md index d46737f42cd3..323e5aeba01b 100644 --- a/addons/docs/README.md +++ b/addons/docs/README.md @@ -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: diff --git a/addons/docs/common/index.js b/addons/docs/common/index.js index ab2313695b6d..ac54d6be1af0 100644 --- a/addons/docs/common/index.js +++ b/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'); diff --git a/addons/docs/package.json b/addons/docs/package.json index b4a245fc254c..502f4b48de4c 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -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" diff --git a/addons/docs/src/blocks/Props.tsx b/addons/docs/src/blocks/Props.tsx index ab4769476adf..871c901cda65 100644 --- a/addons/docs/src/blocks/Props.tsx +++ b/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; } @@ -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 }; } diff --git a/addons/docs/src/frameworks/common/index.ts b/addons/docs/src/frameworks/common/index.ts index 5a17df8d247b..d4a985d89fab 100644 --- a/addons/docs/src/frameworks/common/index.ts +++ b/addons/docs/src/frameworks/common/index.ts @@ -1 +1 @@ -export * from '../../lib/getPropDefs'; +export * from '../../lib/propsUtils'; diff --git a/addons/docs/src/frameworks/react/config.js b/addons/docs/src/frameworks/react/config.js index 0fb8a2a764df..d8ba373774cd 100644 --- a/addons/docs/src/frameworks/react/config.js +++ b/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: { @@ -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, }, }); diff --git a/addons/docs/src/lib/getPropDefs.ts b/addons/docs/src/frameworks/react/extractProps.ts similarity index 52% rename from addons/docs/src/lib/getPropDefs.ts rename to addons/docs/src/frameworks/react/extractProps.ts index 6d591d204df0..fac66c418b49 100644 --- a/addons/docs/src/lib/getPropDefs.ts +++ b/addons/docs/src/frameworks/react/extractProps.ts @@ -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 => { @@ -20,30 +18,7 @@ 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) { @@ -51,7 +26,7 @@ const propsFromPropTypes: PropDefGetter = type => { 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'; @@ -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'), +}); diff --git a/addons/docs/src/frameworks/vue/config.js b/addons/docs/src/frameworks/vue/config.js index 6ca833d0e0d2..eab1a5836071 100644 --- a/addons/docs/src/frameworks/vue/config.js +++ b/addons/docs/src/frameworks/vue/config.js @@ -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: { @@ -12,5 +13,6 @@ addParameters({ const Story = toReact(storyFn()); return ; }, + extractProps, }, }); diff --git a/addons/docs/src/frameworks/vue/extractProps.ts b/addons/docs/src/frameworks/vue/extractProps.ts new file mode 100644 index 000000000000..b90d5b85f53e --- /dev/null +++ b/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 = {}; + SECTIONS.forEach(section => { + sections[section] = propsFromDocgen(component, section); + }); + return { sections }; +}; diff --git a/addons/docs/src/lib/propsUtils.ts b/addons/docs/src/lib/propsUtils.ts new file mode 100644 index 000000000000..1f75438a5a64 --- /dev/null +++ b/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 = {}; + 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); +}; diff --git a/addons/docs/src/typings.d.ts b/addons/docs/src/typings.d.ts index ab6a2ebd1cca..7250049e8436 100644 --- a/addons/docs/src/typings.d.ts +++ b/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'; diff --git a/examples/official-storybook/components/DocgenButton.js b/examples/official-storybook/components/DocgenButton.js index 9267c473b11d..3abebb2bf3a9 100644 --- a/examples/official-storybook/components/DocgenButton.js +++ b/examples/official-storybook/components/DocgenButton.js @@ -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 }) => ( @@ -145,5 +145,3 @@ DocgenButton.propTypes = { */ optionalString: PropTypes.string, }; - -export default DocgenButton; diff --git a/examples/official-storybook/components/ForwardRefButton.js b/examples/official-storybook/components/ForwardRefButton.js new file mode 100644 index 000000000000..70b7ee88f555 --- /dev/null +++ b/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) => ( + +)); diff --git a/examples/official-storybook/components/ImportedPropsButton.js b/examples/official-storybook/components/ImportedPropsButton.js index a394f33a777d..b8a1e7e88cd6 100644 --- a/examples/official-storybook/components/ImportedPropsButton.js +++ b/examples/official-storybook/components/ImportedPropsButton.js @@ -1,5 +1,5 @@ import React from 'react'; -import DocgenButton from './DocgenButton'; +import { DocgenButton } from './DocgenButton'; /** Button component description */ const ImportedPropsButton = ({ disabled, label, onClick }) => ( diff --git a/examples/official-storybook/components/MemoButton.js b/examples/official-storybook/components/MemoButton.js new file mode 100644 index 000000000000..98288ec6ab6f --- /dev/null +++ b/examples/official-storybook/components/MemoButton.js @@ -0,0 +1,4 @@ +import React from 'react'; +import { DocgenButton } from './DocgenButton'; + +export const MemoButton = React.memo(props => ); diff --git a/examples/official-storybook/stories/addon-docs/addon-docs.stories.js b/examples/official-storybook/stories/addon-docs/addon-docs.stories.js index 5cd3bd8892f2..6a2f2ae2e0fd 100644 --- a/examples/official-storybook/stories/addon-docs/addon-docs.stories.js +++ b/examples/official-storybook/stories/addon-docs/addon-docs.stories.js @@ -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', diff --git a/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx b/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx index b0a0a94ca1ba..c5d7a313653d 100644 --- a/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx @@ -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'; ); - -export default { - title: 'Addons|Docs/ForwardRef', - component: ForwardedButton, -}; - -export const displaysCorrectly = () => Hello World!; -displaysCorrectly.story = { name: 'Displays forwarded ref components correctly' }; diff --git a/examples/official-storybook/stories/addon-docs/props.stories.mdx b/examples/official-storybook/stories/addon-docs/props.stories.mdx new file mode 100644 index 000000000000..d6a03b4ee0ac --- /dev/null +++ b/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'; + + + +## Docgen + + + +## React.forwardRef + + + +## React.memo + + diff --git a/examples/official-storybook/stories/addon-docs/react-memo.stories.js b/examples/official-storybook/stories/addon-docs/react-memo.stories.js deleted file mode 100644 index 2acf981529e1..000000000000 --- a/examples/official-storybook/stories/addon-docs/react-memo.stories.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import DocgenButton from '../../components/DocgenButton'; - -const ButtonWithMemo = React.memo(props => ); - -export default { - title: 'Addons|Docs/ButtonWithMemo', - component: ButtonWithMemo, -}; - -export const displaysCorrectly = () => Hello World!; -displaysCorrectly.story = { name: 'Displays components with memo correctly' }; diff --git a/examples/official-storybook/stories/addon-info/react-docgen.stories.js b/examples/official-storybook/stories/addon-info/react-docgen.stories.js index 91400f9e18a4..a3002b07df2c 100644 --- a/examples/official-storybook/stories/addon-info/react-docgen.stories.js +++ b/examples/official-storybook/stories/addon-info/react-docgen.stories.js @@ -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'; diff --git a/examples/vue-kitchen-sink/.storybook/config.js b/examples/vue-kitchen-sink/.storybook/config.js index daf145445510..a62e64d421e5 100644 --- a/examples/vue-kitchen-sink/.storybook/config.js +++ b/examples/vue-kitchen-sink/.storybook/config.js @@ -14,7 +14,7 @@ addParameters({ hierarchyRootSeparator: /\|/, }, docs: { - // inlineStories: true, + inlineStories: true, iframeHeight: '60px', }, }); diff --git a/examples/vue-kitchen-sink/src/stories/Button.vue b/examples/vue-kitchen-sink/src/stories/Button.vue index 940ce7b11004..eb637acd722e 100644 --- a/examples/vue-kitchen-sink/src/stories/Button.vue +++ b/examples/vue-kitchen-sink/src/stories/Button.vue @@ -1,21 +1,42 @@ @@ -31,5 +52,4 @@ background-color: white; outline: none; } - \ No newline at end of file diff --git a/examples/vue-kitchen-sink/src/stories/Welcome.vue b/examples/vue-kitchen-sink/src/stories/Welcome.vue index 6c5258652043..d3ec62eac551 100644 --- a/examples/vue-kitchen-sink/src/stories/Welcome.vue +++ b/examples/vue-kitchen-sink/src/stories/Welcome.vue @@ -16,7 +16,7 @@

See these sample - stories + stories for a component called Button . @@ -55,9 +55,13 @@ + + + + \ No newline at end of file diff --git a/examples/vue-kitchen-sink/src/stories/components/__snapshots__/info-button.stories.storyshot b/examples/vue-kitchen-sink/src/stories/components/__snapshots__/info-button.stories.storyshot new file mode 100644 index 000000000000..edf9087332f7 --- /dev/null +++ b/examples/vue-kitchen-sink/src/stories/components/__snapshots__/info-button.stories.storyshot @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots InfoButton Simple 1`] = ` + +`; diff --git a/examples/vue-kitchen-sink/src/stories/components/info-button.stories.js b/examples/vue-kitchen-sink/src/stories/components/info-button.stories.js new file mode 100644 index 000000000000..fb62af728e0b --- /dev/null +++ b/examples/vue-kitchen-sink/src/stories/components/info-button.stories.js @@ -0,0 +1,11 @@ +import InfoButton from './InfoButton.vue'; + +export default { + title: 'InfoButton', + component: InfoButton, +}; + +export const simple = () => ({ + components: { InfoButton }, + template: '', +}); diff --git a/examples/vue-kitchen-sink/src/stories/components/welcome.stories.js b/examples/vue-kitchen-sink/src/stories/components/welcome.stories.js index d9eebaed48be..0f8a76a70bb0 100644 --- a/examples/vue-kitchen-sink/src/stories/components/welcome.stories.js +++ b/examples/vue-kitchen-sink/src/stories/components/welcome.stories.js @@ -9,6 +9,6 @@ export default { export const welcome = () => { return { - render: h => h(Welcome, { props: { goToButton: linkTo('Button') } }), + render: h => h(Welcome, { listeners: { buttonRequested: linkTo('Button') } }), }; }; diff --git a/examples/vue-kitchen-sink/src/stories/custom-rendering.stories.js b/examples/vue-kitchen-sink/src/stories/custom-rendering.stories.js index e7facaa8b8bf..e84d885fc626 100644 --- a/examples/vue-kitchen-sink/src/stories/custom-rendering.stories.js +++ b/examples/vue-kitchen-sink/src/stories/custom-rendering.stories.js @@ -44,7 +44,7 @@ export const templateMethods = () => ({ template: `

Clicking the button will navigate to another story using the 'addon-links'
- MyButton rendered in a template + props & methods + MyButton rendered in a template + props & methods

`, methods: { action: linkTo('Button'), @@ -65,7 +65,7 @@ export const JSX = () => ({ export const vuexActions = () => ({ components: { MyButton }, - template: 'with vuex: {{ $store.state.count }}', + template: 'with vuex: {{ $store.state.count }}', store: new Vuex.Store({ state: { count: 0 }, mutations: { @@ -88,7 +88,7 @@ vuexActions.story = { export const whateverYouWant = () => ({ components: { MyButton }, - template: 'with awesomeness: {{ $store.state.count }}', + template: 'with awesomeness: {{ $store.state.count }}', store: new Vuex.Store({ state: { count: 0 }, mutations: { diff --git a/lib/components/src/blocks/PropsTable/PropRow.tsx b/lib/components/src/blocks/PropsTable/PropRow.tsx index c88052055342..2f040a0c8eaa 100644 --- a/lib/components/src/blocks/PropsTable/PropRow.tsx +++ b/lib/components/src/blocks/PropsTable/PropRow.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from 'react'; +import React, { FC } from 'react'; import { styled } from '@storybook/theming'; import { transparentize } from 'polished'; import { PropDef } from './PropDef'; @@ -20,7 +20,7 @@ interface PrettyPropValProps { value: any; } -interface PropRowProps { +export interface PropRowProps { row: PropDef; // FIXME: row options } @@ -75,15 +75,15 @@ const prettyPrint = (type: any): string => { } }; -export const PrettyPropType: FunctionComponent = ({ type }) => ( +export const PrettyPropType: FC = ({ type }) => ( {prettyPrint(type)} ); -export const PrettyPropVal: FunctionComponent = ({ value }) => ( +export const PrettyPropVal: FC = ({ value }) => ( {JSON.stringify(value)} ); -export const PropRow: FunctionComponent = ({ +export const PropRow: FC = ({ row: { name, type, required, description, defaultValue }, }) => ( diff --git a/lib/components/src/blocks/PropsTable/PropsTable.stories.tsx b/lib/components/src/blocks/PropsTable/PropsTable.stories.tsx index 3a43436ae02b..9fa0e0032a1d 100644 --- a/lib/components/src/blocks/PropsTable/PropsTable.stories.tsx +++ b/lib/components/src/blocks/PropsTable/PropsTable.stories.tsx @@ -9,6 +9,10 @@ export default { export const normal = () => ; +export const sections = () => ( + +); + export const error = () => ; export const empty = () => ; diff --git a/lib/components/src/blocks/PropsTable/PropsTable.tsx b/lib/components/src/blocks/PropsTable/PropsTable.tsx index 9aa8c48a9df1..32612356ec1f 100644 --- a/lib/components/src/blocks/PropsTable/PropsTable.tsx +++ b/lib/components/src/blocks/PropsTable/PropsTable.tsx @@ -1,7 +1,8 @@ -import React, { FunctionComponent } from 'react'; +import React, { FC } from 'react'; import { styled } from '@storybook/theming'; -import { opacify, transparentize } from 'polished'; -import { PropRow } from './PropRow'; +import { opacify, transparentize, darken, lighten } from 'polished'; +import { PropRow, PropRowProps } from './PropRow'; +import { SectionRow, SectionRowProps } from './SectionRow'; import { PropDef } from './PropDef'; import { EmptyBlock } from '../EmptyBlock'; import { ResetWrapper } from '../../typography/DocumentFormatting'; @@ -77,19 +78,22 @@ export const Table = styled.table<{}>(({ theme }) => ({ marginLeft: 1, marginRight: 1, - 'tr:first-child td:first-child': { - borderTopLeftRadius: theme.appBorderRadius, - }, - - 'tr:first-child td:last-child': { - borderTopRightRadius: theme.appBorderRadius, - }, - 'tr:last-child td:first-child': { - borderBottomLeftRadius: theme.appBorderRadius, + 'tr:first-child': { + 'td:first-child, th:first-child': { + borderTopLeftRadius: theme.appBorderRadius, + }, + 'td:last-child, th:last-child': { + borderTopRightRadius: theme.appBorderRadius, + }, }, - 'tr:last-child td:last-child': { - borderBottomRightRadius: theme.appBorderRadius, + 'tr:last-child': { + 'td:first-child, th:first-child': { + borderBottomLeftRadius: theme.appBorderRadius, + }, + 'td:last-child, th:last-child': { + borderBottomRightRadius: theme.appBorderRadius, + }, }, tbody: { @@ -104,8 +108,14 @@ export const Table = styled.table<{}>(({ theme }) => ({ tr: { background: 'transparent', + overflow: 'hidden', '&:not(:first-child)': { - borderTop: `1px solid ${theme.appBorderColor}`, + borderTopWidth: 1, + borderTopStyle: 'solid', + borderTopColor: + theme.base === 'light' + ? darken(0.1, theme.background.content) + : lighten(0.05, theme.background.content), }, }, @@ -126,24 +136,60 @@ export interface PropsTableRowsProps { rows: PropDef[]; } +export interface PropsTableSectionsProps { + sections?: Record; +} + export interface PropsTableErrorProps { error: PropsTableError; } -export type PropsTableProps = PropsTableRowsProps | PropsTableErrorProps; +export type PropsTableProps = PropsTableRowsProps | PropsTableSectionsProps | PropsTableErrorProps; + +const PropsTableRow: FC = props => { + const { section } = props as SectionRowProps; + if (section) { + return ; + } + const { row } = props as PropRowProps; + return ; +}; /** * Display the props for a component as a props table. Each row is a collection of * PropDefs, usually derived from docgen info for the component. */ -const PropsTable: FunctionComponent = props => { +const PropsTable: FC = props => { const { error } = props as PropsTableErrorProps; if (error) { return {error}; } - const { rows } = props as PropsTableRowsProps; - if (rows.length === 0) { + let allRows: any[]; + const { sections } = props as PropsTableSectionsProps; + if (sections) { + allRows = []; + Object.keys(sections).forEach(section => { + const rows = sections[section]; + if (rows && rows.length > 0) { + allRows.push({ key: section, value: { section } }); + rows.forEach(row => { + allRows.push({ + key: `${section}_${row.name}`, + value: { row }, + }); + }); + } + }); + } else { + const { rows } = props as PropsTableRowsProps; + allRows = rows.map(row => ({ + key: row.name, + value: { row }, + })); + } + + if (allRows.length === 0) { return No props found for this component; } return ( @@ -157,8 +203,8 @@ const PropsTable: FunctionComponent = props => { - {rows.map(row => ( - + {allRows.map(row => ( + ))} diff --git a/lib/components/src/blocks/PropsTable/SectionRow.stories.tsx b/lib/components/src/blocks/PropsTable/SectionRow.stories.tsx new file mode 100644 index 000000000000..1113e2609aa5 --- /dev/null +++ b/lib/components/src/blocks/PropsTable/SectionRow.stories.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { SectionRow } from './SectionRow'; +import { Table } from './PropsTable'; +import { ResetWrapper } from '../../typography/DocumentFormatting'; + +export default { + component: SectionRow, + title: 'Docs|SectionRow', + decorators: [ + getStory => ( + + + {getStory()} +
+
+ ), + ], +}; + +export const props = () => ; diff --git a/lib/components/src/blocks/PropsTable/SectionRow.tsx b/lib/components/src/blocks/PropsTable/SectionRow.tsx new file mode 100644 index 000000000000..faebc6651cf8 --- /dev/null +++ b/lib/components/src/blocks/PropsTable/SectionRow.tsx @@ -0,0 +1,26 @@ +import React, { FC } from 'react'; +import { transparentize } from 'polished'; +import { styled } from '@storybook/theming'; + +export interface SectionRowProps { + section: string; +} + +const SectionTh = styled.th<{}>(({ theme }) => ({ + letterSpacing: '0.35em', + textTransform: 'uppercase', + fontWeight: theme.typography.weight.black, + fontSize: theme.typography.size.s1 - 1, + lineHeight: '24px', + color: + theme.base === 'light' + ? transparentize(0.4, theme.color.defaultText) + : transparentize(0.6, theme.color.defaultText), + background: `${theme.background.app} !important`, +})); + +export const SectionRow: FC = ({ section }) => ( + + {section} + +); diff --git a/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.test.js.snap b/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.test.js.snap index 43d321042705..5d08bb94bf33 100644 --- a/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.test.js.snap +++ b/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.test.js.snap @@ -99,7 +99,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'; @@ -654,7 +654,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';