diff --git a/examples/storybook-6-no-docs/.storybook/main.js b/examples/storybook-6-no-docs/.storybook/main.js index d68e6fc10..7bfe231f1 100644 --- a/examples/storybook-6-no-docs/.storybook/main.js +++ b/examples/storybook-6-no-docs/.storybook/main.js @@ -12,10 +12,11 @@ module.exports = { }, ], stories: [ - //'../../../ui/editors/src/**/*.stories.(js|tsx|mdx)', - //'../../../ui/components/src/**/*.stories.(js|tsx|mdx)', - //'../../../ui/blocks/src/**/*.stories.(js|tsx|mdx)', - '../../stories/src/stories/smart-controls.stories.mdx', + '../../../ui/editors/src/**/*.stories.(js|tsx|mdx)', + '../../../ui/components/src/**/*.stories.(js|tsx|mdx)', + '../../../ui/blocks/src/**/*.stories.(js|tsx|mdx)', + '../../stories/src/**/*.stories.(js|tsx|mdx)', + '../stories/**/*.stories.(js|tsx|mdx)', ], webpackFinal: async (config, { configType }) => { return { diff --git a/examples/storybook-6-no-docs/.storybook/preview.js b/examples/storybook-6-no-docs/.storybook/preview.js index 60fdb5814..dc1886472 100644 --- a/examples/storybook-6-no-docs/.storybook/preview.js +++ b/examples/storybook-6-no-docs/.storybook/preview.js @@ -1,37 +1,17 @@ import React from 'react' import { addDecorator, addParameters } from '@storybook/react'; -import { ThemeProvider, BlockContextProvider } from '@component-controls/storybook'; -import { ControlsTable, Title, Subtitle, Story, Playground, Stories, Description, ComponentSource, PropsTable } from '@component-controls/blocks'; +import { ThemeProvider, useIsDark } from '@component-controls/storybook'; addDecorator((story, ctx ) => { + const isDark = useIsDark(); return ( - + {story(ctx)} ); }) - -export const DocsPage = () => { - return ( - - - - <Subtitle /> - <Description /> - <ComponentSource id="." title='Component' /> - <Playground openTab="source" title="."> - <Story id="." /> - </Playground> - <ControlsTable id="." /> - <PropsTable of="." /> - <Stories dark={true}/> - </BlockContextProvider> - </ThemeProvider> - ); -}; const categories = ['Storybook', 'Blocks', 'Editors', 'Components'] addParameters({ - docs_xxx: { page: DocsPage }, dependencies: { hideEmpty: true }, options: { storySort: (a, b) => { diff --git a/examples/storybook-6-no-docs/package.json b/examples/storybook-6-no-docs/package.json index 16da4cfde..cef622aad 100644 --- a/examples/storybook-6-no-docs/package.json +++ b/examples/storybook-6-no-docs/package.json @@ -16,7 +16,6 @@ "@storybook/addon-docs": "next", "@storybook/addon-storysource": "next", "@storybook/react": "next", - "@storybook/theming": "next", "babel-loader": "^8.0.6", "babel-preset-react-app": "^9.0.0", "cross-env": "^5.2.0", diff --git a/examples/stories/src/stories/smart-controls.stories.mdx b/examples/storybook-6-no-docs/stories/smart-controls.stories.mdx similarity index 77% rename from examples/stories/src/stories/smart-controls.stories.mdx rename to examples/storybook-6-no-docs/stories/smart-controls.stories.mdx index a3b5c906a..d9811cbad 100644 --- a/examples/stories/src/stories/smart-controls.stories.mdx +++ b/examples/storybook-6-no-docs/stories/smart-controls.stories.mdx @@ -1,9 +1,10 @@ -import { Meta, PropsTable, Playground, Story, BlockContextProvider, ControlsTable, ComponentSource, StorySource } from '@component-controls/storybook'; -import { Button } from '../components/Button'; - +import { Meta} from '@component-controls/storybook'; +import { PropsTable, Playground, Story, ControlsTable, ComponentSource, StorySource } from '@component-controls/blocks'; +import { Button } from '../../stories/src/components/Button'; <Meta title="Storybook/MDX" parameters={{component: Button}} /> + # Smart controls <ComponentSource of={Button} title='Component source' /> diff --git a/examples/storybook-6/.storybook/main.js b/examples/storybook-6/.storybook/main.js index 7ac6b535e..d2cb0ca55 100644 --- a/examples/storybook-6/.storybook/main.js +++ b/examples/storybook-6/.storybook/main.js @@ -22,6 +22,7 @@ module.exports = { '../../../ui/components/src/**/*.stories.(js|tsx|mdx)', '../../../ui/blocks/src/**/*.stories.(js|tsx|mdx)', '../../stories/src/**/*.stories.(js|tsx|mdx)', + '../stories/**/*.stories.(js|tsx|mdx)', ], addons: [ '@storybook/addon-docs', diff --git a/examples/storybook-6/.storybook/preview.js b/examples/storybook-6/.storybook/preview.js index 60fdb5814..57ad35dbc 100644 --- a/examples/storybook-6/.storybook/preview.js +++ b/examples/storybook-6/.storybook/preview.js @@ -1,37 +1,18 @@ import React from 'react' import { addDecorator, addParameters } from '@storybook/react'; -import { ThemeProvider, BlockContextProvider } from '@component-controls/storybook'; -import { ControlsTable, Title, Subtitle, Story, Playground, Stories, Description, ComponentSource, PropsTable } from '@component-controls/blocks'; +import { ThemeProvider, useIsDark } from '@component-controls/storybook'; addDecorator((story, ctx ) => { + const isDark = useIsDark(); return ( - <ThemeProvider> + <ThemeProvider dark={isDark}> {story(ctx)} </ThemeProvider> ); }) -export const DocsPage = () => { - return ( - <ThemeProvider> - <BlockContextProvider> - <Title /> - <Subtitle /> - <Description /> - <ComponentSource id="." title='Component' /> - <Playground openTab="source" title="."> - <Story id="." /> - </Playground> - <ControlsTable id="." /> - <PropsTable of="." /> - <Stories dark={true}/> - </BlockContextProvider> - </ThemeProvider> - ); -}; const categories = ['Storybook', 'Blocks', 'Editors', 'Components'] addParameters({ - docs_xxx: { page: DocsPage }, dependencies: { hideEmpty: true }, options: { storySort: (a, b) => { diff --git a/examples/storybook-6/package.json b/examples/storybook-6/package.json index 0b0e521aa..c99aea90b 100644 --- a/examples/storybook-6/package.json +++ b/examples/storybook-6/package.json @@ -16,7 +16,6 @@ "@storybook/addon-docs": "next", "@storybook/addon-storysource": "next", "@storybook/react": "next", - "@storybook/theming": "next", "babel-loader": "^8.0.6", "babel-preset-react-app": "^9.0.0", "cross-env": "^5.2.0", diff --git a/examples/storybook-6/stories/smart-controls.stories.mdx b/examples/storybook-6/stories/smart-controls.stories.mdx new file mode 100644 index 000000000..d5f3d4b4a --- /dev/null +++ b/examples/storybook-6/stories/smart-controls.stories.mdx @@ -0,0 +1,45 @@ +import { Meta, PropsTable, Playground as Preview, Story, BlockContextProvider, ControlsTable, ComponentSource, StorySource } from '@component-controls/storybook'; +import { Button } from '../../stories/src/components/Button'; + +<Meta title="Storybook/MDX" parameters={{component: Button}} /> + + +# Smart controls +<ComponentSource of={Button} title='Component source' /> + +<PropsTable of={Button} /> + + +<Preview> + <Story name="smart story"> + {(props) => ( + <Button label="default" {...props}/> + )} + </Story> +</Preview> + + +# Small story with custom controls + +<Preview> + <Story name="small story" + controls={{ + text: {type: 'text', value: 'Hello'}, + }} + > + {({ text }) => ( + <Button label={text}/> + )} + </Story> +</Preview> + + +<ControlsTable name="small story" /> +# Story with no parameters == no smart controls + +<Preview> + <Story name="no controls"> + <Button label="Hello"/> + <Button label="Second button"/> + </Story> +</Preview> diff --git a/integrations/storybook/README.md b/integrations/storybook/README.md index 5e534dd47..2cd4fd0bc 100644 --- a/integrations/storybook/README.md +++ b/integrations/storybook/README.md @@ -1,7 +1,3 @@ -```diff -- WARNING - THIS ADDON CAN NOT YET BE USED AS SOME OF THE STORYBOOK FEATURES IT REQUIRES ARE NOT YET MERGED -``` - ## Table of contents * [Storybook](#storybook) @@ -32,7 +28,7 @@ ## Storybook -The Storybook integration of the component-controls (aka "Addon Controls") allows you to define and then edit story properties dynamically in the [Storybook](https://storybook.js.org) UI with a set of property editors. +The Storybook integration of component-controls allows you to define and then edit story properties dynamically in the [Storybook](https://storybook.js.org) UI with a set of property editors. The definitions of the control properties can be found [here](https://github.com/ccontrols/component-controls/blob/master/core/specification/src/types.ts): diff --git a/integrations/storybook/package.json b/integrations/storybook/package.json index 4d287628b..5f62f68dc 100644 --- a/integrations/storybook/package.json +++ b/integrations/storybook/package.json @@ -51,7 +51,6 @@ "@storybook/api": "next", "@storybook/client-api": "next", "@storybook/react": "next", - "@storybook/theming": "next", "@types/node": "^13.7.0", "babel-loader": "^8.0.6", "babel-preset-react-app": "^9.0.0" @@ -62,7 +61,6 @@ "@storybook/api": "*", "@storybook/client-api": "*", "@storybook/react": "*", - "@storybook/theming": "*", "polished": "*", "react": "*" }, diff --git a/integrations/storybook/src/blocks/ControlsTable.tsx b/integrations/storybook/src/blocks/ControlsTable.tsx index cf3caeabd..5ed97636f 100644 --- a/integrations/storybook/src/blocks/ControlsTable.tsx +++ b/integrations/storybook/src/blocks/ControlsTable.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { - ControlsTable as BaseControlsTable, + ControlsTable as BlockControlsTable, ControlsTableProps, useStoryContext, } from '@component-controls/blocks'; @@ -24,7 +24,7 @@ export const ControlsTable: FC<ControlsTableProps> = ({ return id ? ( <ThemeProvider> - <BaseControlsTable id={id} {...rest} /> + <BlockControlsTable id={id} {...rest} /> </ThemeProvider> ) : null; }; diff --git a/integrations/storybook/src/context/ThemeProvider.tsx b/integrations/storybook/src/context/ThemeProvider.tsx index 644e8448c..e4833fa3e 100644 --- a/integrations/storybook/src/context/ThemeProvider.tsx +++ b/integrations/storybook/src/context/ThemeProvider.tsx @@ -1,15 +1,13 @@ import React from 'react'; -import { getLuminance } from 'polished'; -import { ThemeContext, Theme } from '@storybook/theming'; import { ThemeProvider as ThemeUIProvider } from '@component-controls/components'; +import { BlockContextProvider } from './BlockContext'; +import { useIsDark } from './useIsDark'; export const ThemeProvider: React.FC = ({ children }) => { - const { background: { content = '#ffffff' } = {} } = React.useContext<Theme>( - ThemeContext as any, - ); + const isDark = useIsDark(); return ( - <ThemeUIProvider dark={getLuminance(content) < 0.5}> - {children} + <ThemeUIProvider dark={isDark}> + <BlockContextProvider>{children}</BlockContextProvider> </ThemeUIProvider> ); }; diff --git a/integrations/storybook/src/context/index.ts b/integrations/storybook/src/context/index.ts index 09bacefc2..82cd2e0a8 100644 --- a/integrations/storybook/src/context/index.ts +++ b/integrations/storybook/src/context/index.ts @@ -1,2 +1,3 @@ export * from './BlockContext'; export * from './ThemeProvider'; +export * from './useIsDark'; diff --git a/integrations/storybook/src/context/useIsDark.tsx b/integrations/storybook/src/context/useIsDark.tsx new file mode 100644 index 000000000..2b702fd0b --- /dev/null +++ b/integrations/storybook/src/context/useIsDark.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); + +export const useIsDark = (): boolean => { + const [isDark, setIsDark] = React.useState(prefersDark.matches); + React.useEffect(() => { + //inspired from: https://github.com/hipstersmoothie/storybook-dark-mode/blob/46476b4e96a5b310f13e7dccc69e2baa5e477814/src/Tool.tsx#L111 + const prefersDarkUpdate = (event: MediaQueryListEvent) => { + setIsDark(event.matches); + }; + prefersDark.addListener(prefersDarkUpdate); + + return () => { + prefersDark.removeListener(prefersDarkUpdate); + }; + }); + return isDark; +}; diff --git a/integrations/storybook/src/page/PageContainer.tsx b/integrations/storybook/src/page/PageContainer.tsx index 933068eb9..a88bd0d35 100644 --- a/integrations/storybook/src/page/PageContainer.tsx +++ b/integrations/storybook/src/page/PageContainer.tsx @@ -1,12 +1,11 @@ import React, { FC } from 'react'; -import { getLuminance } from 'polished'; -import { ThemeContext, Theme } from '@storybook/theming'; import { addons } from '@storybook/addons'; import { STORY_CHANGED } from '@storybook/core-events'; import { PageContainer as BlockPageContainer, PageContainerProps, } from '@component-controls/blocks'; +import { useIsDark } from '../context/useIsDark'; export const PageContextContainer: FC<PageContainerProps> = ({ children, @@ -15,10 +14,8 @@ export const PageContextContainer: FC<PageContainerProps> = ({ const [storyId, setStoryId] = React.useState<string | undefined>( defaultStoryId, ); + const isDark = useIsDark(); const channel = React.useMemo(() => addons.getChannel(), []); - const { background: { content = '#ffffff' } = {} } = React.useContext<Theme>( - ThemeContext as any, - ); React.useEffect(() => { const onStoryChange = (id: string) => { @@ -31,7 +28,7 @@ export const PageContextContainer: FC<PageContainerProps> = ({ }; }, []); return ( - <BlockPageContainer dark={getLuminance(content) < 0.5} storyId={storyId}> + <BlockPageContainer dark={isDark} storyId={storyId}> {children} </BlockPageContainer> ); diff --git a/ui/blocks/package.json b/ui/blocks/package.json index 6922b415e..e7e99da1d 100644 --- a/ui/blocks/package.json +++ b/ui/blocks/package.json @@ -35,6 +35,7 @@ "@component-controls/editors": "^0.6.0", "@component-controls/specification": "^0.6.0", "@component-controls/store": "^0.6.0", + "@mdx-js/react": "^1.5.8", "@storybook/csf": "^0.0.1", "copy-to-clipboard": "^3.2.1", "global": "^4.3.2", @@ -47,7 +48,9 @@ "theme-ui": "^0.3.1" }, "devDependencies": { + "@theme-ui/presets": "^0.3.0", "@types/jest": "^25.1.2", + "@types/mdx-js__react":"^1.5.1", "@types/theme-ui": "^0.3.0", "@types/qs": "^6.9.1", "cross-env": "^5.2.1", diff --git a/ui/blocks/src/BlockContainer/story/StoryBlockContainer.tsx b/ui/blocks/src/BlockContainer/story/StoryBlockContainer.tsx index 1d2b559fc..60b99bd43 100644 --- a/ui/blocks/src/BlockContainer/story/StoryBlockContainer.tsx +++ b/ui/blocks/src/BlockContainer/story/StoryBlockContainer.tsx @@ -33,10 +33,9 @@ export const StoryBlockContainer: FC<StoryBlockContainerProps> = ({ id, name, }); - const { component, kind, story } = context; + const { story } = context; const title = getStoryBlockTitle({ - kind, - component, + story, title: userTitle, }); const block = children(context, rest); diff --git a/ui/blocks/src/ComponentSource/ComponentSource.tsx b/ui/blocks/src/ComponentSource/ComponentSource.tsx index 3d319835e..17ddaf180 100644 --- a/ui/blocks/src/ComponentSource/ComponentSource.tsx +++ b/ui/blocks/src/ComponentSource/ComponentSource.tsx @@ -51,7 +51,7 @@ export const ComponentSource: FC<ComponentSourceProps> = ({ if (component && component.source) { allActions.push({ - title: showFileSource ? 'import' : 'component', + title: showFileSource ? 'import' : 'source', onClick: onShowFileSource, }); } diff --git a/ui/blocks/src/PageContainer/PageContainer.stories.tsx b/ui/blocks/src/PageContainer/PageContainer.stories.tsx new file mode 100644 index 000000000..95fb5fb2f --- /dev/null +++ b/ui/blocks/src/PageContainer/PageContainer.stories.tsx @@ -0,0 +1,51 @@ +/* eslint-disable react/display-name */ +import React from 'react'; +import { future } from '@theme-ui/presets'; +import { PageContainer } from '.'; +import { Title } from '../Title'; +import { PropsTable } from '../PropsTable'; +import { Playground } from '../Playground'; +import { Story } from '../Story'; + +import { storyStore } from '../test/storyStore'; + +export default { + title: 'Blocks/PageContainer', + component: PageContainer, +}; + +export const overview = () => ( + <PageContainer mockStore={storyStore} storyId="id-of-story"> + <Title /> + </PageContainer> +); + +export const dark = () => ( + <PageContainer dark={true} mockStore={storyStore} storyId="id-of-story"> + <Title /> + <Playground title="Custom playground title" openTab="source"> + <Story id="." /> + </Playground> + <PropsTable /> + </PageContainer> +); + +export const theme = () => ( + <PageContainer theme={future} mockStore={storyStore} storyId="id-of-story"> + <Title /> + <Playground title="Custom playground title" openTab="source"> + <Story id="." /> + </Playground> + <PropsTable /> + </PageContainer> +); + +export const components = () => ( + <PageContainer + mockStore={storyStore} + storyId="mdx-story" + components={{ h1: props => <div {...props} /> }} + > + <Title /> + </PageContainer> +); diff --git a/ui/blocks/src/PageContainer/PageContainer.tsx b/ui/blocks/src/PageContainer/PageContainer.tsx index 8b2b9aa81..f7541d6c6 100644 --- a/ui/blocks/src/PageContainer/PageContainer.tsx +++ b/ui/blocks/src/PageContainer/PageContainer.tsx @@ -1,5 +1,14 @@ -import React, { FC } from 'react'; -import { ThemeProvider } from '@component-controls/components'; +/* eslint-disable react/display-name */ +/** @jsx jsx */ +import { FC } from 'react'; +import { jsx, Box, Theme } from 'theme-ui'; +import { MDXProvider, MDXProviderComponents } from '@mdx-js/react'; +import { StoryStore } from '@component-controls/store'; + +import { + ThemeProvider, + markdownComponents, +} from '@component-controls/components'; import { BlockContextProvider, StoryContextConsumer } from '../context'; export interface PageContainerProps { @@ -14,40 +23,63 @@ export interface PageContainerProps { /** * components to customize the markdown display. */ - components?: any; + components?: MDXProviderComponents; + + /** + * optional custom theme + */ + theme?: Theme; + + /** + * mock store for tests + */ + mockStore?: StoryStore; } /** * - * Page container. - * if an MDX page, will display the MDX components - * otherwise, the page elemenst are passed as children + * If the page is an MDX page, will display the MDX components. + * Otherwise, the page elements are passed as children */ export const PageContainer: FC<PageContainerProps> = ({ children, dark, storyId, + mockStore, + theme, + components = {}, }) => { return storyId ? ( - <div - style={{ - display: 'flex', - justifyContent: 'center', - padding: '4rem 20px', - }} - > - <div style={{ maxWidth: '1000px', width: '100%' }}> - <ThemeProvider dark={dark}> - <BlockContextProvider storyId={storyId}> + <ThemeProvider theme={theme} dark={dark}> + <Box + sx={{ + display: 'flex', + justifyContent: 'center', + padding: '4rem 20px', + bg: 'background', + color: 'text', + fontFamily: 'body', + }} + > + <Box sx={{ maxWidth: '1000px', width: '100%' }}> + <BlockContextProvider storyId={storyId} mockStore={mockStore}> <StoryContextConsumer id={storyId}> {({ kind }) => { const { MDXPage } = kind || {}; - return MDXPage ? <MDXPage /> : children; + return MDXPage ? ( + <MDXProvider + components={{ ...markdownComponents, ...components }} + > + <MDXPage /> + </MDXProvider> + ) : ( + children + ); }} </StoryContextConsumer> </BlockContextProvider> - </ThemeProvider> - </div> - </div> + </Box> + </Box> + </ThemeProvider> ) : null; }; diff --git a/ui/blocks/src/Playground/Playground.tsx b/ui/blocks/src/Playground/Playground.tsx index e3e00dc3f..6d137066b 100644 --- a/ui/blocks/src/Playground/Playground.tsx +++ b/ui/blocks/src/Playground/Playground.tsx @@ -14,7 +14,7 @@ import { Zoom, } from '@component-controls/components'; -import { Button } from 'theme-ui'; +import { Button, useThemeUI } from 'theme-ui'; import { StoryBlockContainer, StoryBlockContainerProps, @@ -54,10 +54,13 @@ export const Playground: FC<PlaygroundProps> = ({ const [tabsIndex, setTabsIndex] = React.useState<number | undefined>( undefined, ); + const { theme } = useThemeUI(); const [scale, setScale] = React.useState(userScale); React.useEffect(() => setScale(userScale), [userScale]); let storyId: string; const childArr = React.Children.toArray(children); + const isDark = + dark === undefined ? theme.initialColorModeName === 'dark' : dark; if (childArr.length === 1) { //@ts-ignore storyId = childArr[0].props.id; @@ -66,7 +69,7 @@ export const Playground: FC<PlaygroundProps> = ({ id: 'source', 'aria-label': 'display story source code', panel: ( - <StorySource dark={dark} sxStyle={{ mt: 0, mb: 0 }} id={storyId} /> + <StorySource dark={isDark} sxStyle={{ mt: 0, mb: 0 }} id={storyId} /> ), }); } diff --git a/ui/blocks/src/PropsTable/PropsTable.tsx b/ui/blocks/src/PropsTable/PropsTable.tsx index 7126c2496..030d38188 100644 --- a/ui/blocks/src/PropsTable/PropsTable.tsx +++ b/ui/blocks/src/PropsTable/PropsTable.tsx @@ -203,6 +203,7 @@ export const PropsTable: FC<PropsTableProps> = ({ flexDirection: 'column', alignItems: 'left', flexBasis: '100%', + minWidth: 200, }} > <InputType diff --git a/ui/blocks/src/Stories/Stories.tsx b/ui/blocks/src/Stories/Stories.tsx index 723627bdc..677a6a2dd 100644 --- a/ui/blocks/src/Stories/Stories.tsx +++ b/ui/blocks/src/Stories/Stories.tsx @@ -41,6 +41,7 @@ export const Stories: FC<StoriesProps> = props => ( }, }} title="." + id={id} collapsible={false} key={`playground-${id}`} {...rest} diff --git a/ui/blocks/src/context/block/BlockContext.tsx b/ui/blocks/src/context/block/BlockContext.tsx index b8e871613..3719f15fb 100644 --- a/ui/blocks/src/context/block/BlockContext.tsx +++ b/ui/blocks/src/context/block/BlockContext.tsx @@ -34,18 +34,18 @@ export const BlockContextProvider: React.FC<BlockContextInputProps> = ({ }) => { const storeProvider = mockStore || storyStore; return ( - <BlockDataContextProvider store={storeProvider}> - <BlockControlsContextProvider store={storeProvider}> - <BlockContext.Provider - value={{ - storyId, - storeProvider, - }} - > + <BlockContext.Provider + value={{ + storyId, + storeProvider, + }} + > + <BlockDataContextProvider store={storeProvider} storyId={storyId}> + <BlockControlsContextProvider store={storeProvider}> {children} - </BlockContext.Provider> - </BlockControlsContextProvider> - </BlockDataContextProvider> + </BlockControlsContextProvider> + </BlockDataContextProvider> + </BlockContext.Provider> ); }; diff --git a/ui/blocks/src/context/block/BlockDataContext.tsx b/ui/blocks/src/context/block/BlockDataContext.tsx index 2e688401a..6d8ace814 100644 --- a/ui/blocks/src/context/block/BlockDataContext.tsx +++ b/ui/blocks/src/context/block/BlockDataContext.tsx @@ -37,18 +37,19 @@ export const BlockDataContext = React.createContext<BlockDataContextProps>({}); export interface BlockDataContextInoutProps { store: StoryStore; + storyId: string; } export const BlockDataContextProvider: React.FC<BlockDataContextInoutProps> = ({ children, + storyId, store: storeProvider, }) => { const store: StoriesStore | undefined = storeProvider.getStore(); - const getStoryData = (id?: string) => { + const getStoryData = (id: string = storyId) => { const story: Story | undefined = store && store.stories && id ? store.stories[id] : undefined; - const kind = store && story && story.kind ? store.kinds[story.kind] : undefined; const storyComponent: any = diff --git a/ui/blocks/src/test/MDXStory.tsx b/ui/blocks/src/test/MDXStory.tsx new file mode 100644 index 000000000..b4998c746 --- /dev/null +++ b/ui/blocks/src/test/MDXStory.tsx @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import React from 'react'; +/* @jsx mdx */ +import { mdx } from '@mdx-js/react'; + +const layoutProps = {}; + +const MDXLayout = 'wrapper'; +//@ts-ignore +export function MDXContent({ components, ...props }) { + return ( + //@ts-ignore + <MDXLayout {...layoutProps} {...props} components={components}> + <h1>{`Smart controls`}</h1> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; diff --git a/ui/blocks/src/test/storyStore.tsx b/ui/blocks/src/test/storyStore.tsx index bb011d848..426163d55 100644 --- a/ui/blocks/src/test/storyStore.tsx +++ b/ui/blocks/src/test/storyStore.tsx @@ -1,8 +1,9 @@ /* eslint-disable react/display-name */ import React from 'react'; -import { StoryStore, Store } from '@component-controls/store'; +import { Store } from '@component-controls/store'; import { StoriesStore, ControlTypes } from '@component-controls/specification'; import { Donut, Button, Heading } from 'theme-ui'; +import { MDXContent } from './MDXStory'; const store: StoriesStore = { components: { @@ -243,6 +244,13 @@ and a [link](https://google.com) 'blocks-core-story-plain--controls', ], }, + mdxStory: { + name: 'MDX Story', + title: 'MDX Story', + components: {}, + MDXPage: () => <MDXContent components={{}} />, + stories: ['mdx-story'], + }, }, stories: { 'id-of-story': { @@ -414,6 +422,13 @@ and a [link](https://google.com) name: 'no component', source: "() => 'hello'", }, + 'mdx-story': { + renderFn: () => <Heading>mdx story</Heading>, + id: 'mdx-story', + kind: 'mdxStory', + name: 'mdx story', + source: "() => 'hello'", + }, }, }; export const storyStore = new Store({ store, updateLocalStorage: false }); diff --git a/ui/blocks/src/typings.d.ts b/ui/blocks/src/typings.d.ts index 25d81d7ec..704ab8e9d 100644 --- a/ui/blocks/src/typings.d.ts +++ b/ui/blocks/src/typings.d.ts @@ -1,3 +1,4 @@ declare module '@component-controls/loader/story-store-data'; declare module 'global'; +declare module '@theme-ui/presets'; declare module 'js-string-escape'; diff --git a/ui/blocks/src/utils/constants.ts b/ui/blocks/src/utils/constants.ts index 907322c63..167c60f30 100644 --- a/ui/blocks/src/utils/constants.ts +++ b/ui/blocks/src/utils/constants.ts @@ -1,4 +1,8 @@ -import { StoriesKind, StoryComponent } from '@component-controls/specification'; +import { + StoriesKind, + StoryComponent, + Story, +} from '@component-controls/specification'; export const CURRENT_STORY = '.'; @@ -16,12 +20,9 @@ export const getStoryTitle = ( }; export const getStoryBlockTitle = ({ - kind, - component, + story, title, }: { - kind?: StoriesKind; - component?: StoryComponent; + story?: Story; title?: string; -}): string | undefined => - title == CURRENT_STORY ? getStoryTitle(kind, component) : title; +}): string | undefined => (title == CURRENT_STORY ? story?.name : title); diff --git a/ui/components/src/Markdown/Markdown.tsx b/ui/components/src/Markdown/Markdown.tsx index 7689e2b9b..f7bc9422a 100644 --- a/ui/components/src/Markdown/Markdown.tsx +++ b/ui/components/src/Markdown/Markdown.tsx @@ -1,9 +1,9 @@ /* eslint-disable react/display-name */ /** @jsx jsx */ import { FC } from 'react'; -import { jsx, Box } from 'theme-ui'; +import { jsx } from 'theme-ui'; import MarkdownToJSX, { MarkdownOptions } from 'markdown-to-jsx'; -import { SyntaxHighlighter } from '../SyntaxHighlighter'; +import { markdownComponents } from './MarokdownComponents'; export interface MarkdownProps { /** @@ -16,15 +16,6 @@ export interface MarkdownProps { components?: MarkdownOptions['overrides']; } -const defaultComponents: MarkdownOptions['overrides'] = { - code: SyntaxHighlighter, - p: ({ children }) => ( - <Box as="p" sx={{ my: 2 }}> - {children} - </Box> - ), -}; - /** * Markdown display component to compile and display markdown at run-time. * Uses `markdown-to-jsx` to compile the markdown. @@ -33,7 +24,7 @@ export const Markdown: FC<MarkdownProps> = ({ children, components }) => ( <MarkdownToJSX options={{ forceBlock: true, - overrides: { ...defaultComponents, ...components }, + overrides: { ...markdownComponents, ...components }, }} > {children} diff --git a/ui/components/src/Markdown/MarokdownComponents.tsx b/ui/components/src/Markdown/MarokdownComponents.tsx new file mode 100644 index 000000000..c590d508e --- /dev/null +++ b/ui/components/src/Markdown/MarokdownComponents.tsx @@ -0,0 +1,17 @@ +/* eslint-disable react/display-name */ +/** @jsx jsx */ +import { ComponentType } from 'react'; +import { jsx, Box } from 'theme-ui'; +import { SyntaxHighlighter } from '../SyntaxHighlighter'; + +export interface MarkdownComponentType { + [key: string]: ComponentType<any>; +} +export const markdownComponents: MarkdownComponentType = { + code: SyntaxHighlighter, + p: ({ children }) => ( + <Box as="p" sx={{ my: 2 }}> + {children} + </Box> + ), +}; diff --git a/ui/components/src/Markdown/index.ts b/ui/components/src/Markdown/index.ts index a5b9f6efd..2130a784f 100644 --- a/ui/components/src/Markdown/index.ts +++ b/ui/components/src/Markdown/index.ts @@ -1 +1,2 @@ export * from './Markdown'; +export * from './MarokdownComponents'; diff --git a/ui/components/src/Tabs/Tabs.tsx b/ui/components/src/Tabs/Tabs.tsx index aee8adce8..1f1d7c45b 100644 --- a/ui/components/src/Tabs/Tabs.tsx +++ b/ui/components/src/Tabs/Tabs.tsx @@ -48,8 +48,8 @@ const TabsContainer = styled.div` color: ${theme?.colors?.fadedText}; } .react-tabs__tab--selected { - border-bottom: 3px solid ${theme?.colors?.accent}; - color: ${theme?.colors?.accent}; + border-bottom: 3px solid ${theme?.colors?.selected}; + color: ${theme?.colors?.selected}; } .react-tabs__tab--disabled { color: GrayText; diff --git a/ui/components/src/ThemeContext/ThemeContext.tsx b/ui/components/src/ThemeContext/ThemeContext.tsx index cdbf6aba2..3c91299e7 100644 --- a/ui/components/src/ThemeContext/ThemeContext.tsx +++ b/ui/components/src/ThemeContext/ThemeContext.tsx @@ -1,6 +1,10 @@ import React from 'react'; import { polaris } from '@theme-ui/presets'; +import { get } from '@theme-ui/css'; +import { merge } from '@theme-ui/core'; + import { ThemeProvider as ThemeUIProvider, Theme } from 'theme-ui'; + import { lighten } from 'polished'; export interface ThemeContextProps { @@ -8,74 +12,104 @@ export interface ThemeContextProps { dark?: boolean; } export const ThemeContext = React.createContext<ThemeContextProps>({}); + export interface ThemeProviderProps { dark?: boolean; + theme?: Theme; } + +const applyColorMode = (theme: Theme, dark?: boolean) => { + if (!dark) { + return theme; + } + const modes = get(theme, 'colors.modes', {}); + return merge.all({}, theme, { + colors: get(modes, 'dark', {}), + }); +}; export const ThemeProvider: React.FC<ThemeProviderProps> = ({ + theme: customTheme, children, dark, }) => { - const theme = { - ...polaris, - styles: { - ...polaris.styles, - table: { - margin: 0, - borderCollapse: 'collapse', - fontSize: '14px', - lineHeight: '20px', - textAlign: 'left', - width: '100%', - borderSpacing: '0px', - }, - th: { - border: 'none', - padding: '10px 0 10px 20px', - }, - tbody: { - 'tr:last-of-type': { - borderBottom: 0, + const theme = React.useMemo(() => { + const defTheme = customTheme || polaris; + return applyColorMode( + { + ...defTheme, + initialColorModeName: dark ? 'dark' : 'default', + styles: { + ...defTheme.styles, + table: { + margin: 0, + borderCollapse: 'collapse', + fontSize: '14px', + lineHeight: '20px', + textAlign: 'left', + width: '100%', + borderSpacing: '0px', + }, + th: { + border: 'none', + padding: '10px 0 10px 20px', + }, + tbody: { + 'tr:last-of-type': { + borderBottom: 0, + }, + }, + thead: { + borderBottom: '1px solid #999', + backgroundColor: 'header', + color: 'text', + }, + td: { + padding: '16px 20px', + borderBottom: 0, + }, + tdgroup: { + fontWeight: 400, + lineHeight: '24px', + color: 'rgba(51,51,51,0.6)', + background: '#fafbfc', + whiteSpace: 'nowrap', + padding: '16px 20px', + }, + tr: { + borderBottom: '1px solid rgba(0, 0, 0, 0.1)', + }, + }, + buttons: { + primary: { + color: '#333', + bg: '#f3f3f3', + borderRadius: 5, + boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 0px 1px inset', + }, + secondary: { + bg: 'highlight', + }, + }, + colors: { + ...polaris.colors, + header: '#edebe8', + highlight: '#339793', + selected: '#1EA7FD', + fadedText: lighten(0.25, defTheme.colors.text), + modes: { + dark: { + ...(defTheme.colors.modes ? defTheme.colors.modes.dark : {}), + background: '#38404a', + text: '#d3d4db', + header: '#111111', + fadedText: '#b3b4ba', + }, + }, }, }, - thead: { - borderBottom: '1px solid #999', - backgroundColor: '#f6f8fa', - }, - td: { - padding: '16px 20px', - borderBottom: 0, - }, - tdgroup: { - fontWeight: 400, - lineHeight: '24px', - color: 'rgba(51,51,51,0.6)', - background: '#fafbfc', - whiteSpace: 'nowrap', - padding: '16px 20px', - }, - tr: { - borderBottom: '1px solid rgba(0, 0, 0, 0.1)', - }, - }, - buttons: { - primary: { - color: '#333', - bg: '#f3f3f3', - borderRadius: 5, - boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 0px 1px inset', - }, - secondary: { - bg: 'highlight', - }, - }, - colors: { - ...polaris.colors, - highlight: '#339793', - accent: '#1EA7FD', - fadedText: lighten(0.25, polaris.colors.text), - lightenPrimary: '#F6F9FC', - }, - }; + dark, + ); + }, [dark, customTheme]); return ( <ThemeContext.Provider value={{ diff --git a/ui/components/src/typings.d.ts b/ui/components/src/typings.d.ts index 6cfbda183..8feb11e8a 100644 --- a/ui/components/src/typings.d.ts +++ b/ui/components/src/typings.d.ts @@ -1,4 +1,5 @@ declare module '@theme-ui/presets'; +declare module '@theme-ui/core'; declare module '@mdx-js/runtime'; declare module '@theme-ui/css'; declare module '@mdx-js/react' { diff --git a/yarn.lock b/yarn.lock index 10ce26ef3..a80f0133c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3713,6 +3713,13 @@ dependencies: "@types/unist" "*" +"@types/mdx-js__react@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@types/mdx-js__react/-/mdx-js__react-1.5.1.tgz#3fbd03c083c34564520416202f24447a6197cb6d" + integrity sha512-CTt1SJxp5+bh+F3FNlmh7ZQCI4TH+Y68AvknWwo0IfevtN6OT0gkQEoNz4eUZzuNG0mr+21hSvvN38VJ3+GtPw== + dependencies: + "@types/react" "*" + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"