mirror of
https://github.com/zhigang1992/react.git
synced 2026-04-29 12:45:32 +08:00
feat: initial
This commit is contained in:
112
lib/components/playground/code-theme.ts
Normal file
112
lib/components/playground/code-theme.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { PrismTheme } from 'prism-react-renderer'
|
||||
import { ZeitUIThemes } from 'components/styles/themes'
|
||||
|
||||
const makeCodeTheme = (theme: ZeitUIThemes): PrismTheme => ({
|
||||
plain: {
|
||||
backgroundColor: theme.palette.background,
|
||||
color: theme.palette.accents_4,
|
||||
fontWeight: '400',
|
||||
fontStyle: 'normal',
|
||||
fontFamily: theme.font.mono,
|
||||
fontSize: '.875rem',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
types: ["comment", "prolog", "doctype", "cdata", "punctuation"],
|
||||
style: {
|
||||
color: 'theme.palette.accents_3',
|
||||
opacity: .5,
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["namespace"],
|
||||
style: {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["tag", "operator", "number"],
|
||||
style: {
|
||||
color: theme.palette.accents_6,
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["property", "function"],
|
||||
style: {
|
||||
color: theme.palette.success,
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["tag-id", "selector", "atrule-id"],
|
||||
style: {
|
||||
color: "#eeebff"
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["attr-name"],
|
||||
style: {
|
||||
color: theme.palette.warning,
|
||||
}
|
||||
},
|
||||
{
|
||||
types: [
|
||||
"boolean",
|
||||
"string",
|
||||
"entity",
|
||||
"url",
|
||||
"attr-value",
|
||||
"keyword",
|
||||
"control",
|
||||
"directive",
|
||||
"unit",
|
||||
"statement",
|
||||
"regex",
|
||||
"at-rule",
|
||||
"placeholder",
|
||||
"variable"
|
||||
],
|
||||
style: {
|
||||
color: theme.palette.purple,
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["deleted"],
|
||||
style: {
|
||||
textDecorationLine: "line-through"
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['language-javascript', 'script'],
|
||||
style: {
|
||||
color: theme.palette.success
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["inserted"],
|
||||
style: {
|
||||
textDecorationLine: "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["italic"],
|
||||
style: {
|
||||
fontStyle: "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["important", "bold"],
|
||||
style: {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ["important"],
|
||||
style: {
|
||||
color: "#c4b9fe"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default makeCodeTheme
|
||||
154
lib/components/playground/index.tsx
Normal file
154
lib/components/playground/index.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React, { MouseEvent, useState } from 'react'
|
||||
import { LivePreview, LiveProvider, LiveEditor, LiveError } from 'react-live'
|
||||
import useClipboard from 'react-use-clipboard'
|
||||
import withDefaults from 'components/utils/with-defaults'
|
||||
import { useTheme, useToasts } from 'components'
|
||||
import makeCodeTheme from './code-theme'
|
||||
import RightIcon from 'lib/components/icons/right'
|
||||
import CopyIcon from 'lib/components/icons/copy'
|
||||
import Title from './title'
|
||||
|
||||
interface Props {
|
||||
title?: React.ReactNode | string
|
||||
desc?: string
|
||||
code: string
|
||||
scope: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
title: 'Default',
|
||||
desc: '',
|
||||
code: '',
|
||||
bindings: {},
|
||||
}
|
||||
|
||||
export type PlaygroundProps = Props & typeof defaultProps
|
||||
|
||||
const editor = (code: string) => {
|
||||
const theme = useTheme()
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [, setCopied] = useClipboard(code)
|
||||
const [, setToast] = useToasts()
|
||||
const clickHandler = (event: MouseEvent<HTMLDetailsElement>) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
setVisible(!visible)
|
||||
}
|
||||
|
||||
const copy = (event: MouseEvent<SVGElement>) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
setCopied()
|
||||
setToast({ text: 'code copied.' })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="editor">
|
||||
<details open={visible}>
|
||||
<summary onClick={clickHandler}>
|
||||
<div>
|
||||
<RightIcon active={visible} />
|
||||
<span>Code Editor</span>
|
||||
</div>
|
||||
<div>
|
||||
{visible && <CopyIcon onClick={copy} />}
|
||||
</div>
|
||||
</summary>
|
||||
<div className="area">
|
||||
<LiveEditor />
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<style jsx>{`
|
||||
.editor {
|
||||
border-bottom-left-radius: ${theme.layout.radius};
|
||||
border-bottom-right-radius: ${theme.layout.radius};
|
||||
}
|
||||
|
||||
details {
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 ${theme.layout.gapHalf};
|
||||
border-top: 1px solid ${theme.palette.accents_2};
|
||||
color: ${theme.palette.accents_5};
|
||||
height: 2.875rem;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
summary > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
summary :global(svg) {
|
||||
margin: 0 ${theme.layout.gapHalf};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.area {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
white-space: pre;
|
||||
font-family: ${theme.font.mono};
|
||||
color: ${theme.palette.foreground};
|
||||
background-color: ${theme.palette.background};
|
||||
font-size: 1em;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid ${theme.palette.accents_2};
|
||||
padding: ${theme.layout.gapHalf};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Playground: React.FC<PlaygroundProps> = React.memo(props => {
|
||||
const theme = useTheme()
|
||||
const codeTheme = makeCodeTheme(theme)
|
||||
const code = props.code.trim()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title title={props.title} desc={props.desc} />
|
||||
<div className="playground">
|
||||
<LiveProvider code={code} scope={props.scope} theme={codeTheme}>
|
||||
<div className="wrapper">
|
||||
<LivePreview />
|
||||
<LiveError />
|
||||
</div>
|
||||
{editor(code)}
|
||||
</LiveProvider>
|
||||
|
||||
<style jsx>{`
|
||||
.playground {
|
||||
width: 100%;
|
||||
border-radius: ${theme.layout.radius};
|
||||
border: 1px solid ${theme.palette.accents_2};
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
padding: ${theme.layout.pageMargin};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default withDefaults(Playground, defaultProps)
|
||||
49
lib/components/playground/title.tsx
Normal file
49
lib/components/playground/title.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import withDefaults from 'components/utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
title: React.ReactNode | string
|
||||
desc?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
desc: '',
|
||||
}
|
||||
|
||||
export type TitleProps = Props & typeof defaultProps
|
||||
|
||||
const replaceCode = (desc: string) => {
|
||||
if (!desc.includes('`')) return desc
|
||||
let count = 0
|
||||
return desc.replace(/`/g, () => {
|
||||
const val = count % 2 === 0 ? '<code>' : '</code>'
|
||||
count ++
|
||||
return val
|
||||
})
|
||||
}
|
||||
|
||||
const Title: React.FC<TitleProps> = React.memo(props => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3>{props.title}</h3>
|
||||
{props.desc && <p dangerouslySetInnerHTML={{ __html: replaceCode(props.desc) }} />}
|
||||
|
||||
<style jsx>{`
|
||||
h3 {
|
||||
margin-bottom: ${props.desc ? 0 : '30px'};
|
||||
line-height: 1;
|
||||
font-size: 1.3rem;
|
||||
margin-top: 75px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
h3 > p {
|
||||
margin: 0;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default withDefaults(Title, defaultProps)
|
||||
Reference in New Issue
Block a user