Skip to content

Instantly share code, notes, and snippets.

@libetl
Created March 31, 2022 16:15
Embed
What would you like to do?
My Design System playground
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