Replace Sonner with custom Sera UI-inspired toast system (#296)

* refactor(ui): replace Sonner with custom Sera UI-inspired toast system

Replace the Sonner toast library with a custom implementation inspired by
Sera UI's glassmorphism design. The new system uses an external store pattern
with useSyncExternalStore for React integration, keeping the same
toast.success()/error()/warning() API so all 172 call sites required only
an import path change.

Key changes:
- New toast-store.ts: singleton store with identical API to Sonner
- New toast.tsx: Sera UI-faithful Notification component with Framer Motion
  animations, frosted glass (backdrop-blur-xl), type gradient overlays,
  animated progress bar (green→blue→sky gradient), and hover:scale-105
- Removed sonner and next-themes dependencies
- Rewired all 19 consumer files to import from the new store

* fix(ui): resolve ESLint errors in toast system

- Use const for listeners Set (prefer-const)
- Initialize startRef with 0 instead of Date.now() to satisfy
  react-hooks/purity rule, set actual value inside useEffect
This commit is contained in:
Anso
2026-03-31 21:34:49 -04:00
committed by GitHub
parent 8d988d6b08
commit d393d06885
25 changed files with 241 additions and 74 deletions
+51
View File
@@ -0,0 +1,51 @@
import { useSyncExternalStore } from 'react';
export type ToastType = 'success' | 'error' | 'warning' | 'info';
export interface Toast {
id: string;
type: ToastType;
message: string;
createdAt: number;
}
let toasts: Toast[] = [];
const listeners: Set<() => void> = new Set();
let idCounter = 0;
function notify() {
listeners.forEach((fn) => fn());
}
function addToast(type: ToastType, message: string) {
const id = `toast-${++idCounter}-${Date.now()}`;
toasts = [...toasts, { id, type, message, createdAt: Date.now() }];
notify();
}
export function removeToast(id: string) {
toasts = toasts.filter((t) => t.id !== id);
notify();
}
function subscribe(callback: () => void) {
listeners.add(callback);
return () => {
listeners.delete(callback);
};
}
function getSnapshot() {
return toasts;
}
export function useToasts() {
return useSyncExternalStore(subscribe, getSnapshot);
}
export const toast = {
success: (message: string) => addToast('success', message),
error: (message: string) => addToast('error', message),
warning: (message: string) => addToast('warning', message),
info: (message: string) => addToast('info', message),
};