refactor(app): migrate small components to tailwind
This commit is contained in:
@@ -2,6 +2,7 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
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";
|
||||
@@ -90,4 +91,29 @@ describe("chat Tailwind components", () => {
|
||||
expect(emptyHtml).toContain("text-[rgba(255,255,255,0.88)]");
|
||||
expect(disabledHtml).toContain("disabled");
|
||||
});
|
||||
|
||||
it("renders ImageBubble openable and paywalled states", () => {
|
||||
const openableHtml = renderToStaticMarkup(
|
||||
<ImageBubble
|
||||
messageId="message-1"
|
||||
imageUrl="/chat-image.png"
|
||||
onOpenImage={() => undefined}
|
||||
/>,
|
||||
);
|
||||
const paywalledHtml = renderToStaticMarkup(
|
||||
<ImageBubble imageUrl="/locked-image.png" imagePaywalled />,
|
||||
);
|
||||
|
||||
expect(openableHtml).toContain('role="button"');
|
||||
expect(openableHtml).toContain('tabindex="0"');
|
||||
expect(openableHtml).toContain('aria-label="Open image in fullscreen"');
|
||||
expect(openableHtml).toContain("rounded-tl-none");
|
||||
expect(openableHtml).toContain("block h-auto w-full object-cover");
|
||||
expect(openableHtml).toContain("data:image/png;base64,/chat-image.png");
|
||||
expect(paywalledHtml).toContain("scale-[1.04] blur-[8px]");
|
||||
expect(paywalledHtml).toContain(
|
||||
"bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]",
|
||||
);
|
||||
expect(paywalledHtml).toContain("data:image/png;base64,/locked-image.png");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/* ImageBubble 图片气泡样式 */
|
||||
|
||||
.bubble {
|
||||
position: relative;
|
||||
max-width: var(--chat-media-size, 220px);
|
||||
max-height: var(--chat-media-size, 220px);
|
||||
border-radius: var(--radius-lg, 12px);
|
||||
border-top-left-radius: 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: var(--color-bubble-background, #fff);
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.paywalled .image {
|
||||
filter: blur(8px);
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.paywallOverlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.04) 0%,
|
||||
rgba(255, 255, 255, 0.14) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.errorFallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--chat-media-size, 220px);
|
||||
height: var(--chat-media-size, 220px);
|
||||
background: var(--color-bubble-background, #fff);
|
||||
color: var(--color-text-secondary, #9e9e9e);
|
||||
font-size: var(--icon-size-xl, 24px);
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import { ChatMediaImage } from "./chat-media-image";
|
||||
import styles from "./image-bubble.module.css";
|
||||
|
||||
export interface ImageBubbleProps {
|
||||
messageId?: string;
|
||||
@@ -25,6 +24,12 @@ export function ImageBubble({
|
||||
onOpenImage,
|
||||
}: ImageBubbleProps) {
|
||||
const canOpen = Boolean(messageId && onOpenImage);
|
||||
const imageClassName = [
|
||||
"block h-auto w-full object-cover",
|
||||
imagePaywalled ? "scale-[1.04] blur-[8px]" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
const openImage = () => {
|
||||
if (!messageId || !onOpenImage) return;
|
||||
@@ -33,7 +38,7 @@ export function ImageBubble({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.bubble} ${imagePaywalled ? styles.paywalled : ""}`}
|
||||
className="relative max-h-[var(--chat-media-size,220px)] max-w-[var(--chat-media-size,220px)] cursor-pointer overflow-hidden rounded-[var(--radius-lg,12px)] rounded-tl-none bg-[var(--color-bubble-background,#fff)]"
|
||||
onClick={openImage}
|
||||
role={canOpen ? "button" : undefined}
|
||||
tabIndex={canOpen ? 0 : undefined}
|
||||
@@ -49,13 +54,16 @@ export function ImageBubble({
|
||||
<ChatMediaImage
|
||||
messageId={messageId}
|
||||
remoteUrl={imageUrl}
|
||||
className={styles.image}
|
||||
errorClassName={styles.errorFallback}
|
||||
className={imageClassName}
|
||||
errorClassName="flex size-[var(--chat-media-size,220px)] items-center justify-center bg-[var(--color-bubble-background,#fff)] text-[var(--icon-size-xl,24px)] text-[var(--color-text-secondary,#9e9e9e)]"
|
||||
width={240}
|
||||
height={240}
|
||||
>
|
||||
{imagePaywalled ? (
|
||||
<div className={styles.paywallOverlay} aria-hidden="true" />
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
</ChatMediaImage>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user