mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 07:36:37 +08:00
[新增] 集成 Sonner 通知库,添加 Toaster 组件并在 InlineMessage 中使用
This commit is contained in:
@@ -1,35 +1,44 @@
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type InlineMessageTone = 'info' | 'success' | 'danger';
|
||||
|
||||
const toneClasses: Record<InlineMessageTone, string> = {
|
||||
info: 'border-[var(--status-info-border)] bg-[var(--status-info-soft)] text-[var(--status-info-foreground)]',
|
||||
success:
|
||||
'border-[var(--status-success-border)] bg-[var(--status-success-soft)] text-[var(--status-success-foreground)]',
|
||||
danger:
|
||||
'border-[var(--status-danger-border)] bg-[var(--status-danger-soft)] text-[var(--status-danger-foreground)]',
|
||||
};
|
||||
|
||||
interface InlineMessageProps {
|
||||
tone?: InlineMessageTone;
|
||||
message: string;
|
||||
className?: string;
|
||||
onClear?: () => void;
|
||||
}
|
||||
|
||||
export function InlineMessage({
|
||||
tone = 'info',
|
||||
message,
|
||||
className,
|
||||
onClear,
|
||||
}: InlineMessageProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-2xl border px-4 py-3 text-sm leading-6',
|
||||
toneClasses[tone],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!message) return;
|
||||
|
||||
const options = {
|
||||
position: 'bottom-right' as const,
|
||||
};
|
||||
|
||||
if (tone === 'success') {
|
||||
toast.success(message, options);
|
||||
} else if (tone === 'danger') {
|
||||
toast.error(message, options);
|
||||
} else {
|
||||
toast(message, options);
|
||||
}
|
||||
|
||||
if (onClear) {
|
||||
const timer = setTimeout(() => {
|
||||
onClear();
|
||||
}, 0);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [tone, message, onClear]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { type ReactNode, useState } from 'react';
|
||||
|
||||
import { AuthProvider } from '@/components/providers/auth-provider';
|
||||
import { ThemeProvider } from '@/components/providers/theme-provider';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
|
||||
interface AppProvidersProps {
|
||||
children: ReactNode;
|
||||
@@ -30,7 +31,10 @@ export function AppProviders({ children }: AppProvidersProps) {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
<AuthProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
'use client';
|
||||
|
||||
import { useTheme } from '@/components/providers/theme-provider';
|
||||
import { Toaster as Sonner } from 'sonner';
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||
|
||||
export function Toaster({ ...props }: ToasterProps) {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={resolvedTheme as ToasterProps['theme']}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
'group toast group-[.toaster]:bg-[var(--surface-elevated)] group-[.toaster]:text-[var(--foreground-primary)] group-[.toaster]:border-[var(--border-default)] group-[.toaster]:shadow-lg group-[.toaster]:rounded-2xl group-[.toaster]:font-sans',
|
||||
description: 'group-[.toast]:text-[var(--foreground-secondary)]',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-[var(--primary)] group-[.toast]:text-[var(--primary-foreground)]',
|
||||
cancelButton:
|
||||
'group-[.toast]:bg-[var(--muted)] group-[.toast]:text-[var(--muted-foreground)]',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user