Files
cunkebao_v3/Cunkebao/app/components/common/NotificationSystem.tsx
笔记本里的永平 5ff15472f5 feat: 本次提交更新内容如下
场景获客列表搞定
2025-07-07 17:08:27 +08:00

183 lines
5.6 KiB
TypeScript

"use client"
import { useState, createContext, useContext, type ReactNode } from "react"
import { Card, CardContent } from "@/app/components/ui/card"
import { Button } from "@/app/components/ui/button"
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from "lucide-react"
import { cn } from "@/app/lib/utils"
export type NotificationType = "success" | "error" | "warning" | "info"
export interface Notification {
id: string
type: NotificationType
title: string
message?: string
duration?: number
persistent?: boolean
actions?: Array<{
label: string
onClick: () => void
variant?: "default" | "outline"
}>
}
interface NotificationContextType {
notifications: Notification[]
addNotification: (notification: Omit<Notification, "id">) => void
removeNotification: (id: string) => void
clearAll: () => void
}
const NotificationContext = createContext<NotificationContextType | undefined>(undefined)
export function useNotifications() {
const context = useContext(NotificationContext)
if (!context) {
throw new Error("useNotifications must be used within a NotificationProvider")
}
return context
}
export function NotificationProvider({ children }: { children: ReactNode }) {
const [notifications, setNotifications] = useState<Notification[]>([])
const addNotification = (notification: Omit<Notification, "id">) => {
const id = Math.random().toString(36).substr(2, 9)
const newNotification: Notification = {
...notification,
id,
duration: notification.duration ?? 5000,
}
setNotifications((prev) => [newNotification, ...prev])
// 自动移除非持久化通知
if (!notification.persistent && newNotification.duration > 0) {
setTimeout(() => {
removeNotification(id)
}, newNotification.duration)
}
}
const removeNotification = (id: string) => {
setNotifications((prev) => prev.filter((notification) => notification.id !== id))
}
const clearAll = () => {
setNotifications([])
}
return (
<NotificationContext.Provider value={{ notifications, addNotification, removeNotification, clearAll }}>
{children}
<NotificationContainer />
</NotificationContext.Provider>
)
}
function NotificationContainer() {
const { notifications, removeNotification } = useNotifications()
if (notifications.length === 0) return null
return (
<div className="fixed top-4 right-4 z-50 space-y-2 max-w-sm w-full">
{notifications.map((notification) => (
<NotificationItem key={notification.id} notification={notification} onRemove={removeNotification} />
))}
</div>
)
}
function NotificationItem({
notification,
onRemove,
}: {
notification: Notification
onRemove: (id: string) => void
}) {
const getIcon = () => {
switch (notification.type) {
case "success":
return <CheckCircle className="h-5 w-5 text-green-500" />
case "error":
return <AlertCircle className="h-5 w-5 text-red-500" />
case "warning":
return <AlertTriangle className="h-5 w-5 text-yellow-500" />
case "info":
return <Info className="h-5 w-5 text-blue-500" />
}
}
const getBorderColor = () => {
switch (notification.type) {
case "success":
return "border-l-green-500"
case "error":
return "border-l-red-500"
case "warning":
return "border-l-yellow-500"
case "info":
return "border-l-blue-500"
}
}
return (
<Card className={cn("border-l-4 shadow-lg animate-in slide-in-from-right", getBorderColor())}>
<CardContent className="p-4">
<div className="flex items-start space-x-3">
{getIcon()}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<h4 className="font-medium text-sm">{notification.title}</h4>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-gray-400 hover:text-gray-600"
onClick={() => onRemove(notification.id)}
>
<X className="h-4 w-4" />
</Button>
</div>
{notification.message && <p className="text-sm text-gray-600 mt-1">{notification.message}</p>}
{notification.actions && notification.actions.length > 0 && (
<div className="flex space-x-2 mt-3">
{notification.actions.map((action, index) => (
<Button
key={index}
variant={action.variant || "outline"}
size="sm"
onClick={() => {
action.onClick()
onRemove(notification.id)
}}
>
{action.label}
</Button>
))}
</div>
)}
</div>
</div>
</CardContent>
</Card>
)
}
// 便捷的通知钩子
export function useNotify() {
const { addNotification } = useNotifications()
return {
success: (title: string, message?: string, options?: Partial<Notification>) =>
addNotification({ type: "success", title, message, ...options }),
error: (title: string, message?: string, options?: Partial<Notification>) =>
addNotification({ type: "error", title, message, ...options }),
warning: (title: string, message?: string, options?: Partial<Notification>) =>
addNotification({ type: "warning", title, message, ...options }),
info: (title: string, message?: string, options?: Partial<Notification>) =>
addNotification({ type: "info", title, message, ...options }),
}
}