feat(home): align desktop cards with Figma actions

- Update desktop header actions to match Figma: remove the standalone desktop favorites button and style the wallet connect pill with the wallet icon while allowing localized labels to expand.
- Replace favorite action with the Figma bookmark SVG and hover state; replace download cloud with the provided Figma SVG.
- Align official recommendation cards with the Figma card structure, colors, and bottom action row.
- Rework popular rows to the Figma desktop rhythm with 90px rows, wide thumbnails, rank area, and right-side action buttons.
- Add a dedicated desktop LatestUpdateCard for Figma-style latest-update masonry cards with flexible text-driven heights instead of fixed card heights.
This commit is contained in:
TerryM
2026-06-03 08:18:05 +08:00
parent 4f0d8925a4
commit be638e32c9
8 changed files with 464 additions and 65 deletions

View File

@@ -10,7 +10,12 @@ import { LinkPreviewCard } from "./LinkPreviewCard";
import { formatDateTime } from "./utils/formatTime";
import { FavoriteButton } from "../../favorites/FavoriteButton";
type BubbleComponent = ComponentType<{ post: Post }>;
export type MessageBubbleVariant = "default" | "latest";
type BubbleComponent = ComponentType<{
post: Post;
variant?: MessageBubbleVariant;
}>;
export function pickBubble(post: Post): BubbleComponent {
const a = post.attachments;
@@ -27,11 +32,14 @@ export function pickBubble(post: Post): BubbleComponent {
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 =
@@ -39,6 +47,7 @@ export function MessageBubble({
Bubble === VideoBubble ||
Bubble === ImageBubble ||
Bubble === ImageWithTextBubble;
const isLatestFileCard = variant === "latest" && Bubble === FileDocBubble;
return (
<div
@@ -51,28 +60,34 @@ export function MessageBubble({
>
<article
className={`relative w-full overflow-hidden rounded-2xl bg-[#272632] text-left shadow-sm ${
isVisual ? "p-0" : "px-4 py-3"
isVisual || isLatestFileCard ? "p-0" : "px-4 py-3"
}`}
>
<FavoriteButton
resourceId={post.id}
size="sm"
className="absolute right-3 top-3 z-20 shadow-lg shadow-black/30"
/>
<Bubble post={post} />
{!isLatestFileCard ? (
<FavoriteButton
resourceId={post.id}
size="sm"
className={`absolute z-20 shadow-lg shadow-black/30 ${
variant === "latest" ? "bottom-4 right-4" : "right-3 top-3"
}`}
/>
) : null}
<Bubble post={post} variant={variant} />
{post.linkPreview ? (
<div className={isVisual ? "px-4 pt-3" : "mt-3"}>
<LinkPreviewCard preview={post.linkPreview} />
</div>
) : null}
<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>
{!isLatestFileCard ? (
<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>
);