@@ -13,6 +13,26 @@ module LzString = {
13
13
external compressToEncodedURIComponent : string => string = "compressToEncodedURIComponent"
14
14
}
15
15
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
+
16
36
module CopyButton = {
17
37
let copyToClipboard : string => bool = %raw (j `
18
38
function(str) {
@@ -48,6 +68,8 @@ module CopyButton = {
48
68
let make = (~code ) => {
49
69
let (state , setState ) = React .useState (_ => Init )
50
70
71
+ let buttonRef = React .useRef (Js .Nullable .null )
72
+
51
73
let onClick = evt => {
52
74
ReactEvent .Mouse .preventDefault (evt )
53
75
if copyToClipboard (code ) {
@@ -60,12 +82,35 @@ module CopyButton = {
60
82
React .useEffect1 (() => {
61
83
switch state {
62
84
| 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
+
63
106
let timeoutId = Js .Global .setTimeout (() => {
107
+ buttonEl -> removeChild (bannerEl )
64
108
setState (_ => Init )
65
109
}, 2000 )
66
110
67
111
Some (
68
112
() => {
113
+ cancelAnimationFrame (nextFrameId )
69
114
Js .Global .clearTimeout (timeoutId )
70
115
},
71
116
)
@@ -85,8 +130,10 @@ module CopyButton = {
85
130
{React .string ("Copied!" )}
86
131
</div >
87
132
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" />
90
137
</button >
91
138
}
92
139
}
0 commit comments