From b7c4ba9b4edc1da690641eefbb486d1083269a88 Mon Sep 17 00:00:00 2001 From: chenhang Date: Fri, 10 Jul 2026 19:05:51 +0800 Subject: [PATCH] refactor(auth): migrate form controls to tailwind --- .../__tests__/tailwind-components.test.tsx | 41 +++++++++++++ .../components/auth-legal-text.module.css | 56 ------------------ src/app/auth/components/auth-legal-text.tsx | 16 ++--- .../components/auth-social-button.module.css | 50 ---------------- .../auth/components/auth-social-button.tsx | 17 ++++-- .../components/auth-text-field.module.css | 59 ------------------- src/app/auth/components/auth-text-field.tsx | 27 ++++++--- 7 files changed, 80 insertions(+), 186 deletions(-) delete mode 100644 src/app/auth/components/auth-legal-text.module.css delete mode 100644 src/app/auth/components/auth-social-button.module.css delete mode 100644 src/app/auth/components/auth-text-field.module.css diff --git a/src/app/auth/components/__tests__/tailwind-components.test.tsx b/src/app/auth/components/__tests__/tailwind-components.test.tsx index 5e6e7cf8..372b41f2 100644 --- a/src/app/auth/components/__tests__/tailwind-components.test.tsx +++ b/src/app/auth/components/__tests__/tailwind-components.test.tsx @@ -3,6 +3,9 @@ import { describe, expect, it, vi } from "vitest"; import { AuthBackground } from "../auth-background"; import { AuthErrorMessage } from "../auth-error-message"; +import { AuthLegalText } from "../auth-legal-text"; +import { AuthSocialButton } from "../auth-social-button"; +import { AuthTextField } from "../auth-text-field"; import { EmailLoginForm } from "../email-login-form"; import { EmailRegisterForm } from "../email-register-form"; @@ -49,4 +52,42 @@ describe("auth Tailwind components", () => { expect(html).toContain("object-cover"); expect(html).toContain("%2Fimages%2Fauth%2Fbg-login.png"); }); + + it("renders AuthSocialButton with Tailwind button layout", () => { + const html = renderToStaticMarkup( + G}>Continue, + ); + + expect(html).toContain("min-h-[var(--auth-social-button-height)]"); + expect(html).toContain("border-auth-border"); + expect(html).toContain("text-auth-text-primary"); + expect(html).toContain("Continue"); + }); + + it("renders AuthLegalText with Tailwind checkbox and links", () => { + const html = renderToStaticMarkup(); + + expect(html).toContain('aria-label="Agree to terms"'); + expect(html).toContain("size-[var(--auth-legal-checkbox-size)]"); + expect(html).toContain("bg-auth-legal-check"); + expect(html).toContain("Privacy Agreement"); + expect(html).toContain("Terms of User"); + }); + + it("renders AuthTextField with Tailwind input states", () => { + const html = renderToStaticMarkup( + {}} + label="Email" + placeholder="Email" + errorMessage="Invalid email" + />, + ); + + expect(html).toContain("min-h-[var(--auth-field-height)]"); + expect(html).toContain("placeholder:text-auth-input-placeholder"); + expect(html).toContain("text-auth-text-primary"); + expect(html).toContain("Invalid email"); + }); }); diff --git a/src/app/auth/components/auth-legal-text.module.css b/src/app/auth/components/auth-legal-text.module.css deleted file mode 100644 index 6608342f..00000000 --- a/src/app/auth/components/auth-legal-text.module.css +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - * 视觉规格(与 Dart 对齐): - * - 行:16px 白色圆形复选框 + 12px 富文本 - * - 复选框选中时 10px 粉色实心圆 - * - 链接加粗(Privacy Policy / Terms of Service) - */ -.row { - display: flex; - align-items: flex-start; - gap: var(--spacing-sm); - width: 100%; -} - -.checkbox { - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - width: var(--auth-legal-checkbox-size); - height: var(--auth-legal-checkbox-size); - border-radius: 50%; - background: var(--color-auth-surface); - border: 1px solid var(--color-auth-surface); - padding: 0; - cursor: pointer; - margin-top: 2px; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06); -} - -.checkboxInner { - display: block; - width: var(--auth-legal-checkbox-inner-size); - height: var(--auth-legal-checkbox-inner-size); - border-radius: 50%; - background: var(--color-auth-legal-check); -} - -.text { - margin: 0; - font-size: var(--responsive-caption, var(--font-size-sm)); - line-height: 1.4; - color: var(--color-auth-text-primary); - text-align: left; - flex: 1 1 auto; -} - -.link { - color: var(--color-auth-text-primary); - font-weight: 700; - text-decoration: none; -} - -.link:hover { - text-decoration: underline; -} diff --git a/src/app/auth/components/auth-legal-text.tsx b/src/app/auth/components/auth-legal-text.tsx index d319d4a5..29644c0a 100644 --- a/src/app/auth/components/auth-legal-text.tsx +++ b/src/app/auth/components/auth-legal-text.tsx @@ -15,27 +15,27 @@ import { useState } from "react"; import { AppConstants } from "@/core/app_constants"; -import styles from "./auth-legal-text.module.css"; - export function AuthLegalText() { const [checked, setChecked] = useState(true); return ( -
+
-

+

I agree to the Cozsweet {" "} @@ -44,7 +44,7 @@ export function AuthLegalText() { and{" "} diff --git a/src/app/auth/components/auth-social-button.module.css b/src/app/auth/components/auth-social-button.module.css deleted file mode 100644 index 31b5f41a..00000000 --- a/src/app/auth/components/auth-social-button.module.css +++ /dev/null @@ -1,50 +0,0 @@ -/* - * - * 视觉规格(与 Dart 弹层按钮对齐): - * - 40px 高 - * - 白底 + 1px 边框 `--color-auth-border` (#d5d7db) - * - 半径 20 - * - 无阴影 - * - 图标 20×20 + 标签 - * - 14px 文字深色 - */ -.button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--spacing-md); - width: 100%; - min-height: var(--auth-social-button-height); - padding: 0 var(--spacing-lg); - border: 1px solid var(--color-auth-border); - border-radius: var(--radius-full, 999px); - background: var(--color-auth-surface); - color: var(--color-auth-text-primary); - font-size: var(--responsive-body, var(--font-size-md)); - font-weight: 500; - cursor: pointer; - transition: background 0.15s ease, opacity 0.15s ease; -} - -.button:hover:not(:disabled) { - background: rgba(0, 0, 0, 0.02); -} - -.button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - flex: 0 0 auto; -} - -.label { - flex: 0 0 auto; - text-align: center; -} diff --git a/src/app/auth/components/auth-social-button.tsx b/src/app/auth/components/auth-social-button.tsx index 1e3cddcd..a0821b72 100644 --- a/src/app/auth/components/auth-social-button.tsx +++ b/src/app/auth/components/auth-social-button.tsx @@ -17,8 +17,6 @@ import { type ReactNode, } from "react"; -import styles from "./auth-social-button.module.css"; - export interface AuthSocialButtonProps extends Omit, "children"> { icon?: ReactNode; @@ -37,11 +35,20 @@ export function AuthSocialButton({ ); } diff --git a/src/app/auth/components/auth-text-field.module.css b/src/app/auth/components/auth-text-field.module.css deleted file mode 100644 index f18cca77..00000000 --- a/src/app/auth/components/auth-text-field.module.css +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * 视觉规格(与 Dart 对齐): - * - 46px 高 - * - 白底胶囊(半径 24) - * - 无边框 - * - 10% 粉色阴影(accent #f759a8 @ 10%) - * - padding: 0 16 0 24 - * - 文字深色 #333,占位符 #999 - */ -.wrapper { - display: flex; - flex-direction: column; - gap: var(--spacing-xs); - width: 100%; -} - -.label { - font-size: var(--responsive-caption, var(--font-size-sm)); - color: var(--color-text-secondary); -} - -.input { - width: 100%; - min-height: var(--auth-field-height); - padding: 0 var(--spacing-lg) 0 var(--spacing-xxl); - border: none; - border-radius: var(--radius-full, 999px); - background: var(--color-auth-surface); - color: var(--color-auth-text-primary); - font-size: var(--responsive-body, var(--font-size-md)); - box-shadow: 0 3px 5px var(--color-auth-input-shadow); - outline: none; - transition: box-shadow 0.15s ease; -} - -.input::placeholder { - color: var(--color-auth-input-placeholder); -} - -.input:focus { - box-shadow: 0 3px 5px var(--color-auth-input-shadow), - 0 0 0 2px var(--color-accent); -} - -.invalid { - box-shadow: 0 3px 5px rgba(255, 77, 79, 0.18), - 0 0 0 1px var(--color-error); -} - -.invalid:focus { - box-shadow: 0 3px 5px rgba(255, 77, 79, 0.18), - 0 0 0 2px var(--color-error); -} - -.error { - font-size: var(--responsive-caption, var(--font-size-sm)); - color: var(--color-error); -} diff --git a/src/app/auth/components/auth-text-field.tsx b/src/app/auth/components/auth-text-field.tsx index 372fb7a1..45792655 100644 --- a/src/app/auth/components/auth-text-field.tsx +++ b/src/app/auth/components/auth-text-field.tsx @@ -11,8 +11,6 @@ import { forwardRef, } from "react"; -import styles from "./auth-text-field.module.css"; - export interface AuthTextFieldProps { value: string; onChange: (value: string) => void; @@ -44,14 +42,25 @@ export const AuthTextField = forwardRef( }, ref, ) { + const inputClassName = [ + "min-h-[var(--auth-field-height)] w-full rounded-full border-0 bg-auth-surface py-0 pl-xxl pr-lg text-[var(--responsive-body)] text-auth-text-primary shadow-[0_3px_5px_var(--color-auth-input-shadow)] outline-none transition-shadow duration-150 placeholder:text-auth-input-placeholder focus:shadow-[0_3px_5px_var(--color-auth-input-shadow),0_0_0_2px_var(--color-accent)] disabled:cursor-not-allowed disabled:opacity-70", + errorMessage + ? "shadow-[0_3px_5px_rgba(255,77,79,0.18),0_0_0_1px_var(--color-error)] focus:shadow-[0_3px_5px_rgba(255,77,79,0.18),0_0_0_2px_var(--color-error)]" + : "", + ] + .filter(Boolean) + .join(" "); + return ( -