From 51462660a4f3a981598929a891af4eacbc66d675 Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 13 Jul 2026 15:46:28 +0800 Subject: [PATCH] refactor(app): migrate shared styles to tailwind --- .../__tests__/tailwind-components.test.tsx | 19 +++ .../core/responsive-mobile-shell.tsx | 16 ++- src/app/chat/chat-screen.tsx | 17 ++- .../chat/components/chat-screen.module.css | 30 ---- .../__tests__/tailwind-components.test.tsx | 41 ++++++ .../sidebar/components/user-header.module.css | 132 ------------------ src/app/sidebar/components/user-header.tsx | 34 ++--- .../__tests__/tailwind-components.test.tsx | 16 +++ .../stripe-payment-dialog.module.css | 98 ------------- .../stripe-payment-dialog.styles.ts | 20 +++ .../components/stripe-payment-dialog.tsx | 7 +- .../subscription-checkout-button.tsx | 2 +- src/app/tip/tip-checkout-button.tsx | 2 +- 13 files changed, 145 insertions(+), 289 deletions(-) delete mode 100644 src/app/chat/components/chat-screen.module.css create mode 100644 src/app/sidebar/components/__tests__/tailwind-components.test.tsx delete mode 100644 src/app/sidebar/components/user-header.module.css delete mode 100644 src/app/subscription/components/stripe-payment-dialog.module.css create mode 100644 src/app/subscription/components/stripe-payment-dialog.styles.ts diff --git a/src/app/_components/core/__tests__/tailwind-components.test.tsx b/src/app/_components/core/__tests__/tailwind-components.test.tsx index 849ddef0..2c686720 100644 --- a/src/app/_components/core/__tests__/tailwind-components.test.tsx +++ b/src/app/_components/core/__tests__/tailwind-components.test.tsx @@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest"; import { Checkbox } from "../checkbox"; import { LoadingIndicator } from "../loading-indicator"; import { PageLoadingFallback } from "../page-loading-fallback"; +import { ResponsiveMobileShell } from "../responsive-mobile-shell"; describe("core Tailwind components", () => { it("renders LoadingIndicator with accessible status and dynamic sizing", () => { @@ -54,4 +55,22 @@ describe("core Tailwind components", () => { expect(labeledHtml).toContain("gap-(--spacing-sm)"); }); + it("renders ResponsiveMobileShell with bounded canvas utilities", () => { + const html = renderToStaticMarkup( + + Content + , + ); + + expect(html).toContain("min-h-(--app-viewport-height,100dvh)"); + expect(html).toContain("max-w-(--app-max-width,540px)"); + expect(html).toContain("overflow-x-clip"); + expect(html).toContain("custom-content"); + expect(html).toContain("Content"); + }); + }); diff --git a/src/app/_components/core/responsive-mobile-shell.tsx b/src/app/_components/core/responsive-mobile-shell.tsx index 55f7e2a4..d17887db 100644 --- a/src/app/_components/core/responsive-mobile-shell.tsx +++ b/src/app/_components/core/responsive-mobile-shell.tsx @@ -1,7 +1,5 @@ import type { CSSProperties, ReactNode } from "react"; -import styles from "./responsive-mobile-shell.module.css"; - export interface ResponsiveMobileShellProps { children: ReactNode; /** Background used inside the mobile canvas. */ @@ -24,8 +22,18 @@ export function ResponsiveMobileShell({ } as CSSProperties; return ( -
-
+
+
{children}
diff --git a/src/app/chat/chat-screen.tsx b/src/app/chat/chat-screen.tsx index d89b6fc2..462e6928 100644 --- a/src/app/chat/chat-screen.tsx +++ b/src/app/chat/chat-screen.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, type CSSProperties } from "react"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; @@ -35,7 +35,11 @@ import { useChatUnlockNavigationFlow } from "./hooks/use-chat-unlock-navigation- import { useChatGuestLogin } from "./hooks/use-chat-guest-login"; import { useChatMessageLimitBanner } from "./hooks/use-chat-message-limit-banner"; import { useChatPromotionBootstrap } from "./hooks/use-chat-promotion-bootstrap"; -import styles from "./components/chat-screen.module.css"; + +const chatShellStyle = { + "--chat-message-font-size": "clamp(16px, 3.333vw, 18px)", + "--chat-message-line-height": "1.5", +} as CSSProperties; export function ChatScreen() { const router = useRouter(); @@ -188,8 +192,11 @@ export function ChatScreen() { return ( -
-
+
+
-
+
{/* isGuest 派生自 auth.loginStatus */} { + it("renders guest, member, and VIP header states", () => { + const guestHtml = renderToStaticMarkup( + undefined} + />, + ); + const memberHtml = renderToStaticMarkup( + , + ); + const vipHtml = renderToStaticMarkup( + , + ); + + expect(guestHtml).toContain("flex w-full items-center"); + expect(guestHtml).toContain('aria-label="Log in"'); + expect(guestHtml).toContain("login"); + expect(memberHtml).toContain("VIP membership not activated"); + expect(vipHtml).toContain("VIP Member"); + expect(vipHtml).toContain("rounded-full"); + }); + + it("renders the loading skeleton with stable utility dimensions", () => { + const html = renderToStaticMarkup( + , + ); + + expect(html).toContain('aria-hidden="true"'); + expect(html).toContain("w-[60%]"); + expect(html).toContain("w-[80%]"); + expect(html).toContain("w-[40%]"); + }); +}); diff --git a/src/app/sidebar/components/user-header.module.css b/src/app/sidebar/components/user-header.module.css deleted file mode 100644 index 0b233973..00000000 --- a/src/app/sidebar/components/user-header.module.css +++ /dev/null @@ -1,132 +0,0 @@ -.row { - display: flex; - align-items: center; - gap: var(--spacing-md, 12px); - width: 100%; - box-sizing: border-box; -} - -.avatar { - flex: 0 0 auto; - width: var(--sidebar-avatar-size, 56px); - height: var(--sidebar-avatar-size, 56px); - border-radius: 50%; - background: var(--color-accent, #f84d96); - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - color: #ffffff; -} - -.avatarImg { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.avatarIcon { - color: inherit; -} - -.text { - display: flex; - flex-direction: column; - gap: var(--sidebar-card-gap-y, 4px); - flex: 1 1 auto; - min-width: 0; -} - -.name { - margin: 0; - font-size: var(--font-responsive-md, 16px); - font-weight: 600; - color: #000000; - line-height: 1.2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.subtitle { - margin: 0; - font-size: clamp(12px, 2.593vw, 14px); - color: var(--color-text-secondary, #9e9e9e); - line-height: 1.2; -} - -/* "VIP Member" 粉色 pill(含小钻石图标) */ -.vipMemberPill { - display: inline-flex; - align-items: center; - gap: var(--spacing-xs, 4px); - align-self: flex-start; - padding: var(--responsive-pill-padding-sm, 3px 8px); - border-radius: var(--radius-full, 999px); - background: var(--color-pill-bg, rgba(248, 77, 150, 0.10)); - color: var(--color-accent, #f84d96); - font-size: var(--font-size-xs, 10px); - font-weight: 600; - line-height: 1; -} - -.diamondIcon { - width: clamp(12px, 2.593vw, 14px); - height: clamp(11px, 2.407vw, 13px); - display: block; - flex-shrink: 0; -} - -/* guest 状态下的 "login" 粉色胶囊按钮 */ -.loginPill { - display: inline-flex; - align-items: center; - justify-content: center; - padding: clamp(5px, 1.111vw, 6px) var(--spacing-lg, 16px); - border-radius: var(--radius-full, 999px); - background: linear-gradient( - 90deg, - var(--color-button-gradient-start, #ff67e0), - var(--color-button-gradient-end, #ff52a2) - ); - color: var(--color-text-primary, #ffffff); - font-size: clamp(12px, 2.593vw, 14px); - font-weight: 600; - border: 0; - cursor: pointer; - flex-shrink: 0; -} - -.loginPill:hover { - opacity: 0.9; -} - -.loginPill:focus-visible { - outline: 2px solid var(--color-accent, #f84d96); - outline-offset: 2px; -} - -/* ============================================================ - * 骨架占位(isLoading 时显示) - * ============================================================ */ -.skeleton { - display: block; - background: var(--color-dark-gray, #e5e5e5); - border-radius: var(--radius-md, 8px); -} - -.skeletonMd { - width: 60%; - height: clamp(16px, 3.333vw, 18px); -} - -.skeletonSm { - width: 80%; - height: clamp(12px, 2.593vw, 14px); -} - -.skeletonXs { - width: 40%; - height: clamp(12px, 2.593vw, 14px); -} diff --git a/src/app/sidebar/components/user-header.tsx b/src/app/sidebar/components/user-header.tsx index b715e91b..ac39a1d0 100644 --- a/src/app/sidebar/components/user-header.tsx +++ b/src/app/sidebar/components/user-header.tsx @@ -5,8 +5,6 @@ import Image from "next/image"; import { UserMessageAvatar } from "@/app/_components"; import type { SidebarUserState } from "@/app/sidebar/sidebar-view-model"; -import styles from "./user-header.module.css"; - export interface UserHeaderProps { /** 派生状态:未登录 / 已登录未 VIP / 已登录且 VIP */ state: SidebarUserState; @@ -37,38 +35,42 @@ export function UserHeader({ }: UserHeaderProps) { if (isLoading) { return ( -