From b426cc7a727172132cf61b94105dba2aa032f950 Mon Sep 17 00:00:00 2001 From: chenhang Date: Sat, 11 Jul 2026 13:31:46 +0800 Subject: [PATCH] refactor(chat): migrate simple bubble styles to tailwind --- .../__tests__/tailwind-components.test.tsx | 40 +++++++++++++++++++ .../chat/components/message-avatar.module.css | 30 -------------- src/app/chat/components/message-avatar.tsx | 7 +++- .../chat/components/text-bubble.module.css | 30 -------------- src/app/chat/components/text-bubble.tsx | 14 ++++++- 5 files changed, 57 insertions(+), 64 deletions(-) create mode 100644 src/app/chat/components/__tests__/tailwind-components.test.tsx delete mode 100644 src/app/chat/components/message-avatar.module.css delete mode 100644 src/app/chat/components/text-bubble.module.css diff --git a/src/app/chat/components/__tests__/tailwind-components.test.tsx b/src/app/chat/components/__tests__/tailwind-components.test.tsx new file mode 100644 index 00000000..dce97b4f --- /dev/null +++ b/src/app/chat/components/__tests__/tailwind-components.test.tsx @@ -0,0 +1,40 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; + +import { MessageAvatar } from "../message-avatar"; +import { TextBubble } from "../text-bubble"; + +describe("chat Tailwind components", () => { + it("renders MessageAvatar AI and user branches with image utilities", () => { + const aiHtml = renderToStaticMarkup(); + const userHtml = renderToStaticMarkup( + , + ); + + expect(aiHtml).toContain('aria-label="AI avatar"'); + expect(aiHtml).toContain("size-[var(--chat-avatar-size,43px)]"); + expect(aiHtml).toContain("size-full object-cover"); + expect(aiHtml).toContain("%2Fimages%2Fchat%2Fpic-chat-elio.png"); + expect(userHtml).toContain('aria-label="User avatar"'); + expect(userHtml).toContain("%2Fuser-avatar.png"); + }); + + it("renders TextBubble AI and user visual variants", () => { + const aiHtml = renderToStaticMarkup( + , + ); + const userHtml = renderToStaticMarkup( + , + ); + + expect(aiHtml).toContain("whitespace-pre-wrap"); + expect(aiHtml).toContain("rounded-tl-none"); + expect(aiHtml).toContain("bg-white"); + expect(aiHtml).toContain("hello\nthere"); + expect(userHtml).toContain("rounded-tr-none"); + expect(userHtml).toContain( + "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]", + ); + expect(userHtml).toContain("from user"); + }); +}); diff --git a/src/app/chat/components/message-avatar.module.css b/src/app/chat/components/message-avatar.module.css deleted file mode 100644 index 8ab326fc..00000000 --- a/src/app/chat/components/message-avatar.module.css +++ /dev/null @@ -1,30 +0,0 @@ -/* MessageAvatar 头像样式 */ - -.avatar { - flex: 0 0 auto; - width: var(--chat-avatar-size, 43px); - height: var(--chat-avatar-size, 43px); - border-radius: 9999px; - overflow: hidden; - border: 2px solid var(--color-avatar-border, #fbf3f5); - box-shadow: var(--shadow-input-box, 0 1px 2px rgba(0, 0, 0, 0.1)); - background: var(--color-avatar-border, #fbf3f5); - display: flex; - align-items: center; - justify-content: center; -} - -.avatar img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.placeholder { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - font-size: var(--icon-size-xl, 24px); -} diff --git a/src/app/chat/components/message-avatar.tsx b/src/app/chat/components/message-avatar.tsx index 1a189bf8..ad3b7365 100644 --- a/src/app/chat/components/message-avatar.tsx +++ b/src/app/chat/components/message-avatar.tsx @@ -2,23 +2,26 @@ import Image from "next/image"; import { UserMessageAvatar } from "@/app/_components"; -import styles from "./message-avatar.module.css"; export interface MessageAvatarProps { isFromAI: boolean; userAvatarUrl?: string | null; } +const AVATAR_CLASS_NAME = + "flex size-[var(--chat-avatar-size,43px)] shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--color-avatar-border,#fbf3f5)] bg-[var(--color-avatar-border,#fbf3f5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]"; + export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) { if (isFromAI) { return ( -
+
Elio
); diff --git a/src/app/chat/components/text-bubble.module.css b/src/app/chat/components/text-bubble.module.css deleted file mode 100644 index bfe00367..00000000 --- a/src/app/chat/components/text-bubble.module.css +++ /dev/null @@ -1,30 +0,0 @@ -/* TextBubble 文字气泡样式 */ - -.bubble { - /* 撑满父(MessageContent column 宽)—— 让气泡占满所在半区 */ - width: 100%; - align-self: stretch; - padding: var(--spacing-3, 12px) var(--spacing-4, 16px); - border-radius: var(--radius-xxl, 24px); - font-size: var(--chat-message-font-size, var(--responsive-body, 16px)); - line-height: var(--chat-message-line-height, 1.5); - white-space: pre-wrap; - word-break: break-word; - box-shadow: var(--shadow-input-box, 0 1px 2px rgba(0, 0, 0, 0.1)); -} - -.bubbleAi { - background: #fff; - color: var(--color-text-foreground, #000); - border-top-left-radius: 0; -} - -.bubbleUser { - background: linear-gradient( - to right, - var(--color-button-gradient-start, #ff67e0), - var(--color-button-gradient-end, #ff52a2) - ); - color: #fff; - border-top-right-radius: 0; -} diff --git a/src/app/chat/components/text-bubble.tsx b/src/app/chat/components/text-bubble.tsx index 31c84fcb..84d77338 100644 --- a/src/app/chat/components/text-bubble.tsx +++ b/src/app/chat/components/text-bubble.tsx @@ -8,17 +8,27 @@ * - AI 消息:白底黑字 + 左上角尖角 * - 用户消息:渐变(primaryGradient)+ 白字 + 右上角尖角 */ -import styles from "./text-bubble.module.css"; export interface TextBubbleProps { content: string; isFromAI: boolean; } +const BASE_CLASS_NAME = + "w-full self-stretch whitespace-pre-wrap break-words rounded-[var(--radius-xxl,24px)] px-[var(--spacing-4,16px)] py-[var(--spacing-3,12px)] text-[length:var(--chat-message-font-size,var(--responsive-body,16px))] leading-[var(--chat-message-line-height,1.5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]"; + +const AI_CLASS_NAME = + "rounded-tl-none bg-white text-[var(--color-text-foreground,#000)]"; + +const USER_CLASS_NAME = + "rounded-tr-none bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-white"; + export function TextBubble({ content, isFromAI }: TextBubbleProps) { return (
{content}