mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 11:20:50 +08:00
feat: add notification animations and improve UI across pages Add enter/exit animations to NotificationContainer with smooth slide effects Refactor ConfigPage search bar to float over editor with improved UX Enhance AuthFilesPage type badges with proper light/dark theme color support Fix grid layout in AuthFilesPage to use consistent 3-column layout Update icon button sizing and loading state handlin Update i18n translations for search functionality
This commit is contained in:
@@ -1,16 +1,89 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
import type { Notification } from '@/types';
|
||||
|
||||
interface AnimatedNotification extends Notification {
|
||||
isExiting?: boolean;
|
||||
}
|
||||
|
||||
const ANIMATION_DURATION = 300; // ms
|
||||
|
||||
export function NotificationContainer() {
|
||||
const { notifications, removeNotification } = useNotificationStore();
|
||||
const [animatedNotifications, setAnimatedNotifications] = useState<AnimatedNotification[]>([]);
|
||||
const prevNotificationsRef = useRef<Notification[]>([]);
|
||||
|
||||
if (!notifications.length) return null;
|
||||
// Track notifications and manage animation states
|
||||
useEffect(() => {
|
||||
const prevNotifications = prevNotificationsRef.current;
|
||||
const prevIds = new Set(prevNotifications.map((n) => n.id));
|
||||
const currentIds = new Set(notifications.map((n) => n.id));
|
||||
|
||||
// Find new notifications (for enter animation)
|
||||
const newNotifications = notifications.filter((n) => !prevIds.has(n.id));
|
||||
|
||||
// Find removed notifications (for exit animation)
|
||||
const removedIds = new Set(
|
||||
prevNotifications.filter((n) => !currentIds.has(n.id)).map((n) => n.id)
|
||||
);
|
||||
|
||||
setAnimatedNotifications((prev) => {
|
||||
// Mark removed notifications as exiting
|
||||
let updated = prev.map((n) =>
|
||||
removedIds.has(n.id) ? { ...n, isExiting: true } : n
|
||||
);
|
||||
|
||||
// Add new notifications
|
||||
newNotifications.forEach((n) => {
|
||||
if (!updated.find((an) => an.id === n.id)) {
|
||||
updated.push({ ...n, isExiting: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Remove notifications that are not in current and not exiting
|
||||
// (they've already completed their exit animation)
|
||||
updated = updated.filter(
|
||||
(n) => currentIds.has(n.id) || n.isExiting
|
||||
);
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
// Clean up exited notifications after animation
|
||||
if (removedIds.size > 0) {
|
||||
setTimeout(() => {
|
||||
setAnimatedNotifications((prev) =>
|
||||
prev.filter((n) => !removedIds.has(n.id))
|
||||
);
|
||||
}, ANIMATION_DURATION);
|
||||
}
|
||||
|
||||
prevNotificationsRef.current = notifications;
|
||||
}, [notifications]);
|
||||
|
||||
const handleClose = (id: string) => {
|
||||
// Start exit animation
|
||||
setAnimatedNotifications((prev) =>
|
||||
prev.map((n) => (n.id === id ? { ...n, isExiting: true } : n))
|
||||
);
|
||||
|
||||
// Actually remove after animation
|
||||
setTimeout(() => {
|
||||
removeNotification(id);
|
||||
}, ANIMATION_DURATION);
|
||||
};
|
||||
|
||||
if (!animatedNotifications.length) return null;
|
||||
|
||||
return (
|
||||
<div className="notification-container">
|
||||
{notifications.map((notification) => (
|
||||
<div key={notification.id} className={`notification ${notification.type}`}>
|
||||
{animatedNotifications.map((notification) => (
|
||||
<div
|
||||
key={notification.id}
|
||||
className={`notification ${notification.type} ${notification.isExiting ? 'exiting' : 'entering'}`}
|
||||
>
|
||||
<div className="message">{notification.message}</div>
|
||||
<button className="close-btn" onClick={() => removeNotification(notification.id)}>
|
||||
<button className="close-btn" onClick={() => handleClose(notification.id)}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user