Skip to content

Commit a1d887f

Browse files
committed
Add copy / playground button to CodeExample component
1 parent 7c2dd7a commit a1d887f

File tree

6 files changed

+355
-18
lines changed

6 files changed

+355
-18
lines changed

src/components/CodeExample.js

Lines changed: 115 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/CodeExample.res

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,89 @@ let langShortname = (lang: string) =>
88
| rest => rest
99
}
1010

11+
module LzString = {
12+
@bs.module("lz-string")
13+
external compressToEncodedURIComponent: string => string = "compressToEncodedURIComponent"
14+
}
15+
16+
module CopyButton = {
17+
let copyToClipboard: string => bool = %raw(j`
18+
function(str) {
19+
try {
20+
const el = document.createElement('textarea');
21+
el.value = str;
22+
el.setAttribute('readonly', '');
23+
el.style.position = 'absolute';
24+
el.style.left = '-9999px';
25+
document.body.appendChild(el);
26+
const selected =
27+
document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
28+
el.select();
29+
document.execCommand('copy');
30+
document.body.removeChild(el);
31+
if (selected) {
32+
document.getSelection().removeAllRanges();
33+
document.getSelection().addRange(selected);
34+
}
35+
return true;
36+
} catch(e) {
37+
return false;
38+
}
39+
}
40+
`)
41+
42+
type state =
43+
| Init
44+
| Copied
45+
| Failed
46+
47+
@react.component
48+
let make = (~code) => {
49+
let (state, setState) = React.useState(_ => Init)
50+
51+
let onClick = evt => {
52+
ReactEvent.Mouse.preventDefault(evt)
53+
if copyToClipboard(code) {
54+
setState(_ => Copied)
55+
} else {
56+
setState(_ => Failed)
57+
}
58+
}
59+
60+
React.useEffect1(() => {
61+
switch state {
62+
| Copied =>
63+
let timeoutId = Js.Global.setTimeout(() => {
64+
setState(_ => Init)
65+
}, 2000)
66+
67+
Some(
68+
() => {
69+
Js.Global.clearTimeout(timeoutId)
70+
},
71+
)
72+
| _ => None
73+
}
74+
}, [state])
75+
76+
let activeClass = switch state {
77+
| Copied => "opacity-100"
78+
| _ => "opacity-0 hidden"
79+
}
80+
81+
let banner =
82+
<div
83+
className={"absolute top-0 -mt-1 -mr-1 px-2 rounded right-0 bg-turtle text-gray-80-tr transition-all duration-500 ease-in-out " ++
84+
activeClass}>
85+
{React.string("Copied!")}
86+
</div>
87+
88+
<button disabled={state === Copied} className="relative" onClick>
89+
banner <Icon.Copy className="text-gray-20 mt-px hover:cursor-pointer hover:text-gray-80" />
90+
</button>
91+
}
92+
}
93+
1194
@react.component
1295
let make = (~highlightedLines=[], ~code: string, ~showLabel=true, ~lang="text") => {
1396
let children = HighlightJs.renderHLJS(~highlightedLines, ~code, ~lang, ())
@@ -62,9 +145,9 @@ module Toggle = {
62145
}
63146

64147
let activeClass = if selected === i {
65-
"font-medium text-gray-90 bg-gray-5 border-t-2 border-l border-r"
148+
"font-medium text-gray-90 bg-gray-5 border-t-2 first:border-l"
66149
} else {
67-
"font-medium hover:text-gray-60 border-t-2 border-l border-r bg-gray-10 hover:cursor-pointer"
150+
"font-medium hover:text-gray-60 border-t-2 bg-gray-10 hover:cursor-pointer"
68151
}
69152

70153
let onClick = evt => {
@@ -83,14 +166,14 @@ module Toggle = {
83166
let borderColor = if selected === i {
84167
"#f4646a #EDF0F2"
85168
} else {
86-
"#CDCDD6 #EDF0F2"
169+
"transparent"
87170
}
88171

89172
<span
90173
key
91174
style={ReactDOM.Style.make(~borderColor, ())}
92175
className={paddingX ++
93-
(" flex-none px-4 first:ml-6 xs:first:ml-0 inline-block p-1 rounded-tl rounded-tr " ++
176+
(" flex-none px-4 inline-block p-1 first:rounded-tl " ++
94177
activeClass)}
95178
onClick>
96179
{React.string(label)}
@@ -105,13 +188,38 @@ module Toggle = {
105188
})
106189
->Belt.Option.getWithDefault(React.null)
107190

191+
let buttonDiv = switch Js.Array2.find(multiple, tab => {
192+
switch tab.lang {
193+
| Some("res") | Some("rescript") => true
194+
| _ => false
195+
}
196+
}) {
197+
| Some({code: ""}) => React.null
198+
| Some(tab) =>
199+
let playgroundLinkButton =
200+
<Next.Link href={`/try?code=${LzString.compressToEncodedURIComponent(tab.code)}}`}>
201+
<a target="_blank">
202+
<Icon.ExternalLink className="text-gray-20 hover:cursor-pointer hover:text-gray-80" />
203+
</a>
204+
</Next.Link>
205+
206+
let copyButton = <CopyButton code={tab.code} />
207+
208+
<div className="flex items-center justify-end h-full pr-4 space-x-3">
209+
playgroundLinkButton copyButton
210+
</div>
211+
| None => React.null
212+
}
213+
108214
<div className="relative pt-6 w-full rounded-none text-gray-80">
109215
//text within code-box
110216
<div
111217
className="absolute flex w-full overflow-auto scrolling-touch font-sans bg-transparent text-14 text-gray-40 "
112218
style={ReactDOM.Style.make(~marginTop="-30px", ())}>
113-
<div className="flex space-x-1"> {React.array(tabElements)} </div>
114-
<div className="flex-1 border-b border-gray-10"> {React.string(j`\\u00A0`)} </div>
219+
<div className="flex ml-2 xs:ml-0"> {React.array(tabElements)} </div>
220+
<div className="flex-1 w-full bg-gray-10 border-b rounded-tr border-gray-10 items-center">
221+
buttonDiv
222+
</div>
115223
</div>
116224
<div
117225
className="px-4 lg:px-5 text-14 pb-4 pt-4 overflow-x-auto bg-gray-5 border-gray-10 xs:rounded-b-lg border">

0 commit comments

Comments
 (0)