feat: initial

This commit is contained in:
unix
2020-03-19 01:15:58 +08:00
parent a37c630d54
commit 1a258b23d0
181 changed files with 17112 additions and 0 deletions

View 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

View 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)

View 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)