feat(stream): bubble footer with timestamp and inline favorite/download
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.
This commit is contained in:
@@ -18,29 +18,11 @@ import { useToast } from "../../Toast";
|
||||
import { FavoriteButton } from "../../../favorites/FavoriteButton";
|
||||
import type { MessageBubbleVariant } from "../MessageBubble";
|
||||
|
||||
function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) {
|
||||
const { t } = useI18n();
|
||||
const { showToast } = useToast();
|
||||
const { showSaveToAlbumGuide } = useSaveToAlbumGuide();
|
||||
function AttachmentRow({ att }: { postId: string; att: Attachment }) {
|
||||
const { Icon, color } = fileIcon({ mime: att.mime, filename: att.filename });
|
||||
const displayFilename = filenameWithExtension(att.filename, att.mime);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [previewFailed, setPreviewFailed] = useState(false);
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (isDownloading) return;
|
||||
setIsDownloading(true);
|
||||
try {
|
||||
await downloadAttachment(postId, att.id, displayFilename);
|
||||
const mediaKind = mediaSaveKindFromAttachment(att);
|
||||
if (mediaKind) showSaveToAlbumGuide(mediaKind);
|
||||
} catch {
|
||||
showToast(t("downloadFail"), "error");
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isImage = att.kind === "image" || att.mime.startsWith("image/");
|
||||
const previewUrl =
|
||||
att.thumbnailUrl ?? att.posterUrl ?? (isImage ? att.url : undefined);
|
||||
@@ -84,25 +66,9 @@ function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) {
|
||||
})()}
|
||||
</div>
|
||||
<div className="text-[12px] font-medium leading-[19.2px] text-[#A8A9AE]">
|
||||
{isDownloading ? t("downloading") : formatBytes(att.sizeBytes)}
|
||||
{formatBytes(att.sizeBytes)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[#191921] text-white transition hover:bg-[#22232D] disabled:cursor-wait"
|
||||
aria-label={
|
||||
isDownloading ? t("downloading") : `Download ${att.filename}`
|
||||
}
|
||||
aria-busy={isDownloading}
|
||||
>
|
||||
{isDownloading ? (
|
||||
<LoaderCircle className="h-5 w-5 animate-spin" strokeWidth={2.3} />
|
||||
) : (
|
||||
<DownloadCloudIcon className="h-6 w-6" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user