Skip to content

Commit

Permalink
feat(gatsby-theme-docz): add optional iframe for preview and ed… (#1305)
Browse files Browse the repository at this point in the history
* feat(gatsby-theme-docz): add optional iframe for preview and editor

* feat(gatsby-theme-docz): add useScopingInPlayground in theme config

* docs: add example with styled components and scoping (#1306) - @lkuechler
  • Loading branch information
rakannimer committed Dec 6, 2019
1 parent 8d1ae9e commit 9c5082e
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 41 deletions.
2 changes: 2 additions & 0 deletions core/gatsby-theme-docz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
"prop-types": "^15.7.2",
"re-resizable": "^6.1.0",
"react-feather": "^2.0.3",
"react-frame-component": "^4.1.1",
"react-helmet-async": "^1.0.4",
"react-live": "^2.2.1",
"react-resize-detector": "^4.2.1",
"rehype-docz": "^2.1.0",
"rehype-slug": "^2.0.3",
"remark-docz": "^2.1.0",
Expand Down
23 changes: 23 additions & 0 deletions core/gatsby-theme-docz/src/components/Playground/IframeWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @jsx jsx */
import { jsx } from 'theme-ui'
import Iframe from 'react-frame-component'

import * as styles from './styles'

const CLEAR_PADDING = `<style> body { padding: 0; margin: 0; } </style>`
const INITIAL_IFRAME_CONTENT = `<!DOCTYPE html><html><head> ${CLEAR_PADDING} </head><body><div></div></body></html>`

export const IframeWrapper = ({ children, height, style = {} }) => {
return (
<Iframe
initialContent={INITIAL_IFRAME_CONTENT}
sx={{
...styles.previewInner(),
height,
...style,
}}
>
{children}
</Iframe>
)
}
115 changes: 74 additions & 41 deletions core/gatsby-theme-docz/src/components/Playground/index.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,79 @@
/** @jsx jsx */
import { jsx } from 'theme-ui'
import { useState } from 'react'
import React from 'react'
import { useConfig } from 'docz'
import { LiveProvider, LiveError, LivePreview, LiveEditor } from 'react-live'
import { Resizable } from 're-resizable'
import copy from 'copy-text-to-clipboard'
import ReactResizeDetector from 'react-resize-detector'

import { IframeWrapper } from './IframeWrapper'
import { usePrismTheme } from '~utils/theme'
import * as styles from './styles'
import * as Icons from '../Icons'

const getResizableProps = (width, setWidth) => ({
minWidth: 260,
maxWidth: '100%',
size: {
width: width,
height: 'auto',
},
style: {
margin: 0,
marginRight: 'auto',
},
enable: {
top: false,
right: true,
bottom: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
},
onResizeStop: (e, direction, ref) => {
setWidth(ref.style.width)
},
})

const transformCode = code => {
if (code.startsWith('()') || code.startsWith('class')) return code
return `<React.Fragment>${code}</React.Fragment>`
}

export const Playground = ({ code, scope, language }) => {
export const Playground = ({ code, scope, language, useScoping = false }) => {
const {
themeConfig: { showPlaygroundEditor, showLiveError, showLivePreview },
themeConfig: {
showPlaygroundEditor,
showLiveError,
showLivePreview,
useScopingInPlayground,
},
} = useConfig()

const [previewHeight, setPreviewHeight] = React.useState()
const [editorHeight, setEditorHeight] = React.useState()
const Wrapper = React.useCallback(
useScoping || useScopingInPlayground
? props => <IframeWrapper {...props}>{props.children}</IframeWrapper>
: props => (
<div sx={styles.previewInner(showingCode)}>{props.children}</div>
),
[useScoping]
)

// Makes sure scope is only given on mount to avoid infinite re-render on hot reloads
const [scopeOnMount] = useState(scope)
const [scopeOnMount] = React.useState(scope)
const theme = usePrismTheme()
const [showingCode, setShowingCode] = useState(() => showPlaygroundEditor)
const [width, setWidth] = useState(() => '100%')
const [showingCode, setShowingCode] = React.useState(showPlaygroundEditor)
const [width, setWidth] = React.useState('100%')
const resizableProps = getResizableProps(width, setWidth)

const copyCode = () => copy(code)

const toggleCode = () => setShowingCode(s => !s)

const resizableProps = {
minWidth: 260,
maxWidth: '100%',
size: {
width,
height: 'auto',
},
style: {
margin: 0,
marginRight: 'auto',
},
enable: {
top: false,
right: true,
bottom: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
},
onResizeStop: (e, direction, ref) => {
setWidth(ref.style.width)
},
}

return (
<Resizable {...resizableProps} data-testid="playground">
<LiveProvider
Expand All @@ -65,11 +84,17 @@ export const Playground = ({ code, scope, language }) => {
theme={theme}
>
<div sx={styles.previewWrapper}>
<div sx={styles.previewInner(showingCode)}>
<Wrapper height={previewHeight}>
{showLivePreview && (
<LivePreview sx={styles.preview} data-testid="live-preview" />
<LivePreview style={styles.preview} data-testid="live-preview" />
)}
</div>
<ReactResizeDetector
handleHeight
onResize={(width, height) => {
setPreviewHeight(height)
}}
/>
</Wrapper>
<div sx={styles.buttons}>
<button sx={styles.button} onClick={copyCode}>
<Icons.Clipboard size={12} />
Expand All @@ -79,14 +104,22 @@ export const Playground = ({ code, scope, language }) => {
</button>
</div>
</div>
{showingCode && (
<Wrapper height={editorHeight}>
<div style={styles.editor(theme)}>
<LiveEditor data-testid="live-editor" />
</div>
<ReactResizeDetector
handleHeight
onResize={(width, height) => {
setEditorHeight(height)
}}
/>
</Wrapper>
)}
{showLiveError && (
<LiveError sx={styles.error} data-testid="live-error" />
)}
{showingCode && (
<div sx={styles.editor(theme)}>
<LiveEditor data-testid="live-editor" />
</div>
)}
</LiveProvider>
</Resizable>
)
Expand Down
2 changes: 2 additions & 0 deletions core/gatsby-theme-docz/src/theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default merge(typography, {
showDarkModeSwitch: true,
// Display edit this page button on every page
showMarkdownEditButton: true,
// Wrap the playground editor and preview in iframes to avoid style/script collisions
useScopingInPlayground: false,
colors: {
...modes.light,
modes: {
Expand Down
2 changes: 2 additions & 0 deletions examples/with-styled-components-and-scoping/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.docz
node_modules
48 changes: 48 additions & 0 deletions examples/with-styled-components-and-scoping/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Docz Styled Components Example

## Using `create-docz-app`

```sh
npx create-docz-app docz-app-styled-docz --example with-styled-components-and-scoping
# or
yarn create docz-app docz-app-styled-docz --example with-styled-components-and-scoping
```

## Download manually

```sh
curl https://codeload.github.com/doczjs/docz/tar.gz/master | tar -xz --strip=2 docz-master/examples/with-styled-components-and-scoping
mv with-styled-components-and-scoping docz-example-styled-docz
```

## Setup

```sh
yarn # npm i
```

## Run

```sh
yarn dev # npm run dev
```

## Build

```sh
yarn build # npm run build
```

## Serve built app

```sh
yarn serve # npm run serve
```

## Deploy

```sh
yarn deploy
```

Note that by default `docz` generates the output site in `.docz/public` to change that add a `dest` field to your `doczrc.js` with the path you want to generate the code in.
5 changes: 5 additions & 0 deletions examples/with-styled-components-and-scoping/doczrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
themeConfig: {
useScopingInPlayground: true,
},
}
22 changes: 22 additions & 0 deletions examples/with-styled-components-and-scoping/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"private": true,
"name": "docz-example-styled-components-with-scoping",
"version": "2.0.0-rc.41",
"license": "MIT",
"files": [
"src/",
"package.json"
],
"scripts": {
"dev": "docz dev",
"build": "docz build"
},
"dependencies": {
"docz": "latest",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-frame-component": "^4.1.1",
"styled-components": "^4.3.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import styled from 'styled-components'
import t from 'prop-types'

const kinds = {
info: '#5352ED',
positive: '#2ED573',
negative: '#FF4757',
warning: '#FFA502',
}

const AlertStyled = styled('div')`
padding: 15px 20px;
background: white;
border-radius: 3px;
color: white;
background: ${({ kind = 'info' }) => kinds[kind]};
`

export const Alert = props => <AlertStyled {...props} />

Alert.propTypes = {
kind: t.oneOf(['info', 'positive', 'negative', 'warning']),
}

Alert.defaultProps = {
kind: 'info',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Alert
menu: Components
---

import { Playground, Props } from 'docz'
import { Alert } from './Alert'

# Alert

## Properties

<Props of={Alert} />

## Basic usage

<Playground>
<Alert>Some message</Alert>
</Playground>

## Using different kinds

<Playground>
<Alert kind="info">Some message</Alert>
<Alert kind="positive">Some message</Alert>
<Alert kind="negative">Some message</Alert>
<Alert kind="warning">Some message</Alert>
</Playground>
Loading

0 comments on commit 9c5082e

Please sign in to comment.