Skip to content

Commit 7b22623

Browse files
committed
Fix the copy banner animation
1 parent a1d887f commit 7b22623

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

src/components/CodeExample.js

Lines changed: 18 additions & 2 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: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@ module LzString = {
1313
external compressToEncodedURIComponent: string => string = "compressToEncodedURIComponent"
1414
}
1515

16+
module DomUtil = {
17+
@scope("document") @val external createElement: string => Dom.element = "createElement"
18+
@scope("document") @val external createTextNode: string => Dom.element = "createTextNode"
19+
@send external appendChild: (Dom.element, Dom.element) => unit = "appendChild"
20+
@send external removeChild: (Dom.element, Dom.element) => unit = "removeChild"
21+
22+
@set external setClassName: (Dom.element, string) => unit = "className"
23+
24+
type classList
25+
@get external classList: Dom.element => classList = "classList"
26+
@send external toggle: (classList, string) => unit = "toggle"
27+
28+
type animationFrameId
29+
@scope("window") @val
30+
external requestAnimationFrame: (unit => unit) => animationFrameId = "requestAnimationFrame"
31+
32+
@scope("window") @val
33+
external cancelAnimationFrame: animationFrameId => unit = "cancelAnimationFrame"
34+
}
35+
1636
module CopyButton = {
1737
let copyToClipboard: string => bool = %raw(j`
1838
function(str) {
@@ -48,6 +68,8 @@ module CopyButton = {
4868
let make = (~code) => {
4969
let (state, setState) = React.useState(_ => Init)
5070

71+
let buttonRef = React.useRef(Js.Nullable.null)
72+
5173
let onClick = evt => {
5274
ReactEvent.Mouse.preventDefault(evt)
5375
if copyToClipboard(code) {
@@ -60,12 +82,35 @@ module CopyButton = {
6082
React.useEffect1(() => {
6183
switch state {
6284
| Copied =>
85+
open DomUtil
86+
let buttonEl = Js.Nullable.toOption(buttonRef.current)->Belt.Option.getExn
87+
88+
// Note on this imperative DOM nonsense:
89+
// For Tailwind transitions to behave correctly, we need to first paint the DOM element in the tree,
90+
// and in the next tick, add the opacity-100 class, so the transition animation actually takes place.
91+
// If we don't do that, the banner will essentially pop up without any animation
92+
let bannerEl = createElement("div")
93+
bannerEl->setClassName(
94+
"foobar opacity-0 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 ",
95+
)
96+
let textNode = createTextNode("Copied!")
97+
98+
bannerEl->appendChild(textNode)
99+
buttonEl->appendChild(bannerEl)
100+
101+
let nextFrameId = requestAnimationFrame(() => {
102+
bannerEl->classList->toggle("opacity-0")
103+
bannerEl->classList->toggle("opacity-100")
104+
})
105+
63106
let timeoutId = Js.Global.setTimeout(() => {
107+
buttonEl->removeChild(bannerEl)
64108
setState(_ => Init)
65109
}, 2000)
66110

67111
Some(
68112
() => {
113+
cancelAnimationFrame(nextFrameId)
69114
Js.Global.clearTimeout(timeoutId)
70115
},
71116
)
@@ -85,8 +130,10 @@ module CopyButton = {
85130
{React.string("Copied!")}
86131
</div>
87132

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" />
133+
<button
134+
ref={ReactDOM.Ref.domRef(buttonRef)} disabled={state === Copied} className="relative" onClick>
135+
/* banner */
136+
<Icon.Copy className="text-gray-20 mt-px hover:cursor-pointer hover:text-gray-80" />
90137
</button>
91138
}
92139
}

0 commit comments

Comments
 (0)