feat: apply figma browse mobile redesign

This commit is contained in:
TerryM
2026-05-28 10:36:38 +08:00
parent 3825c4ec2f
commit 49f61b89f1
26 changed files with 401 additions and 264 deletions

View File

@@ -1,4 +1,5 @@
import { ArrowDownToLine, LoaderCircle, X } from "lucide-react";
import { LoaderCircle, X } from "lucide-react";
import { DownloadCloudIcon } from "../../icons/DownloadCloudIcon";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { useI18n } from "../../../i18n";
@@ -12,8 +13,15 @@ import { postDisplayText } from "../utils/postText";
const MAX_VISIBLE = 4;
function imageRatio(att: Attachment) {
return att.width && att.height ? `${att.width} / ${att.height}` : "4 / 3";
function albumGridClass(count: number) {
const height = "h-[230px] min-[440px]:h-[250px] md:h-[300px] lg:h-[340px]";
if (count === 2) return `${height} grid grid-cols-1 grid-rows-2`;
return `${height} grid grid-cols-2 grid-rows-2`;
}
function albumItemClass(index: number, count: number) {
if (count === 3 && index === 0) return "row-span-2";
return "";
}
function ImageListDownloadButton({
@@ -51,7 +59,7 @@ function ImageListDownloadButton({
{isDownloading ? (
<LoaderCircle className="h-4 w-4 animate-spin" strokeWidth={2.3} />
) : (
<ArrowDownToLine className="h-4 w-4" strokeWidth={2.3} />
<DownloadCloudIcon className="h-4 w-4" />
)}
</button>
);
@@ -142,54 +150,22 @@ export function AlbumBubble({ post }: { post: Post }) {
const [listOpen, setListOpen] = useState(false);
const images = post.attachments;
const text = postDisplayText(post, lang);
const shouldMerge = images.length > MAX_VISIBLE;
if (!shouldMerge) {
return (
<div className="flex flex-col gap-1.5">
{images.map((att, i) => (
<div
key={att.id}
className="relative max-h-[180px] w-full overflow-hidden rounded-xl min-[440px]:max-h-[200px] md:max-h-[240px] lg:max-h-[280px]"
>
<button
type="button"
onClick={() => openLightbox(images, i, text, post.id)}
className="block w-full"
aria-label={att.filename}
>
<img
src={att.url}
alt={att.filename}
loading="lazy"
className="h-full w-full object-cover"
style={{ aspectRatio: imageRatio(att) }}
/>
</button>
<AttachmentDownloadPill postId={post.id} attachment={att} />
</div>
))}
{text ? (
<div className="message-stream-copyable-text select-text whitespace-pre-wrap break-words text-[14px] leading-snug text-neutral-100">
{autolink(text)}
</div>
) : null}
</div>
);
}
const visible = images.slice(0, MAX_VISIBLE);
const extra = images.length - MAX_VISIBLE;
const layoutCount = Math.min(images.length, MAX_VISIBLE);
return (
<div className="flex flex-col gap-1.5">
<div className="grid h-[220px] grid-cols-2 grid-rows-2 gap-[2px] overflow-hidden rounded-xl min-[440px]:h-[250px] md:h-[300px] lg:h-[340px]">
<div className="flex flex-col">
<div className={`${albumGridClass(layoutCount)} gap-px overflow-hidden`}>
{visible.map((att, i) => {
const isLastSlot = i === MAX_VISIBLE - 1 && extra > 0;
return (
<div
key={att.id}
className="relative h-full w-full overflow-hidden"
className={`relative h-full w-full overflow-hidden ${albumItemClass(
i,
layoutCount,
)}`}
>
<button
type="button"
@@ -204,12 +180,10 @@ export function AlbumBubble({ post }: { post: Post }) {
src={att.thumbnailUrl ?? att.url}
alt={att.filename}
loading="lazy"
className={`h-full w-full object-cover ${
isLastSlot ? "blur-sm scale-105" : ""
}`}
className="h-full w-full object-cover"
/>
{isLastSlot ? (
<div className="absolute inset-0 flex items-center justify-center bg-black/45 text-3xl font-semibold text-white">
<div className="absolute inset-0 flex items-center justify-center bg-black/55 text-4xl font-bold text-white">
+{extra}
</div>
) : null}
@@ -222,7 +196,7 @@ export function AlbumBubble({ post }: { post: Post }) {
})}
</div>
{text ? (
<div className="message-stream-copyable-text select-text whitespace-pre-wrap break-words text-[14px] leading-snug text-neutral-100">
<div className="message-stream-copyable-text select-text whitespace-pre-wrap break-words px-4 pt-3 text-[14px] leading-6 text-neutral-100">
{autolink(text)}
</div>
) : null}