diff --git a/src/app.scss b/src/app.scss
index 4a00f11..77faba0 100644
--- a/src/app.scss
+++ b/src/app.scss
@@ -31,10 +31,15 @@
@mixin light {
// general
--accent-pink: hsl(302, 100%, 76%);
+ --accent-pink-alt: hsl(302, 100%, 50%);
--accent-red: hsl(348, 100%, 80%);
+ --accent-red-alt: hsl(348, 100%, 50%);
--accent-purple: hsl(264, 100%, 81%);
+ --accent-purple-alt: hsl(264, 100%, 50%);
--accent-blue: hsl(220, 100%, 78%);
+ --accent-blue-alt: hsl(220, 100%, 50%);
--accent: var(--accent-pink);
+ --accent-alt: var(--accent-pink-alt);
// foregrounds
--fg: hsl(0, 0%, 0%);
@@ -107,10 +112,15 @@
@mixin dark {
// general
--accent-pink: hsl(302, 100%, 76%);
+ --accent-pink-alt: hsl(302, 100%, 50%);
--accent-red: hsl(348, 100%, 80%);
+ --accent-red-alt: hsl(348, 100%, 50%);
--accent-purple: hsl(264, 100%, 81%);
+ --accent-purple-alt: hsl(264, 100%, 50%);
--accent-blue: hsl(220, 100%, 78%);
+ --accent-blue-alt: hsl(220, 100%, 50%);
--accent: var(--accent-pink);
+ --accent-alt: var(--accent-pink-alt);
// foregrounds
--fg: hsl(0, 0%, 100%);
diff --git a/src/lib/components/visual/Toast.svelte b/src/lib/components/visual/Toast.svelte
new file mode 100644
index 0000000..5fa413f
--- /dev/null
+++ b/src/lib/components/visual/Toast.svelte
@@ -0,0 +1,78 @@
+
+
+
+
+
removeToast(id)}
+ >
+
+
+
\ No newline at end of file
diff --git a/src/lib/store/ToastProvider.ts b/src/lib/store/ToastProvider.ts
new file mode 100644
index 0000000..20c692d
--- /dev/null
+++ b/src/lib/store/ToastProvider.ts
@@ -0,0 +1,68 @@
+import { writable } from "svelte/store";
+
+export type ToastType = "success" | "error" | "info" | "warning";
+
+export interface Toast {
+ id: number;
+ type: ToastType;
+ message: string;
+ disappearing: boolean;
+ durations: {
+ enter: number;
+ stay: number;
+ exit: number;
+ };
+}
+
+const toasts = writable([]);
+
+let toastId = 0;
+
+function addToast(
+ type: ToastType,
+ message: string,
+ disappearing?: boolean,
+ durations?: { enter: number; stay: number; exit: number },
+) {
+ const id = toastId++;
+
+ durations = durations ?? {
+ enter: 300,
+ stay: disappearing || disappearing === undefined ? 5000 : Infinity,
+ exit: 500,
+ };
+
+ // if "disappearing" not set, default error/warning to infinite duration
+ if (disappearing === undefined) {
+ switch (type) {
+ case "error":
+ case "warning":
+ durations.stay = Infinity;
+ break;
+ }
+ }
+
+ const newToast: Toast = {
+ id,
+ type,
+ message,
+ disappearing: disappearing ?? true,
+ durations,
+ };
+ toasts.update((currentToasts) => [...currentToasts, newToast]);
+
+ setTimeout(
+ () => {
+ removeToast(id);
+ },
+ durations.enter + durations.stay + durations.exit,
+ );
+}
+
+function removeToast(id: number) {
+ toasts.update((currentToasts) =>
+ currentToasts.filter((toast) => toast.id !== id),
+ );
+}
+
+export { toasts, addToast, removeToast };
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index a6e4542..e5ca680 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -29,11 +29,18 @@
import "../app.scss";
import { writable } from "svelte/store";
import { DISCORD_URL, GITHUB_URL } from "$lib/consts";
+ import { type Toast as ToastType, toasts } from "$lib/store/ToastProvider";
+ import Toast from "$lib/components/visual/Toast.svelte";
let { children } = $props();
let shouldGoBack = writable(false);
let navbar = $state();
let hover = $state(false);
+ let toastList = $state([]);
+
+ toasts.subscribe((value) => {
+ toastList = value as ToastType[];
+ });
const items = $derived<
{
@@ -218,6 +225,12 @@
{/key}
+
+ {#each toastList as { id, type, message, durations }}
+
+ {/each}
+
+
import * as Settings from "$lib/sections/settings";
+ import { addToast } from "$lib/store/ToastProvider";
import { SettingsIcon } from "lucide-svelte";
+ function showToast() {
+ addToast("success", "This is a success message!");
+ }
@@ -22,4 +26,6 @@
+
+
Show Toast
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 35381ca..4dab3f9 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -25,10 +25,15 @@ export default {
},
colors: {
accent: "var(--accent)",
+ "accent-alt": "var(--accent-alt)",
"accent-pink": "var(--accent-pink)",
+ "accent-pink-alt": "var(--accent-pink-alt)",
"accent-red": "var(--accent-red)",
+ "accent-red-alt": "var(--accent-red-alt)",
+ "accent-purple-alt": "var(--accent-purple-alt)",
"accent-purple": "var(--accent-purple)",
"accent-blue": "var(--accent-blue)",
+ "accent-blue-alt": "var(--accent-blue-alt)",
},
boxShadow: {
panel: "var(--shadow-panel)",