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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,19 +3,31 @@ import { Toaster as SonnerToaster, toast } from "sonner";
type ToasterProps = React.ComponentProps<typeof SonnerToaster>; type ToasterProps = React.ComponentProps<typeof SonnerToaster>;
const Toaster = ({ style, ...props }: ToasterProps) => { /**
* sonner 封装:站点统一 toast。
* 视觉用 design tokens,位置右上角,跟随主题。
*/
const Toaster = ({ ...props }: ToasterProps) => {
return ( return (
<SonnerToaster <SonnerToaster
theme="light" position="top-right"
className="toaster group" offset={16}
gap={10}
mobileOffset={12}
toastOptions={{ toastOptions={{
unstyled: false,
classNames: { 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", toast:
description: "group-[.toast]:text-[var(--color-muted-foreground)]", "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: 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: 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} {...props}