Closed sonhanguyen closed 5 years ago
Related #894
Hi @FredyC, I've found a way to do it in user land by overriding the theme. Basically you can wrap the theme with your theme which uses <ComponentsProvider>
to provide custom pre
and playground
component and inside the custom theme you can hook those two up since you have access to both's sources. I was only haft way through it last night, will share some code when I'm done.
@FredyC +1. I got the same requirement during developing the UI lib for my team.
@sonhanguyen You idea seems great, but I do not really get what we can do in <ComponentsProvider>
. Can't wait for your update. thanks.
This is what I came up with @wangpin34
// theme.jsx
import * as React from 'react'
import { ComponentsProvider, useComponents } from 'docz'
import { PlaygroundProps } from 'docz-theme-default/dist/components/ui/Playground'
import { transform } from 'buble'
const createPlayground = (Default, scope) => (props: PlaygroundProps) =>{
scope = { ...props.scope, ...scope }
return <Default { ...props } scope={scope} />
}
const createPre = (Default, Blockquote, scope) => props => {
const { props: childProps, type } = props.children
if (!childProps) { return null }
const {
className, // language-<ext>
mdxType, originalType, // code
parentName, // pre
children,
hidden, ['run-with']: toRun, // added
metastring,
...meta
} = childProps
if (toRun) {
const jsx = '$h'
const props = { [jsx]: React.createElement, ...scope, ...meta }
const VAR_NAME = /^[\$_\w]+$/i
const code = `
var {${Object.keys(props).filter(key => key.match(VAR_NAME))}} = props;
${transform(children, { jsx }).code}
`
try {
const func = new Function('exports', 'props', code)
func(scope, props)
} catch (e) { return <>
<Blockquote>{e.message}</Blockquote>
<Default { ...props }>{code.trim()}</Default>
</>
}
}
return hidden ? null : <Default { ...props } />
}
const usePerInstance = <T extends any>(factory: () => T) => React.useMemo(factory, [])
const ModuleScopeProvider = props => {
const components = useComponents()
const { playground, blockquote, pre } = components
const scope = usePerInstance(Object)
return <ComponentsProvider components={{
...components,
playground: usePerInstance(() => createPlayground(playground, scope)),
pre: usePerInstance(() => createPre(pre, blockquote, scope))
}}>
{props.children}
</ComponentsProvider>
}
const enhance = Theme => props => <Theme {...props}>
<ModuleScopeProvider children={props.children} />
</Theme>
export default enhance(require('docz-theme-default').default)
And this is how you use it
# doc.mdx
` ``jsx hidden run-with text=Hello
exports.Test = () => <span>{text}</span>
` ``
Currently there is a caveat which is that the block which mutates the scope need to be before <Playground>
<Playground>
<Test />
</Playground>
I tagged @FredyC since he is interested in the issue, not sure how it solves you guys' problem tbh. However I'm curious too so if you come up with something let me know
Sometimes you might want to create some ad-hoc example code and immediately use it on the playground.
The alternative is having the code imported from another module but then it would not be the same because you can't show it in the docs.