Match the Figma 4206-6509 card layout for /browse: every bubble now renders a bottom row with the publish timestamp on the left and the action buttons on the right. Image, album, video, text and link cards show only the FavoriteButton; file-document cards show the FavoriteButton plus a new BubbleAttachmentDownloadButton sized to match. Removes the absolute-positioned favorite from the default variant, drops the right-aligned timestamp block, and strips the inline per-row download button from FileDocBubble's default variant since the download now lives in the footer. The 'latest' masonry variant is untouched so the home page continues to use LatestFileCard's existing internal footer.
123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
import type { ComponentType } from "react";
|
|
import type { Post } from "../../types/post";
|
|
import { TextBubble } from "./bubbles/TextBubble";
|
|
import { FileDocBubble } from "./bubbles/FileDocBubble";
|
|
import { ImageBubble } from "./bubbles/ImageBubble";
|
|
import { ImageWithTextBubble } from "./bubbles/ImageWithTextBubble";
|
|
import { AlbumBubble } from "./bubbles/AlbumBubble";
|
|
import { VideoBubble } from "./bubbles/VideoBubble";
|
|
import { LinkPreviewCard } from "./LinkPreviewCard";
|
|
import { formatDateTime } from "./utils/formatTime";
|
|
import { FavoriteButton } from "../../favorites/FavoriteButton";
|
|
import { BubbleAttachmentDownloadButton } from "./BubbleAttachmentDownloadButton";
|
|
|
|
export type MessageBubbleVariant = "default" | "latest";
|
|
|
|
type BubbleComponent = ComponentType<{
|
|
post: Post;
|
|
variant?: MessageBubbleVariant;
|
|
}>;
|
|
|
|
export function pickBubble(post: Post): BubbleComponent {
|
|
const a = post.attachments;
|
|
if (a.length === 0) return TextBubble;
|
|
if (a.length >= 2 && a.every((x) => x.kind === "image")) return AlbumBubble;
|
|
const only = a[0];
|
|
if (only.kind === "video") return VideoBubble;
|
|
if (only.kind === "image") {
|
|
return post.text ? ImageWithTextBubble : ImageBubble;
|
|
}
|
|
return FileDocBubble;
|
|
}
|
|
|
|
export function MessageBubble({
|
|
post,
|
|
fluid = false,
|
|
variant = "default",
|
|
}: {
|
|
post: Post;
|
|
/** When true, fill the parent container instead of applying the standalone
|
|
* feed max-widths. Used by the desktop 3-column masonry on the home page. */
|
|
fluid?: boolean;
|
|
/** Desktop latest-updates cards follow the dedicated Figma masonry design. */
|
|
variant?: MessageBubbleVariant;
|
|
}) {
|
|
const Bubble = pickBubble(post);
|
|
const isVisual =
|
|
Bubble === AlbumBubble ||
|
|
Bubble === VideoBubble ||
|
|
Bubble === ImageBubble ||
|
|
Bubble === ImageWithTextBubble;
|
|
const isFileBubble = Bubble === FileDocBubble;
|
|
const isLatestVariant = variant === "latest";
|
|
const isLatestFileCard = isLatestVariant && isFileBubble;
|
|
|
|
return (
|
|
<div
|
|
id={`post-${post.id}`}
|
|
className={
|
|
fluid
|
|
? "w-full scroll-mt-[82px] md:scroll-mt-[98px]"
|
|
: "mx-auto w-full max-w-[358px] scroll-mt-[82px] md:max-w-[680px] md:scroll-mt-[98px] lg:max-w-[900px] xl:max-w-[1120px]"
|
|
}
|
|
>
|
|
<article
|
|
className={`relative w-full overflow-hidden rounded-2xl bg-[#272632] text-left shadow-sm ${
|
|
isVisual || isLatestFileCard ? "p-0" : "px-4 py-3"
|
|
}`}
|
|
>
|
|
{isLatestVariant && !isFileBubble ? (
|
|
<FavoriteButton
|
|
resourceId={post.id}
|
|
size="sm"
|
|
className="absolute z-20 bottom-4 right-4 shadow-lg shadow-black/30"
|
|
/>
|
|
) : null}
|
|
|
|
<Bubble post={post} variant={variant} />
|
|
|
|
{post.linkPreview ? (
|
|
<div className={isVisual ? "px-4 pt-3" : "mt-3"}>
|
|
<LinkPreviewCard preview={post.linkPreview} />
|
|
</div>
|
|
) : null}
|
|
|
|
{!isLatestVariant ? (
|
|
<div
|
|
className={`flex items-center justify-between gap-3 ${
|
|
isVisual ? "px-4 pb-3 pt-3" : "mt-3"
|
|
}`}
|
|
>
|
|
<time
|
|
dateTime={post.publishedAt}
|
|
className="min-w-0 truncate text-[12px] leading-[19px] text-[#A8A9AE]"
|
|
>
|
|
{formatDateTime(post.publishedAt)}
|
|
</time>
|
|
<div className="flex shrink-0 items-center gap-2">
|
|
<FavoriteButton resourceId={post.id} size="sm" />
|
|
{isFileBubble && post.attachments[0] ? (
|
|
<BubbleAttachmentDownloadButton
|
|
postId={post.id}
|
|
attachment={post.attachments[0]}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
{isLatestVariant && !isFileBubble ? (
|
|
<time
|
|
dateTime={post.publishedAt}
|
|
className={`block text-right text-[12px] leading-[19px] text-[#A8A9AE] ${
|
|
isVisual ? "px-4 pb-3 pt-0.5" : "mt-3"
|
|
}`}
|
|
>
|
|
{formatDateTime(post.publishedAt)}
|
|
</time>
|
|
) : null}
|
|
</article>
|
|
</div>
|
|
);
|
|
}
|