fix(home): match Figma media pills for latest cards
Some checks failed
Deploy Staging (terry-wallet-login) / deploy (push) Failing after 37s

- Align latest-update media size pills to the Figma spec: 72x24 pill, black background, 24px gray icon cell, 10px label, and the exact small Figma cloud-download SVG.
- For non-document latest cards, remove the duplicate footer download action so media cards only show per-media size/download pills plus the bookmark action, matching the Figma non-document card design.
- Keep card heights flexible so content determines the final card height instead of locking to design mock heights.
This commit is contained in:
TerryM
2026-06-03 08:33:29 +08:00
parent be638e32c9
commit 724bfb8f24

View File

@@ -147,57 +147,112 @@ function FileCard({ post, att }: { post: Post; att: Attachment }) {
); );
} }
function PillDownloadIcon() {
return (
<svg
width="12"
height="10"
viewBox="0 0 12 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M9.84528 3.61663C9.68928 1.5958 8.02426 0 6.00008 0C4.26863 0 2.78232 1.14503 2.30275 2.81502C0.934727 3.29852 0 4.6181 0 6.10918C0 8.034 1.53816 9.60013 3.42862 9.60013H9.00013C10.6544 9.60013 12.0002 8.22993 12.0002 6.54555C12.0002 5.17142 11.113 3.99191 9.84528 3.61663ZM8.0174 5.98132L6.30309 7.7268C6.21952 7.81189 6.1098 7.85465 6.00008 7.85465C5.89037 7.85465 5.78065 7.81189 5.69708 7.7268L3.98277 5.98132C3.8602 5.85652 3.82334 5.66888 3.88977 5.50568C3.9562 5.34291 4.11263 5.23644 4.28577 5.23644H5.14293V3.49096C5.14293 3.00921 5.52693 2.61822 6.00008 2.61822C6.47323 2.61822 6.85724 3.00921 6.85724 3.49096V5.23644H7.71439C7.88754 5.23644 8.04397 5.34291 8.1104 5.50568C8.17683 5.66888 8.13997 5.85652 8.0174 5.98132Z"
fill="#A8A9AE"
/>
</svg>
);
}
function MediaSizeChip({ att }: { att: Attachment }) {
return (
<div className="absolute left-3 top-2.5 z-20 flex h-6 w-[72px] items-center overflow-hidden rounded-full bg-black text-white">
<span className="grid h-6 w-6 shrink-0 place-items-center bg-[#545454]">
<PillDownloadIcon />
</span>
<span className="flex h-4 w-12 items-center justify-center text-[10px] font-medium leading-4">
{formatBytes(att.sizeBytes)}
</span>
</div>
);
}
function MediaTile({
att,
showExtra,
}: {
att: Attachment;
showExtra?: number;
}) {
const src = attachmentPreview(att);
const isVideo = att.kind === "video" || att.mime.startsWith("video/");
return (
<div className="relative h-full w-full overflow-hidden bg-black">
{src ? (
<img
src={src}
alt=""
className="h-full w-full object-cover"
loading="lazy"
decoding="async"
/>
) : null}
<MediaSizeChip att={att} />
{isVideo ? (
<div className="absolute inset-0 grid place-items-center">
<span className="grid h-16 w-16 place-items-center rounded-full bg-black/55 text-white backdrop-blur-sm">
<Play className="ml-1 h-8 w-8 fill-current" />
</span>
</div>
) : null}
{showExtra ? (
<div className="absolute inset-0 grid place-items-center bg-black/55 text-[24px] font-bold text-white backdrop-blur-sm">
+{showExtra}
</div>
) : null}
</div>
);
}
function MediaGrid({ attachments }: { attachments: Attachment[] }) { function MediaGrid({ attachments }: { attachments: Attachment[] }) {
const visible = attachments.slice(0, 4); const visible = attachments.slice(0, 4);
const extra = Math.max(0, attachments.length - visible.length); const extra = Math.max(0, attachments.length - visible.length);
if (visible.length <= 1) { if (visible.length === 0) return <div className="h-full w-full bg-black" />;
const src = attachmentPreview(visible[0]); if (visible.length === 1) return <MediaTile att={visible[0]} />;
return src ? ( if (visible.length === 2) {
<img return (
src={src} <div className="grid h-full w-full grid-cols-2 gap-0">
alt="" {visible.map((att) => (
className="h-full w-full object-cover" <MediaTile key={att.id} att={att} />
loading="lazy" ))}
decoding="async" </div>
/> );
) : ( }
<div className="h-full w-full bg-black" /> if (visible.length === 3) {
return (
<div className="grid h-full w-full grid-cols-2 gap-0">
<MediaTile att={visible[0]} />
<div className="grid h-full grid-rows-2 gap-0">
<MediaTile att={visible[1]} />
<MediaTile att={visible[2]} />
</div>
</div>
); );
} }
return ( return (
<div className="grid h-full w-full grid-cols-2 grid-rows-2 gap-0"> <div className="grid h-full w-full grid-cols-2 grid-rows-2 gap-0">
{visible.map((att, index) => ( {visible.map((att, index) => (
<div key={att.id} className="relative overflow-hidden bg-black"> <MediaTile
<img key={att.id}
src={attachmentPreview(att)} att={att}
alt="" showExtra={extra > 0 && index === visible.length - 1 ? extra : 0}
className="h-full w-full object-cover" />
loading="lazy"
decoding="async"
/>
{extra > 0 && index === visible.length - 1 ? (
<div className="absolute inset-0 grid place-items-center bg-black/55 text-[24px] font-bold text-white backdrop-blur-sm">
+{extra}
</div>
) : null}
</div>
))} ))}
</div> </div>
); );
} }
function MediaBadge({ att }: { att?: Attachment }) {
if (!att) return null;
return (
<div className="absolute left-3 top-3 z-20 inline-flex h-8 items-center overflow-hidden rounded-full bg-black/75 text-[12px] font-medium text-white shadow-lg backdrop-blur">
<span className="grid h-8 w-8 place-items-center rounded-full bg-[#3a3a40]">
<DownloadCloudIcon />
</span>
<span className="px-3">{formatBytes(att.sizeBytes)}</span>
</div>
);
}
function VisualCard({ post }: { post: Post }) { function VisualCard({ post }: { post: Post }) {
const { lang } = useI18n(); const { lang } = useI18n();
const att = post.attachments[0]; const att = post.attachments[0];
@@ -213,21 +268,13 @@ function VisualCard({ post }: { post: Post }) {
}`} }`}
> >
<MediaGrid attachments={post.attachments} /> <MediaGrid attachments={post.attachments} />
<MediaBadge att={att} />
{isVideo ? (
<div className="absolute inset-0 grid place-items-center">
<span className="grid h-14 w-14 place-items-center rounded-full bg-black/55 text-white backdrop-blur-sm">
<Play className="ml-1 h-7 w-7 fill-current" />
</span>
</div>
) : null}
</div> </div>
{text || post.title ? ( {text || post.title ? (
<div className="message-stream-copyable-text whitespace-pre-wrap break-words px-4 pt-3 text-[15px] font-medium leading-6 text-white"> <div className="message-stream-copyable-text whitespace-pre-wrap break-words px-4 pt-3 text-[15px] font-medium leading-6 text-white">
{autolink(text || post.title || "")} {autolink(text || post.title || "")}
</div> </div>
) : null} ) : null}
<Footer post={post} attachment={att} /> <Footer post={post} />
</article> </article>
); );
} }