My Design System playground
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import { promises as fs } from "fs"; | |
import * as myDesignSystem from "my-design-system"; | |
import { LiveProvider, LiveError, LivePreview, LiveContext } from "react-live"; | |
import dynamic from "next/dynamic"; | |
let monaco: any = undefined; | |
const MonacoEditor: typeof import("react-monaco-editor").default = | |
(typeof global.window === 'undefined') ? undefined : dynamic(import("react-monaco-editor") | |
.then((mod) => { | |
monaco = mod.monaco; | |
return mod.default; | |
}) as any, { ssr: false }) as any; | |
type EditorProps = { | |
code: string; | |
error: string; | |
onChange: (code: string) => void; | |
language: string; | |
setLoading: (value: boolean) => void; | |
typings: Record<string, string>; | |
} | |
const removeImports = (code?: string) => !code ? code : code.replace(/import\s*[^;]+from\s*[^;]+;/g, '') | |
const MyEditor = ({ code, error, onChange, typings, setLoading }: EditorProps): JSX.Element => { | |
const [editor, setEditor] = React.useState(undefined); | |
if (error?.match(/\([0-9]+:[0-9]+\)/) && monaco && editor) { | |
const [, lineString, columnString] = error.match(/\(([0-9]+):([0-9]+)\)/); | |
const line = parseInt(lineString); | |
const column = parseInt(columnString); | |
monaco.editor.setModelMarkers(editor.getModel(), "editor", [ | |
{ severity: 8, message: error, startLineNumber: line, startColumn: column, endLineNumber: line, endColumn: column } | |
]) | |
} | |
if (monaco) { | |
monaco.languages.typescript?.typescriptDefaults.setCompilerOptions({ | |
jsx: monaco.languages.typescript.JsxEmit.React, | |
reactNamespace: 'React', | |
jsxFactory: 'React.createElement', | |
target: monaco.languages.typescript.ScriptTarget.ES2016, | |
allowNonTsExtensions: true, | |
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs, | |
module: monaco.languages.typescript.ModuleKind.CommonJS, | |
noEmit: true, | |
typeRoots: ["node_modules/@types"], | |
lib: [ | |
"dom", | |
"dom.iterable", | |
"es2015" | |
], | |
}); | |
Object.entries(typings ?? {}).forEach(([filename, content]) => { | |
monaco.languages.typescript?.typescriptDefaults.addExtraLib(content, filename); | |
}); | |
(window as any).monaco = monaco; | |
(window as any).editor = editor; | |
} | |
if (!error && monaco && editor) { | |
monaco.editor.setModelMarkers(editor.getModel(), "editor", []); | |
} | |
return !MonacoEditor ? <div /> : <MonacoEditor | |
editorDidMount={(editor) => { | |
setLoading(false) | |
setEditor(editor); | |
const model = monaco.editor.getModels().length > 1 ? monaco.editor.getModels()[0] : | |
monaco.editor.createModel('', 'typescript', monaco.Uri.parse('file:///index.tsx')); | |
editor.setModel(model); | |
}} | |
width="100%" | |
height={typeof global.window !== 'undefined' ? global.window.innerHeight - 210 : "100%"} | |
language={"typescript"} | |
theme="vs-dark" | |
value={code} | |
options={{ | |
autoClosingBrackets: "always", | |
"semanticHighlighting.enabled": true, | |
autoClosingQuotes: "always", | |
fontLigatures: true, | |
scrollbar: { | |
vertical: "visible", | |
horizontal: "visible" | |
}, | |
minimap: { | |
enabled: false | |
} | |
}} | |
onChange={onChange} | |
/> | |
} | |
const PlaygroundInner = ({ inline, setInline, loading, setLoading, code, setCode, typings }) => { | |
return ( | |
<LiveContext.Consumer> | |
{context => { | |
const { code: initialCode, language, error, onChange } = context as any; | |
return <> | |
<div> | |
<input type="checkbox" value={"inline"} checked={inline} onChange={() => { | |
setInline(!inline); setCode(defaultCode[`${!inline}`]) | |
}}/>JSX only | |
</div> | |
<div style={{display: "grid"}}> | |
<div> | |
<div | |
style={{ | |
backgroundColor: "rgb(50, 42, 56)", | |
caretColor: "white", | |
textAlign: loading ? "center" : undefined | |
}} | |
>{loading && <p>Loading...</p>} | |
<MyEditor | |
language={language} | |
code={code ?? initialCode} | |
error={error} | |
onChange={(code) => { setCode(code); onChange(removeImports(code)); }} | |
setLoading={setLoading} | |
typings={typings} /> | |
</div> | |
</div> | |
<div> | |
<div> | |
<LiveError /> | |
<LivePreview /> | |
</div> | |
</div> | |
</div></> | |
}} | |
</LiveContext.Consumer>); | |
} | |
const defaultCode = { | |
false: | |
`import * as React from 'react'; | |
import { Button } from 'my-design-system'; | |
type HasText = { text: string }; | |
const AButton = ({ text }: HasText) => <Button>{text}</Button>; | |
const LetsCodeButton = () => <AButton text="Start by writing in the editor."/>; | |
render(<LetsCodeButton/>); | |
`, | |
true: `<Button>Start by writing in the editor.</Button>` | |
} | |
const Playground = ({ typings }: { typings: Record<string, string> }) => { | |
const [inline, setInline] = React.useState(true); | |
const [code, setCode] = React.useState(undefined); | |
const [loading, setLoading] = React.useState(true); | |
const containerRef = React.useRef(); | |
return <div ref={containerRef}> | |
<h1>Playground</h1> | |
<LiveProvider | |
code={removeImports(code) ?? removeImports(defaultCode[`${inline}`])} | |
noInline={!inline} | |
scope={{ | |
...myDesignSystem, | |
}} | |
><PlaygroundInner | |
inline={inline} | |
setInline={setInline} | |
code={code} | |
setCode={setCode} | |
loading={loading} | |
setLoading={setLoading} | |
typings={typings} /></LiveProvider></div>; | |
} | |
export async function getStaticProps() { | |
const cssTypings = (await fs.readFile(`${process.cwd()}/node_modules/csstype/index.d.ts`)).toString(); | |
const propTypings = (await fs.readFile(`${process.cwd()}/node_modules/@types/prop-types/index.d.ts`)).toString(); | |
const reactTypings = (await fs.readFile(`${process.cwd()}/node_modules/@types/react/index.d.ts`)).toString(); | |
const myDesignSystemTypings = (await fs.readFile(`${process.cwd()}/node_modules/my-design-system/my-design-system.d.ts`)).toString() | |
.replace('/// <reference types="node" />', '') | |
.replace('/// <reference types="react" />', ''); | |
return { | |
props: { | |
typings: { | |
'ts:@types/csstype.d.ts': cssTypings, | |
'ts:@types/prop-types.d.ts': propTypings, | |
'ts:@types/react.d.ts': reactTypings, | |
'ts:@types/react-as-module.d.ts': | |
`import React = globalThis.React; | |
declare module 'react' { | |
export = React; | |
} | |
`, | |
'ts:@types/render-function.d.ts': | |
` | |
export {}; | |
declare global { | |
function render(element: React.ReactElement): void; | |
}`, | |
'ts:@types/design-system-typings.d.ts': myDesignSystemTypings, | |
} | |
}, | |
} | |
} | |
export default Playground; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment