From 181e0aee193e18ce2c5a5160eb20be3dba0367a9 Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 13 Jul 2026 10:28:31 +0800 Subject: [PATCH] refactor(app): migrate banner styles to tailwind --- .../__tests__/tailwind-components.test.tsx | 46 ++++++++++++- .../chat/components/chat-header.module.css | 60 ----------------- src/app/chat/components/chat-header.tsx | 24 ++++--- ...hat-insufficient-credits-banner.module.css | 65 ------------------- .../chat-insufficient-credits-banner.tsx | 12 ++-- .../__tests__/tailwind-components.test.tsx | 44 ++++++++++++- .../components/splash-button.module.css | 46 ------------- src/app/splash/components/splash-button.tsx | 9 +-- 8 files changed, 114 insertions(+), 192 deletions(-) delete mode 100644 src/app/chat/components/chat-header.module.css delete mode 100644 src/app/chat/components/chat-insufficient-credits-banner.module.css delete mode 100644 src/app/splash/components/splash-button.module.css diff --git a/src/app/chat/components/__tests__/tailwind-components.test.tsx b/src/app/chat/components/__tests__/tailwind-components.test.tsx index 4fe5e2d5..93d937c1 100644 --- a/src/app/chat/components/__tests__/tailwind-components.test.tsx +++ b/src/app/chat/components/__tests__/tailwind-components.test.tsx @@ -1,12 +1,20 @@ import { renderToStaticMarkup } from "react-dom/server"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; +import { ChatHeader } from "../chat-header"; +import { ChatInsufficientCreditsBanner } from "../chat-insufficient-credits-banner"; import { ChatSendButton } from "../chat-send-button"; import { ImageBubble } from "../image-bubble"; import { MessageAvatar } from "../message-avatar"; import { PrivateMessageCard } from "../private-message-card"; import { TextBubble } from "../text-bubble"; +vi.mock("@/router/use-app-navigator", () => ({ + useAppNavigator: () => ({ + openSubscription: vi.fn(), + }), +})); + describe("chat Tailwind components", () => { it("renders MessageAvatar AI and user branches with image utilities", () => { const aiHtml = renderToStaticMarkup(); @@ -116,4 +124,40 @@ describe("chat Tailwind components", () => { ); expect(paywalledHtml).toContain("data:image/png;base64,/locked-image.png"); }); + + it("renders ChatInsufficientCreditsBanner with split title and CTA", () => { + const html = renderToStaticMarkup( + undefined} + />, + ); + + expect(html).toContain('role="status"'); + expect(html).toContain('aria-live="polite"'); + expect(html).toContain("Need credits"); + expect(html).toContain("
"); + expect(html).toContain("Top up now"); + expect(html).toContain("Credits keep the conversation going."); + expect(html).toContain('aria-label="Top up"'); + expect(html).toContain("bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)]"); + expect(html).toContain("active:scale-[0.98]"); + }); + + it("renders ChatHeader guest and member branches", () => { + const guestHtml = renderToStaticMarkup(); + const memberHtml = renderToStaticMarkup( + Offer} />, + ); + + expect(guestHtml).toContain('aria-label="Sign up to unlock more features"'); + expect(guestHtml).toContain("bg-[var(--color-accent,#f84d96)]"); + expect(guestHtml).toContain("size-[var(--icon-size-sm,16px)]"); + expect(memberHtml).toContain("Offer"); + expect(memberHtml).toContain('aria-label="Menu"'); + expect(memberHtml).toContain("bg-[rgba(13,11,20,0.7)]"); + expect(memberHtml).toContain("size-[var(--icon-size-xl,32px)]"); + }); }); diff --git a/src/app/chat/components/chat-header.module.css b/src/app/chat/components/chat-header.module.css deleted file mode 100644 index 7ee906db..00000000 --- a/src/app/chat/components/chat-header.module.css +++ /dev/null @@ -1,60 +0,0 @@ -/* ChatHeader 顶部栏样式(与 Dart chat_header.dart 对齐) */ - -/* 外层:等价于 Dart `Column(mainAxisSize: MainAxisSize.min)` — 无 padding/背景 */ -.header { - flex: 0 0 auto; - display: flex; - flex-direction: column; - align-items: stretch; - padding: 0; - background: transparent; -} - -/* 游客模式横幅(Dart: Container(color: accent, padding: vertical 8 / horizontal 12)) */ -.guestBanner { - width: 100%; - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-sm, 8px); - padding: var(--spacing-sm, 8px) var(--spacing-md, 12px); - background: var(--color-accent, #f84d96); - color: #fff; - font-size: var(--responsive-caption, var(--font-size-sm, 12px)); - font-weight: 500; - cursor: pointer; - border: 0; - text-align: center; -} - -.guestBannerIcon { - width: var(--icon-size-sm, 16px); - height: var(--icon-size-sm, 16px); -} - -/* 登录模式行容器(Dart: Container(margin-left 12, padding: vertical 8 / horizontal 12)) */ -.headerRow { - display: flex; - align-items: center; - justify-content: space-between; - margin-left: var(--spacing-md, 12px); - padding: var(--spacing-sm, 8px) var(--spacing-md, 12px); -} - -/* 菜单按钮(Dart: Container(padding 8, bg #0d0b14 @ 70%, radius 32)) */ -.menuButton { - display: flex; - align-items: center; - justify-content: center; - padding: var(--spacing-sm, 8px); - background: rgba(13, 11, 20, 0.7); /* AppColors.darkBackground #0d0b14 @ alpha 0.7 */ - border-radius: var(--radius-full, 999px); - border: 0; - cursor: pointer; - color: var(--color-text-primary, #fff); -} - -.menuIcon { - width: var(--icon-size-xl, 32px); - height: var(--icon-size-xl, 32px); -} diff --git a/src/app/chat/components/chat-header.tsx b/src/app/chat/components/chat-header.tsx index 0506a315..4b204939 100644 --- a/src/app/chat/components/chat-header.tsx +++ b/src/app/chat/components/chat-header.tsx @@ -11,8 +11,6 @@ import { Lock, Menu } from "lucide-react"; import { ROUTES } from "@/router/routes"; import { useAppNavigator } from "@/router/use-app-navigator"; -import styles from "./chat-header.module.css"; - export interface ChatHeaderProps { isGuest: boolean; offerBanner?: ReactNode; @@ -23,14 +21,18 @@ export function ChatHeader({ isGuest, offerBanner }: ChatHeaderProps) { if (isGuest) { return ( -
+
@@ -38,18 +40,22 @@ export function ChatHeader({ isGuest, offerBanner }: ChatHeaderProps) { } return ( -
+
{offerBanner} -
+
{/* 菜单按钮(点击 → push 到 /sidebar,与 Dart MenuButton 一致) */}
diff --git a/src/app/chat/components/chat-insufficient-credits-banner.module.css b/src/app/chat/components/chat-insufficient-credits-banner.module.css deleted file mode 100644 index 2b409182..00000000 --- a/src/app/chat/components/chat-insufficient-credits-banner.module.css +++ /dev/null @@ -1,65 +0,0 @@ -/* ChatInsufficientCreditsBanner — 积分不足横幅 - * 视觉规格(与设计稿对齐): - * - 粉→品红渐变背景(与 splash gradient 一致) - * - 居中文案 + 渐变 pill 按钮 - * - 圆角 + 阴影(与 /sidebar 已有卡片的视觉重量对齐) - */ - -.banner { - flex: 0 0 auto; - margin: - 0 - var(--chat-inline-padding, 16px) - var(--page-section-gap, 16px); - padding: - var(--page-section-gap-lg, 24px) - var(--responsive-card-padding, 16px); - border-radius: var(--responsive-card-radius-sm, 24px); - background: linear-gradient(135deg, #f96ade 0%, #f657a0 100%); - text-align: center; - box-shadow: 0 4px 16px rgba(248, 77, 150, 0.25); -} - -.text { - color: #fff; - font-size: var(--responsive-card-title, 18px); - font-weight: 600; - line-height: 1.4; - margin: 0 0 var(--spacing-sm, 8px); - /* 与 splash 标题的粉色字保持视觉一致(白底渐变叠加层需要白字保证可读性) */ - opacity: 0.95; -} - -.description { - max-width: min(100%, 360px); - margin: 0 auto var(--spacing-lg, 16px); - color: rgba(255, 255, 255, 0.88); - font-size: var(--responsive-caption, 14px); - font-weight: 600; - line-height: 1.4; -} - -.cta { - width: 100%; - min-height: var(--responsive-control-height, 48px); - padding: 0 var(--responsive-card-padding-lg, 24px); - border: none; - border-radius: 999px; - background: linear-gradient(135deg, #ffb8e0 0%, #f57ec0 100%); - color: #fff; - font-size: var(--responsive-card-title, 18px); - font-weight: 700; - cursor: pointer; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - transition: - transform 0.15s ease, - box-shadow 0.15s ease; -} - -.cta:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); -} - -.cta:active { - transform: scale(0.98); -} diff --git a/src/app/chat/components/chat-insufficient-credits-banner.tsx b/src/app/chat/components/chat-insufficient-credits-banner.tsx index 531f5cd6..2681c565 100644 --- a/src/app/chat/components/chat-insufficient-credits-banner.tsx +++ b/src/app/chat/components/chat-insufficient-credits-banner.tsx @@ -18,8 +18,6 @@ */ import { useAppNavigator } from "@/router/use-app-navigator"; -import styles from "./chat-insufficient-credits-banner.module.css"; - export interface ChatInsufficientCreditsBannerProps { title?: string; description?: string; @@ -51,12 +49,12 @@ export function ChatInsufficientCreditsBanner({ return (
-

+

{titleLines.map((line, index) => ( {line} @@ -65,11 +63,13 @@ export function ChatInsufficientCreditsBanner({ ))}

{description ? ( -

{description}

+

+ {description} +

) : null}