atmosphere explorer pds.ls
tool typescript atproto
Something went wrong. Try again.
4.6 kB · 111 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112import { createSignal, For, Show } from "solid-js";import { createStore } from "solid-js/store";
type Notification = { id: string; message: string; progress?: number; total?: number; type?: "info" | "success" | "error"; onCancel?: () => void;};
const [notifications, setNotifications] = createStore<Notification[]>([]);const [removingIds, setRemovingIds] = createSignal<Set<string>>(new Set());
// Omit duration for progress notifications that are dismissed by their caller.export const addNotification = ({ duration, ...notification}: Omit<Notification, "id"> & { duration?: number }) => { const id = `notification-${Date.now()}-${Math.random()}`; setNotifications(notifications.length, { ...notification, id }); if (duration !== undefined) setTimeout(() => removeNotification(id), duration); return id;};
export const updateNotification = (id: string, updates: Partial<Notification>) => { setNotifications((n) => n.id === id, updates);};
export const removeNotification = (id: string) => { setRemovingIds(new Set([...removingIds(), id])); setTimeout(() => { setNotifications((n) => n.filter((notification) => notification.id !== id)); setRemovingIds((ids) => { const newIds = new Set(ids); newIds.delete(id); return newIds; }); }, 250);};
export const NotificationContainer = () => { return ( <div class="pointer-events-none fixed bottom-4 left-4 z-60 flex flex-col gap-2"> <For each={notifications}> {(notification) => ( <div class="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto flex min-w-64 flex-col gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-3 shadow-md select-none dark:border-neutral-700" classList={{ "border-blue-500 dark:border-blue-400": notification.type === "info", "border-green-500 dark:border-green-400": notification.type === "success", "border-red-500 dark:border-red-400": notification.type === "error", "animate-[slideIn_0.25s_ease-in]": !removingIds().has(notification.id), "animate-[slideOut_0.25s_ease-in]": removingIds().has(notification.id), }} onClick={() => notification.progress === undefined && removeNotification(notification.id) } > <div class="flex items-center gap-2 text-sm"> <Show when={notification.progress !== undefined}> <span class="iconify lucide--download" /> </Show> <Show when={notification.type === "success"}> <span class="iconify lucide--check-circle text-green-600 dark:text-green-400" /> </Show> <Show when={notification.type === "error"}> <span class="iconify lucide--x-circle text-red-500 dark:text-red-400" /> </Show> <span>{notification.message}</span> </div> <Show when={notification.progress !== undefined}> <div class="flex flex-col gap-1"> <Show when={notification.total !== undefined && notification.total > 0} fallback={ <div class="text-xs text-neutral-600 dark:text-neutral-400"> {notification.progress} MB </div> } > <div class="h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700"> <div class="h-full rounded-full bg-blue-500 transition-all dark:bg-blue-400" style={{ width: `${notification.progress}%` }} /> </div> <div class="text-xs text-neutral-600 dark:text-neutral-400"> {notification.progress}% </div> </Show> <Show when={notification.onCancel}> <button class="dark:hover:bg-dark-200 dark:active:bg-dark-100 dark:bg-dark-300 mt-1 rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-xs select-none hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" onClick={(e) => { e.stopPropagation(); notification.onCancel?.(); }} > Cancel </button> </Show> </div> </Show> </div> )} </For> </div> );};