atproto explorer pdsls.dev
atproto tool
1import { createSignal, For, Show } from "solid-js"; 2 3export type Notification = { 4 id: string; 5 message: string; 6 progress?: number; 7 total?: number; 8 type?: "info" | "success" | "error"; 9}; 10 11const [notifications, setNotifications] = createSignal<Notification[]>([]); 12const [removingIds, setRemovingIds] = createSignal<Set<string>>(new Set()); 13 14export const addNotification = (notification: Omit<Notification, "id">) => { 15 const id = `notification-${Date.now()}-${Math.random()}`; 16 setNotifications([...notifications(), { ...notification, id }]); 17 return id; 18}; 19 20export const updateNotification = (id: string, updates: Partial<Notification>) => { 21 setNotifications(notifications().map((n) => (n.id === id ? { ...n, ...updates } : n))); 22}; 23 24export const removeNotification = (id: string) => { 25 setRemovingIds(new Set([...removingIds(), id])); 26 setTimeout(() => { 27 setNotifications(notifications().filter((n) => n.id !== id)); 28 setRemovingIds((ids) => { 29 const newIds = new Set(ids); 30 newIds.delete(id); 31 return newIds; 32 }); 33 }, 250); 34}; 35 36export const NotificationContainer = () => { 37 return ( 38 <div class="pointer-events-none fixed bottom-4 left-4 z-50 flex flex-col gap-2"> 39 <For each={notifications()}> 40 {(notification) => ( 41 <div 42 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" 43 classList={{ 44 "border-blue-500 dark:border-blue-400": notification.type === "info", 45 "border-green-500 dark:border-green-400": notification.type === "success", 46 "border-red-500 dark:border-red-400": notification.type === "error", 47 "animate-[slideIn_0.25s_ease-in]": !removingIds().has(notification.id), 48 "animate-[slideOut_0.25s_ease-in]": removingIds().has(notification.id), 49 }} 50 onClick={() => removeNotification(notification.id)} 51 > 52 <div class="flex items-center gap-2 text-sm"> 53 <Show when={notification.progress !== undefined}> 54 <span class="iconify lucide--download" /> 55 </Show> 56 <Show when={notification.type === "success"}> 57 <span class="iconify lucide--check-circle text-green-600 dark:text-green-400" /> 58 </Show> 59 <Show when={notification.type === "error"}> 60 <span class="iconify lucide--x-circle text-red-500 dark:text-red-400" /> 61 </Show> 62 <span>{notification.message}</span> 63 </div> 64 <Show when={notification.progress !== undefined}> 65 <div class="flex flex-col gap-1"> 66 <Show 67 when={notification.total !== undefined && notification.total > 0} 68 fallback={ 69 <div class="text-xs text-neutral-600 dark:text-neutral-400"> 70 {notification.progress} MB 71 </div> 72 } 73 > 74 <div class="h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700"> 75 <div 76 class="h-full rounded-full bg-blue-500 transition-all dark:bg-blue-400" 77 style={{ width: `${notification.progress}%` }} 78 /> 79 </div> 80 <div class="text-xs text-neutral-600 dark:text-neutral-400"> 81 {notification.progress}% 82 </div> 83 </Show> 84 </div> 85 </Show> 86 </div> 87 )} 88 </For> 89 </div> 90 ); 91};