Skip to content

Commit 60b075d

Browse files
committed
Implement toast for degit action
1 parent 7a62e9f commit 60b075d

2 files changed

Lines changed: 226 additions & 99 deletions

File tree

Lines changed: 102 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { Style } from "@/components/Style";
4-
import { useEffect, useState } from "react";
4+
import { Toast } from "@/components/Toast";
5+
import { useState } from "react";
56
import { GoCommandPalette } from "react-icons/go";
67
import { RxOpenInNewWindow } from "react-icons/rx";
78
import { SiCodesandbox, SiGithub, SiStackblitz } from "react-icons/si";
@@ -13,116 +14,118 @@ export function Social({
1314
demoname: string;
1415
embed_url: string;
1516
}) {
16-
const [copied, setCopied] = useState(false);
17+
const [show, setShow] = useState(false);
18+
19+
const command = `npx -y degit pmndrs/examples/demos/${demoname} ${demoname} && cd ${demoname} && npm i && npm run dev`;
1720

1821
const handleClick = async () => {
19-
await navigator.clipboard.writeText(
20-
`cd $(mktemp -d ${demoname}.XXX) && npx -y degit pmndrs/examples/demos/${demoname} . && npm i && npm run dev`,
21-
);
22-
setCopied(true);
22+
await navigator.clipboard.writeText(command);
23+
setShow(true);
2324
};
2425

25-
useEffect(() => {
26-
if (!copied) return;
27-
const int = setTimeout(() => setCopied(false), 2000);
28-
return () => clearTimeout(int);
29-
}, [copied]);
30-
3126
return (
32-
<nav className="Social">
33-
<Style
34-
css={`
35-
@scope {
36-
& {
37-
display: flex;
38-
gap: 0.1rem;
39-
padding: 0.3rem;
40-
border-radius: 999px;
41-
background: rgb(255 255 255 / 0.82);
42-
backdrop-filter: blur(10px);
43-
box-shadow: 0 1px 6px rgb(0 0 0 / 0.1);
44-
}
27+
<>
28+
<nav className="Social">
29+
<Style
30+
css={`
31+
@scope {
32+
& {
33+
display: flex;
34+
gap: 0.1rem;
35+
padding: 0.3rem;
36+
border-radius: 999px;
37+
background: rgb(255 255 255 / 0.82);
38+
backdrop-filter: blur(10px);
39+
box-shadow: 0 1px 6px rgb(0 0 0 / 0.1);
40+
}
4541
46-
a {
47-
position: relative;
48-
display: grid;
49-
place-items: center;
50-
width: 1.8rem;
51-
height: 1.8rem;
52-
border-radius: 50%;
53-
color: #555;
54-
transition:
55-
background 0.15s ease,
56-
color 0.15s ease;
57-
}
42+
a {
43+
position: relative;
44+
display: grid;
45+
place-items: center;
46+
width: 1.8rem;
47+
height: 1.8rem;
48+
border-radius: 50%;
49+
color: #555;
50+
transition:
51+
background 0.15s ease,
52+
color 0.15s ease;
53+
}
5854
59-
a:hover {
60-
background: rgb(0 0 0 / 0.06);
61-
color: #111;
62-
}
55+
a:hover {
56+
background: rgb(0 0 0 / 0.06);
57+
color: #111;
58+
}
6359
64-
a svg {
65-
width: 0.95rem;
66-
height: 0.95rem;
67-
}
60+
a svg {
61+
width: 0.95rem;
62+
height: 0.95rem;
63+
}
6864
69-
a > span {
70-
position: absolute;
71-
top: 100%;
72-
left: 50%;
73-
translate: -50% 0;
74-
margin-top: 0.45rem;
75-
padding: 0.25em 0.5em;
76-
border-radius: 4px;
77-
background: #222;
78-
color: white;
79-
font-size: 0.65rem;
80-
white-space: nowrap;
81-
pointer-events: none;
82-
opacity: 0;
83-
transition: opacity 0.15s ease;
84-
}
65+
a > span {
66+
position: absolute;
67+
top: 100%;
68+
left: 50%;
69+
translate: -50% 0;
70+
margin-top: 0.45rem;
71+
padding: 0.25em 0.5em;
72+
border-radius: 4px;
73+
background: #222;
74+
color: white;
75+
font-size: 0.65rem;
76+
white-space: nowrap;
77+
pointer-events: none;
78+
opacity: 0;
79+
transition: opacity 0.15s ease;
80+
}
8581
86-
a:hover > span {
87-
opacity: 1;
82+
a:hover > span {
83+
opacity: 1;
84+
}
8885
}
89-
}
90-
`}
91-
/>
86+
`}
87+
/>
9288

93-
<a target="_blank" rel="noopener noreferrer" href={embed_url}>
94-
<RxOpenInNewWindow />
89+
<a target="_blank" rel="noopener noreferrer" href={embed_url}>
90+
<RxOpenInNewWindow />
91+
<span>fullpage</span>
92+
</a>
93+
<a
94+
target="_blank"
95+
rel="noopener noreferrer"
96+
href={`https://github.com/pmndrs/examples/tree/main/demos/${demoname}`}
97+
>
98+
<SiGithub />
99+
<span>code</span>
100+
</a>
101+
<a
102+
target="_blank"
103+
rel="noopener noreferrer"
104+
href={`https://stackblitz.com/github/pmndrs/examples/tree/main/demos/${demoname}`}
105+
>
106+
<SiStackblitz />
107+
<span>stackblitz</span>
108+
</a>
109+
<a
110+
target="_blank"
111+
rel="noopener noreferrer"
112+
href={`https://codesandbox.io/s/github/pmndrs/examples/tree/main/demos/${demoname}`}
113+
>
114+
<SiCodesandbox />
115+
<span>codesandbox</span>
116+
</a>
117+
<a href="javascript:void(0);" onClick={handleClick}>
118+
<GoCommandPalette />
119+
<span>degit</span>
120+
</a>
121+
</nav>
95122

96-
<span>fullpage</span>
97-
</a>
98-
<a
99-
target="_blank"
100-
rel="noopener noreferrer"
101-
href={`https://github.com/pmndrs/examples/tree/main/demos/${demoname}`}
102-
>
103-
<SiGithub />
104-
<span>code</span>
105-
</a>
106-
<a
107-
target="_blank"
108-
rel="noopener noreferrer"
109-
href={`https://stackblitz.com/github/pmndrs/examples/tree/main/demos/${demoname}`}
110-
>
111-
<SiStackblitz />
112-
<span>stackblitz</span>
113-
</a>
114-
<a
115-
target="_blank"
116-
rel="noopener noreferrer"
117-
href={`https://codesandbox.io/s/github/pmndrs/examples/tree/main/demos/${demoname}`}
118-
>
119-
<SiCodesandbox />
120-
<span>codesandbox</span>
121-
</a>
122-
<a href="javascript:void(0);" onClick={handleClick}>
123-
<GoCommandPalette />
124-
<span>{copied ? "copied command!" : "degit"}</span>
125-
</a>
126-
</nav>
123+
<Toast visible={show} onDone={() => setShow(false)}>
124+
<p>
125+
Degit command copied. Paste in your terminal to install{" "}
126+
<strong>{demoname}</strong> locally.
127+
</p>
128+
</Toast>
129+
</>
127130
);
128131
}

apps/website/components/Toast.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"use client";
2+
3+
import { Style } from "@/components/Style";
4+
import { useEffect, useRef, useState } from "react";
5+
import { createPortal } from "react-dom";
6+
7+
export function Toast({
8+
visible,
9+
onDone,
10+
children,
11+
duration = 6000,
12+
}: {
13+
visible: boolean;
14+
onDone: () => void;
15+
children: React.ReactNode;
16+
duration?: number;
17+
}) {
18+
const [mounted, setMounted] = useState(false);
19+
const [phase, setPhase] = useState<"enter" | "exit">("enter");
20+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
21+
22+
useEffect(() => {
23+
if (visible) {
24+
setMounted(true);
25+
setPhase("enter");
26+
27+
clearTimeout(timeoutRef.current);
28+
timeoutRef.current = setTimeout(() => setPhase("exit"), duration);
29+
} else if (mounted) {
30+
setPhase("exit");
31+
}
32+
33+
return () => clearTimeout(timeoutRef.current);
34+
}, [visible, duration]);
35+
36+
const handleAnimationEnd = () => {
37+
if (phase === "exit") {
38+
setMounted(false);
39+
onDone();
40+
}
41+
};
42+
43+
if (!mounted) return null;
44+
45+
return createPortal(
46+
<div
47+
className="Toast"
48+
data-phase={phase}
49+
onAnimationEnd={handleAnimationEnd}
50+
>
51+
<Style
52+
css={`
53+
@scope {
54+
:scope {
55+
position: fixed;
56+
bottom: 1rem;
57+
left: 50%;
58+
z-index: 9999;
59+
background: #333;
60+
color: #f0f0f0;
61+
border-radius: 0.5rem;
62+
padding: 0.75rem 1rem;
63+
max-width: min(520px, 90vw);
64+
width: max-content;
65+
box-shadow:
66+
0 2px 8px rgb(0 0 0 / 0.3),
67+
0 8px 30px rgb(0 0 0 / 0.2);
68+
}
69+
70+
:scope[data-phase="enter"] {
71+
animation: toast-in 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.2) forwards;
72+
}
73+
74+
:scope[data-phase="exit"] {
75+
animation: toast-out 0.2s ease-in forwards;
76+
}
77+
78+
@keyframes toast-in {
79+
from {
80+
opacity: 0;
81+
translate: -50% 1rem;
82+
}
83+
to {
84+
opacity: 1;
85+
translate: -50% 0;
86+
}
87+
}
88+
89+
@keyframes toast-out {
90+
from {
91+
opacity: 1;
92+
translate: -50% 0;
93+
}
94+
to {
95+
opacity: 0;
96+
translate: -50% 1rem;
97+
}
98+
}
99+
100+
p {
101+
margin: 0 0 0.4rem;
102+
font-size: 0.8rem;
103+
}
104+
105+
code {
106+
display: block;
107+
font-size: 0.72rem;
108+
color: #aaa;
109+
word-break: break-all;
110+
white-space: pre-wrap;
111+
line-height: 1.4;
112+
}
113+
114+
code:before {
115+
content: "$ ";
116+
}
117+
}
118+
`}
119+
/>
120+
{children}
121+
</div>,
122+
document.body,
123+
);
124+
}

0 commit comments

Comments
 (0)