diff --git a/src/app/_components/core/__tests__/tailwind-components.test.tsx b/src/app/_components/core/__tests__/tailwind-components.test.tsx
new file mode 100644
index 00000000..7dcdaaca
--- /dev/null
+++ b/src/app/_components/core/__tests__/tailwind-components.test.tsx
@@ -0,0 +1,21 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+
+import { LoadingIndicator } from "../loading-indicator";
+
+describe("core Tailwind components", () => {
+ it("renders LoadingIndicator with accessible status and dynamic sizing", () => {
+ const html = renderToStaticMarkup(
+ ,
+ );
+
+ expect(html).toContain('role="status"');
+ expect(html).toContain('aria-label="Loading"');
+ expect(html).toContain("animate-spin");
+ expect(html).toContain("width:24px");
+ expect(html).toContain("height:24px");
+ expect(html).toContain("border-width:2px");
+ expect(html).toContain("border-color:#f00 transparent transparent transparent");
+ expect(html).toContain("Loading now");
+ });
+});
diff --git a/src/app/_components/core/loading-indicator.module.css b/src/app/_components/core/loading-indicator.module.css
deleted file mode 100644
index e9ed37a5..00000000
--- a/src/app/_components/core/loading-indicator.module.css
+++ /dev/null
@@ -1,26 +0,0 @@
-.wrapper {
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- gap: var(--spacing-sm);
-}
-
-.spinner {
- display: inline-block;
- border-style: solid;
- border-color: var(--color-text-secondary) transparent transparent
- transparent;
- border-radius: 50%;
- animation: spin 0.8s linear infinite;
-}
-
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-.label {
- color: var(--color-text-secondary);
- font-size: var(--font-size-sm);
-}
diff --git a/src/app/_components/core/loading-indicator.tsx b/src/app/_components/core/loading-indicator.tsx
index 0593f270..f6ed3dd8 100644
--- a/src/app/_components/core/loading-indicator.tsx
+++ b/src/app/_components/core/loading-indicator.tsx
@@ -7,10 +7,10 @@
* 行为对齐 Flutter `CircularProgressIndicator`:
* - `size`:圆圈直径(默认 32)
* - `strokeWidth`:环宽(默认 3)
- * - `color`:环颜色(默认 `--color-accent`)
+ * - `color`:环颜色(默认 `--color-text-secondary`)
* - `label`:可选的可视标签(环形下方文本)
*/
-import styles from "./loading-indicator.module.css";
+import type { CSSProperties } from "react";
export interface LoadingIndicatorProps {
size?: number;
@@ -25,16 +25,28 @@ export function LoadingIndicator({
color,
label,
}: LoadingIndicatorProps) {
- const style: React.CSSProperties = {
+ const spinnerColor = color ?? "var(--color-text-secondary)";
+ const style: CSSProperties = {
width: size,
height: size,
borderWidth: strokeWidth,
- ...(color ? { borderTopColor: color } : {}),
+ borderColor: `${spinnerColor} transparent transparent transparent`,
};
return (
-
-
- {label ?
{label} : null}
+
+
+ {label ? (
+
+ {label}
+
+ ) : null}
);
}
diff --git a/src/app/auth/components/__tests__/tailwind-components.test.tsx b/src/app/auth/components/__tests__/tailwind-components.test.tsx
new file mode 100644
index 00000000..91fede1a
--- /dev/null
+++ b/src/app/auth/components/__tests__/tailwind-components.test.tsx
@@ -0,0 +1,15 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+
+import { AuthErrorMessage } from "../auth-error-message";
+
+describe("auth Tailwind components", () => {
+ it("renders AuthErrorMessage only when a message is present", () => {
+ expect(renderToStaticMarkup(
)).toBe("");
+
+ const html = renderToStaticMarkup(
);
+ expect(html).toContain('role="alert"');
+ expect(html).toContain("text-error");
+ expect(html).toContain("Invalid email");
+ });
+});
diff --git a/src/app/auth/components/auth-error-message.module.css b/src/app/auth/components/auth-error-message.module.css
deleted file mode 100644
index 24854701..00000000
--- a/src/app/auth/components/auth-error-message.module.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- *
- * 视觉规格(与 Dart 对齐):
- * - 细小红字,无背景,无圆角,无边框
- * - 12px 居中
- */
-.banner {
- width: 100%;
- padding: 0;
- background: transparent;
- border: none;
- color: var(--color-error);
- font-size: var(--responsive-caption, var(--font-size-sm));
- line-height: 1.4;
- text-align: center;
-}
diff --git a/src/app/auth/components/auth-error-message.tsx b/src/app/auth/components/auth-error-message.tsx
index 8930edb0..824d4a2f 100644
--- a/src/app/auth/components/auth-error-message.tsx
+++ b/src/app/auth/components/auth-error-message.tsx
@@ -4,12 +4,13 @@
*
*
*/
-import styles from "./auth-error-message.module.css";
-
export function AuthErrorMessage({ message }: { message: string | null }) {
if (!message) return null;
return (
-
+
{message}
);
diff --git a/src/app/chat/components/__tests__/pwa-install-overlay.test.tsx b/src/app/chat/components/__tests__/pwa-install-overlay.test.tsx
new file mode 100644
index 00000000..d2e24cca
--- /dev/null
+++ b/src/app/chat/components/__tests__/pwa-install-overlay.test.tsx
@@ -0,0 +1,10 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+
+import { PwaInstallOverlay } from "../pwa-install-overlay";
+
+describe("PwaInstallOverlay", () => {
+ it("does not render visible markup", () => {
+ expect(renderToStaticMarkup(
)).toBe("");
+ });
+});
diff --git a/src/app/chat/components/pwa-install-overlay.module.css b/src/app/chat/components/pwa-install-overlay.module.css
deleted file mode 100644
index 85554c11..00000000
--- a/src/app/chat/components/pwa-install-overlay.module.css
+++ /dev/null
@@ -1,5 +0,0 @@
-/* PwaInstallOverlay PWA 安装弹窗触发器样式(无可见 UI,逻辑触发用) */
-
-.hidden {
- display: none;
-}
diff --git a/src/app/chat/components/pwa-install-overlay.tsx b/src/app/chat/components/pwa-install-overlay.tsx
index 1bc551d9..911ad1b5 100644
--- a/src/app/chat/components/pwa-install-overlay.tsx
+++ b/src/app/chat/components/pwa-install-overlay.tsx
@@ -22,7 +22,6 @@ import {
import { pwaUtil } from "@/utils";
import { PwaInstallDialog } from "./pwa-install-dialog";
-import styles from "./pwa-install-overlay.module.css";
const DEVELOPMENT_DIALOG_DELAY_MS = 1000;
const PRODUCTION_DIALOG_DELAY_MS = 1500;
@@ -74,7 +73,7 @@ export function PwaInstallOverlay({ enabled }: PwaInstallOverlayProps) {
};
}, [enabled]);
- return
;
+ return null;
}
async function shouldShowPwaInstallDialog(isDevelopment: boolean) {