Skip to content

Commit

Permalink
feat: add playground
Browse files Browse the repository at this point in the history
  • Loading branch information
pyadav committed May 23, 2023
1 parent 59b64f6 commit 919039c
Show file tree
Hide file tree
Showing 11 changed files with 681 additions and 1,268 deletions.
111 changes: 111 additions & 0 deletions apps/www/components/code-theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { theme } from "@odpf/apsara";

const makeCodeTheme = () => ({
plain: {
backgroundColor: theme.colors.gray1,
color: theme.colors.grass11,
fontWeight: '400',
fontStyle: 'normal',
fontFamily: theme.fonts.mono,
fontSize: '.875rem',
textRendering: 'geometricPrecision',
},
styles: [
{
types: ['comment', 'prolog', 'doctype', 'cdata', 'punctuation'],
style: {
color: 'theme.palette.accents_3',
opacity: 0.5,
},
},
{
types: ['namespace'],
style: {
opacity: 1,
},
},
{
types: ['tag', 'operator', 'number'],
style: {
color: theme.colors.amber1,
},
},
{
types: ['property', 'function'],
style: {
color: theme.colors.green10,
},
},
{
types: ['tag-id', 'selector', 'atrule-id'],
style: {
color: '#eeebff',
},
},
{
types: ['attr-name'],
style: {
color: theme.colors.red10,
},
},
{
types: [
'boolean',
'string',
'entity',
'url',
'attr-value',
'keyword',
'control',
'directive',
'unit',
'statement',
'regex',
'at-rule',
'placeholder',
'variable',
],
style: {
color: theme.colors.purple10,
},
},
{
types: ['deleted'],
style: {
textDecorationLine: 'line-through',
},
},
{
types: ['language-javascript', 'script'],
style: {
color: theme.colors.green10,
},
},
{
types: ['inserted'],
style: {
textDecorationLine: 'underline',
},
},
{
types: ['italic'],
style: {
fontStyle: 'italic',
},
},
{
types: ['important', 'bold'],
style: {
fontWeight: 'bold',
},
},
{
types: ['important'],
style: {
color: '#c4b9fe',
},
},
],
})

export default makeCodeTheme
109 changes: 109 additions & 0 deletions apps/www/components/editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { styled } from "@odpf/apsara";
import { ChevronRight, Copy } from "lucide-react";
import React, { useState } from "react";
import { LiveEditor } from "react-live";
import useCopyToClipboard from "./useClipboard";

interface Props {
code: string;
}

const StyledEditor = styled("div", {
borderRadius: "$2",
});

const Details = styled("details", {
transition: "all 0.2s ease",
overflow: "hidden",
});

const Summary = styled("summary", {
boxSizing: "border-box",
borderTop: "1px solid $gray4",
color: "$gray11",
width: "100%",
listStyle: "none",
userSelect: "none",
outline: "none",
cursor: "pointer",
backgroundColor: "$gray1"
});

const SummarySafari = styled("div", {
boxSizing: "border-box",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
height: "2.875rem",
padding: "$4",
});

const Action = styled("div", {
width: "auto",
display: "flex",
alignItems: "center",
fontSize: "0.8rem",
svg: {
cursor: "pointer",
},
});

const Area = styled("div", {
position: "relative",
boxSizing: "border-box",
whiteSpace: "pre",
fontFamily: "Consolas,Monaco",
lineHeight: '1.5',
color: "$gray4",
fontSize: "14px",
overflow: "hidden",
borderTop: "1px solid",
padding: "$4",
});

const Editor: React.FC<Props> = ({ code }) => {
const [_, copy] = useCopyToClipboard();

const [visible, setVisible] = useState(false);
const clickHandler = (event: React.MouseEvent) => {
event.stopPropagation();
event.preventDefault();
setVisible(!visible);
};

const copyHandler = (event: React.MouseEvent) => {
event.stopPropagation();
event.preventDefault();
copy(code);
};

return (
<StyledEditor>
<Details open={visible}>
<Summary onClick={clickHandler}>
<SummarySafari>
<Action>
<span className="arrow">
<ChevronRight size={16} />
</span>
<span>{"Code Editor"}</span>
</Action>
<Action>
{visible && (
<span className="copy" onClick={copyHandler} title={"Copy Code"}>
<Copy size={18} />
</span>
)}
</Action>
</SummarySafari>
</Summary>
<Area>
<LiveEditor />
</Area>
</Details>
</StyledEditor>
);
};

export default Editor;
34 changes: 34 additions & 0 deletions apps/www/components/live.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

import { styled } from '@odpf/apsara';
import React from 'react';
import { LiveError, LivePreview, LiveProvider } from 'react-live';
import Editor from './editor';

export interface Props {
code: string
scope: {
[key: string]: any
}
}

const Wrapper = styled('div', {
width: '100%',
padding: "$4",
display: "flex",
gap: "$2",
flexDirection: 'column',
})

const DynamicLive: React.FC<Props> = ({ code, scope }) => {
return (
<LiveProvider code={code} scope={scope}>
<Wrapper>
<LivePreview/>
<LiveError className="live-error" />
</Wrapper>
<Editor code={code} />
</LiveProvider>
)
}

export default DynamicLive
4 changes: 4 additions & 0 deletions apps/www/components/mdx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Link2Icon } from "@radix-ui/react-icons";
import * as React from "react";
import { Frontmatter } from "~/types/frontmatter";
import Icons from "./icons";
import DynamicLive from "./live";
import Playground from "./playground";
import { Preview } from "./preview";
import { Searchbar } from "./searchbar";

Expand All @@ -11,6 +13,8 @@ export const components = {
...Icons,
Preview: Preview,
Searchbar: Searchbar,
DynamicLive: DynamicLive,
Playground: Playground,
pre: (props: any) => (
<Apsara.Text
{...props}
Expand Down
54 changes: 54 additions & 0 deletions apps/www/components/playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

import { styled } from '@stitches/react'
import dynamic from 'next/dynamic'
import React from 'react'

const DynamicLive = dynamic(() => import('./live'), {
ssr: false,
loading: () => (
<div style={{ padding: '32px 16px', fontSize: "12px", borderRadius: "6px",
border: '1px solid #ededed' }}>
loading...
</div>
),
})

export type PlaygroundProps = {
title?: React.ReactNode | string
desc?: React.ReactNode | string
code: string
scope: {
[key: string]: any
}
}

const defaultProps = {
code: '',
scope: {},
}

const StyledPlayground = styled("div", {
borderRadius: "$2",
border: '1px solid $gray4'
})

const Playground: React.FC<PlaygroundProps> = React.memo(
({
code: inputCode,
scope,
}: PlaygroundProps & typeof defaultProps) => {

const code = inputCode.trim()
return (
<>
<StyledPlayground>
<DynamicLive code={code} scope={scope} />
</StyledPlayground>
</>
)
},
)

Playground.defaultProps = defaultProps
Playground.displayName = 'Playground'
export default Playground
30 changes: 30 additions & 0 deletions apps/www/components/useClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useState } from 'react'

type CopiedValue = string | null
type CopyFn = (text: string) => Promise<boolean> // Return success

function useCopyToClipboard(): [CopiedValue, CopyFn] {
const [copiedText, setCopiedText] = useState<CopiedValue>(null)

const copy: CopyFn = async text => {
if (!navigator?.clipboard) {
console.warn('Clipboard not supported')
return false
}

// Try to save to clipboard then save it in the state if worked
try {
await navigator.clipboard.writeText(text)
setCopiedText(text)
return true
} catch (error) {
console.warn('Copy failed', error)
setCopiedText(null)
return false
}
}

return [copiedText, copy]
}

export default useCopyToClipboard
Loading

0 comments on commit 919039c

Please sign in to comment.