Skip to content

Commit 1f52816

Browse files
authored
Merge pull request #437 from drizzle-team/toasts
Toasts
2 parents 8a5c5de + 54f3a7b commit 1f52816

File tree

2 files changed

+275
-10
lines changed

2 files changed

+275
-10
lines changed

src/ui/components/RightAside.astro

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
---
22
import AsideArticle from "./AsidePosts.astro";
33
import AsideSponsors from "./AsideSponsors.astro";
4-
import CarbonCard from "./CarbonCard.astro";
54
import SmallProgressBar from "./SmallProgressBar.astro";
65
import progress from "../../data/progress";
6+
import Toasts from "./Toasts.astro";
77
---
88

99
<div class="aside-right">
1010
<div class="aside-right__content">
11-
<div class="progress-wrap">
12-
<SmallProgressBar progress={progress} />
13-
</div>
14-
<AsideArticle />
15-
<AsideSponsors />
11+
<div class="progress-wrap">
12+
<SmallProgressBar progress={progress} />
13+
</div>
14+
<AsideArticle />
15+
<AsideSponsors />
1616
</div>
17-
<CarbonCard />
17+
<Toasts />
1818
</div>
1919
<style>
2020
.aside-right {
@@ -27,7 +27,11 @@ import progress from "../../data/progress";
2727
justify-content: space-between;
2828
height: calc(100vh - 104px);
2929
z-index: 99;
30-
position: relative;
30+
position: relative;
31+
}
32+
33+
html.dark .aside-right__content {
34+
background-color: #111;
3135
}
3236

3337
.aside-right__content {
@@ -36,11 +40,14 @@ import progress from "../../data/progress";
3640
flex-direction: column;
3741
align-items: center;
3842
justify-content: space-between;
43+
position: relative;
44+
z-index: 10000;
45+
background-color: #fff;
3946
}
4047

4148
.progress-wrap {
42-
width: 100%;
43-
margin-bottom: 4px;
49+
width: 100%;
50+
margin-bottom: 4px;
4451
}
4552

4653
@media screen and (max-width: 1024px) {

src/ui/components/Toasts.astro

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
---
2+
const toasts = [
3+
{
4+
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trending-up"><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></svg>`,
5+
title: "One Dollar Stats",
6+
description: "$1 per mo web analytics",
7+
href: "https://driz.link/onedollarstats",
8+
},
9+
];
10+
---
11+
12+
<div
13+
data-toasts
14+
class="toasts-wrap animated_bottom"
15+
transition:persist="toasts"
16+
>
17+
<div class="by-drizzle">Product by Drizzle</div>
18+
{
19+
toasts.map((toast, index) => (
20+
<a
21+
href={toast.href}
22+
target="_blank"
23+
rel="noreferrer nofollow"
24+
data-toast={index}
25+
data-toast-name={toast.title}
26+
style={`transform: translateY(${(toasts.length - index - 1) * 100}%) scale(${1 - (toasts.length - index - 1) * 0.06});`}
27+
class="toast"
28+
>
29+
<div class="toast-content">
30+
<div class="toast-icon">
31+
<Fragment set:html={toast.icon} />
32+
<div class="toast-indicator" />
33+
</div>
34+
<div class="toast-text">
35+
<span class="toast-title">{toast.title}</span>
36+
<span class="toast-description">{toast.description}</span>
37+
</div>
38+
</div>
39+
</a>
40+
))
41+
}
42+
</div>
43+
<style>
44+
.toasts-wrap {
45+
display: flex;
46+
flex-direction: column;
47+
position: absolute;
48+
z-index: 2;
49+
bottom: 0;
50+
left: 0;
51+
right: 0;
52+
width: 100%;
53+
opacity: 1;
54+
transition: opacity 1s;
55+
margin-bottom: 32px;
56+
}
57+
58+
.toasts-wrap-fade {
59+
opacity: 0.5;
60+
transition: opacity 0.3s;
61+
}
62+
63+
.toast {
64+
transition: transform 0.2s cubic-bezier(0.41, 0.41, 0.35, 1.3);
65+
}
66+
67+
.toasts-wrap:has(.toast:hover) .toast {
68+
transform: none !important;
69+
}
70+
71+
.toasts-wrap-fade:has(.toast:hover) {
72+
opacity: 1 !important;
73+
}
74+
75+
html.dark .toast-content {
76+
background: #111;
77+
border: 1px solid #292929;
78+
}
79+
80+
.toast-content {
81+
display: flex;
82+
align-items: center;
83+
width: 100%;
84+
/* box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px 0px; */
85+
background: #fbfbfc;
86+
border-radius: 8px;
87+
border: 1px solid #e0e1e3;
88+
}
89+
90+
.toast-text {
91+
display: flex;
92+
flex-direction: column;
93+
gap: 2px;
94+
}
95+
96+
.toast-title {
97+
text-transform: uppercase;
98+
font-size: 12px;
99+
font-weight: 600;
100+
line-height: normal;
101+
}
102+
103+
.toast-description {
104+
font-size: 12px;
105+
line-height: normal;
106+
}
107+
108+
.toast:not(:last-child) .toast-content {
109+
margin-bottom: 8px;
110+
}
111+
112+
@keyframes bottom {
113+
0% {
114+
transform: translateY(100%);
115+
opacity: 0;
116+
}
117+
75% {
118+
transform: translateY(-15%);
119+
opacity: 0.75;
120+
}
121+
100% {
122+
transform: translateY(0);
123+
opacity: 1;
124+
}
125+
}
126+
127+
.animated_bottom {
128+
animation: bottom 0.3s;
129+
}
130+
131+
.animated_fadeout {
132+
animation: fadeOut 3s;
133+
}
134+
135+
html.dark .toast-icon {
136+
background: #111;
137+
border: 1px solid #292929;
138+
}
139+
140+
.toast-icon {
141+
position: relative;
142+
width: 32px;
143+
height: 32px;
144+
border-radius: 50%;
145+
background-color: #f0f0f0;
146+
padding: 6px;
147+
margin: 8px;
148+
aspect-ratio: 1;
149+
border: 1px solid #e0e1e3;
150+
}
151+
152+
.toast-indicator {
153+
position: absolute;
154+
width: 8px;
155+
height: 8px;
156+
bottom: 0px;
157+
right: 0px;
158+
border-radius: 100%;
159+
background: #ef4444;
160+
}
161+
162+
html.dark .by-drizzle {
163+
background: #111;
164+
border: 1px solid #292929;
165+
color: #909090;
166+
}
167+
168+
.by-drizzle {
169+
text-transform: uppercase;
170+
font-size: 10px;
171+
bottom: -24px;
172+
position: absolute;
173+
transition: bottom 0.2s;
174+
margin-bottom: 8px;
175+
left: 50%;
176+
color: #909090;
177+
border: 1px solid #e0e1e3;
178+
white-space: pre;
179+
z-index: -1;
180+
transform: translateX(-50%);
181+
background-color: #fbfbfc;
182+
padding: 0 8px;
183+
border-bottom-left-radius: 8px;
184+
border-bottom-right-radius: 8px;
185+
}
186+
</style>
187+
<style is:global>
188+
.toast-icon svg {
189+
width: 100%;
190+
height: 100%;
191+
}
192+
</style>
193+
<script is:inline>
194+
const toasts = document.querySelectorAll("[data-toast]");
195+
const localStorageItems = localStorage.getItem("toasts");
196+
const storedToasts = localStorageItems ? JSON.parse(localStorageItems) : [];
197+
toasts.forEach((toast) => {
198+
const toastName = toast.dataset.toastName;
199+
const toastIndicator = toast.querySelector(".toast-indicator");
200+
if (storedToasts.includes(toastName) && toastIndicator) {
201+
toastIndicator.style.display = "none";
202+
}
203+
});
204+
</script>
205+
<script>
206+
const initToasts = () => {
207+
const toasts = document.querySelectorAll(
208+
"[data-toast]",
209+
) as NodeListOf<HTMLElement>;
210+
const localStorageItems = localStorage.getItem("toasts");
211+
const storedToasts = localStorageItems ? JSON.parse(localStorageItems) : [];
212+
213+
toasts.forEach((toast, index) => {
214+
const toastName = toast.dataset.toastName;
215+
const toastIndicator = toast.querySelector(
216+
".toast-indicator",
217+
) as HTMLElement;
218+
if (storedToasts.includes(toastName) && toastIndicator) {
219+
toastIndicator.style.display = "none";
220+
}
221+
222+
toast.addEventListener("mouseenter", () => {
223+
if (!storedToasts.includes(toastName)) {
224+
storedToasts.push(toastName);
225+
localStorage.setItem("toasts", JSON.stringify(storedToasts));
226+
}
227+
if (toastIndicator) {
228+
toastIndicator.style.display = "none";
229+
}
230+
});
231+
232+
setTimeout(
233+
() => {
234+
toast.setAttribute(
235+
"style",
236+
`transform: translateY(calc(${(toasts.length - index - 1) * 100}% - ${(toasts.length - index - 1) * 12}px)) scale(${1 - (toasts.length - index - 1) * 0.06});`,
237+
);
238+
},
239+
100 * toasts.length - index * 100,
240+
);
241+
242+
if (index !== toasts.length - 1) {
243+
toast.classList.add("animated_fadeout");
244+
}
245+
});
246+
247+
const toastsWrap = document.querySelector("[data-toasts]");
248+
if (toastsWrap) {
249+
setTimeout(() => {
250+
toastsWrap.classList.remove("animated_bottom");
251+
}, 300);
252+
setTimeout(() => {
253+
toastsWrap.classList.add("toasts-wrap-fade");
254+
}, 3000);
255+
}
256+
};
257+
document.addEventListener("astro:page-load", initToasts);
258+
</script>

0 commit comments

Comments
 (0)