fix(ui): use sonner toast library with product styling

Replace hand-rolled notice with sonner; single Toaster in AppLayout; token-based toast styles, top-right.
This commit is contained in:
noelorin 2026-09-27 15:59:26 +08:00
parent e0ecf5b106
commit dcf66f23d0
8 changed files with 22 additions and 20 deletions

View File

@ -2,7 +2,6 @@
* 鉴权页主体:薄 SSR — 外层容器 + AuthForm 在浏览器渲染。
*/
import type { Locale } from "@app/i18n";
import { Toaster } from "@app/ui";
import AuthForm, { type AuthFormMode } from "~/components/AuthForm";
export interface AuthBodyProps {
@ -18,7 +17,6 @@ export default function AuthBody({ locale, mode, next, token }: AuthBodyProps) {
<div className="flex min-h-[60vh] items-center justify-center px-4 py-16">
<AuthForm locale={locale} mode={mode} next={next} defaultToken={token} />
</div>
<Toaster />
</>
);
}

View File

@ -2,6 +2,7 @@
import { ClientRouter } from "astro:transitions";
import "../styles/globals.css";
import UserMenu from "~/components/UserMenu";
import { Toaster } from "@app/ui";
import ThemeSwitch from "~/components/ThemeSwitch";
import LocaleSwitch from "~/components/LocaleSwitch";
import {
@ -238,5 +239,6 @@ const nav = [
</footer>
)}
</div>
</body>
<Toaster client:load />
</body>
</html>

View File

@ -1,6 +1,5 @@
---
import { type Locale, t } from "@app/i18n";
import { Toaster } from "@app/ui";
import { BrowseBody } from "~/components/pages/BrowseBody";
import AppLayout from "~/layouts/AppLayout.astro";
@ -23,5 +22,4 @@ const { locale } = Astro.props;
}}
>
<BrowseBody locale={locale} client:load />
<Toaster client:load />
</AppLayout>

View File

@ -1,6 +1,5 @@
---
import { type Locale, t } from "@app/i18n";
import { Toaster } from "@app/ui";
import { DanmakuSettingsBody } from "~/components/pages/DanmakuSettingsBody";
import AppLayout from "~/layouts/AppLayout.astro";
@ -23,5 +22,4 @@ const { locale } = Astro.props;
}}
>
<DanmakuSettingsBody locale={locale} client:load />
<Toaster client:load />
</AppLayout>

View File

@ -1,6 +1,5 @@
---
import { type Locale, t } from "@app/i18n";
import { Toaster } from "@app/ui";
import { LibraryBody } from "~/components/pages/LibraryBody";
import AppLayout from "~/layouts/AppLayout.astro";
@ -23,5 +22,4 @@ const { locale } = Astro.props;
}}
>
<LibraryBody locale={locale} client:load />
<Toaster client:load />
</AppLayout>

View File

@ -1,6 +1,5 @@
---
import { type Locale, t } from "@app/i18n";
import { Toaster } from "@app/ui";
import { MountsBody } from "~/components/pages/MountsBody";
import AppLayout from "~/layouts/AppLayout.astro";
@ -23,5 +22,4 @@ const { locale } = Astro.props;
}}
>
<MountsBody locale={locale} client:load />
<Toaster client:load />
</AppLayout>

View File

@ -1,6 +1,5 @@
---
import { type Locale, t } from "@app/i18n";
import { Toaster } from "@app/ui";
import { WatchBody } from "~/components/pages/WatchBody";
import AppLayout from "~/layouts/AppLayout.astro";
@ -24,5 +23,4 @@ const { locale, mediaItemId } = Astro.props;
}}
>
<WatchBody locale={locale} mediaItemId={mediaItemId} client:load />
<Toaster client:load />
</AppLayout>

View File

@ -3,19 +3,31 @@ import { Toaster as SonnerToaster, toast } from "sonner";
type ToasterProps = React.ComponentProps<typeof SonnerToaster>;
const Toaster = ({ style, ...props }: ToasterProps) => {
/**
* sonner 封装:站点统一 toast。
* 视觉用 design tokens,位置右上角,跟随主题。
*/
const Toaster = ({ ...props }: ToasterProps) => {
return (
<SonnerToaster
theme="light"
className="toaster group"
position="top-right"
offset={16}
gap={10}
mobileOffset={12}
toastOptions={{
unstyled: false,
classNames: {
toast: "group toast group-[.toaster]:bg-[var(--color-background)] group-[.toaster]:text-[var(--color-foreground)] group-[.toaster]:border-[var(--color-border)] group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-[var(--color-muted-foreground)]",
toast:
"group toast w-[min(22rem,calc(100vw-2rem))] rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] px-3.5 py-2.5 text-sm text-[var(--color-foreground)] shadow-lg",
title: "text-sm font-medium leading-snug",
description: "text-[var(--color-muted-foreground)] text-xs",
actionButton:
"group-[.toast]:bg-[var(--color-primary)] group-[.toast]:text-[var(--color-primary-foreground)]",
"bg-[var(--color-primary)] text-[var(--color-primary-foreground)] rounded-md px-2.5 py-1 text-xs font-medium",
cancelButton:
"group-[.toast]:bg-[var(--color-muted)] group-[.toast]:text-[var(--color-muted-foreground)]",
"bg-[var(--color-muted)] text-[var(--color-muted-foreground)] rounded-md px-2.5 py-1 text-xs",
success: "border-[var(--color-border)]",
error: "border-[var(--color-destructive,#dc2626)]/45",
info: "border-[var(--color-border)]",
},
}}
{...props}