import { useI18n } from "../../../i18n"; import type { Post } from "../../../types/post"; import { ALBUM_GAP, ALBUM_MAX_HEIGHT } from "../../../constants/media"; import { AttachmentDownloadPill } from "../AttachmentDownloadPill"; import { BubbleImage } from "../BubbleImage"; import { useImageRatios } from "../hooks/useImageRatios"; import { useLightbox } from "../overlays/ImageLightbox"; import { autolink } from "../utils/autolink"; import { computeAlbumLayout } from "../utils/albumLayout"; import { postDisplayText } from "../utils/postText"; import { CollapsibleText } from "../CollapsibleText"; const MAX_VISIBLE = 4; export function AlbumBubble({ post }: { post: Post }) { const { openLightbox } = useLightbox(); const { lang } = useI18n(); const images = post.attachments; const text = postDisplayText(post, lang); const visible = images.slice(0, MAX_VISIBLE); const extra = images.length - MAX_VISIBLE; const sources = visible.map( (att) => att.thumbnailUrl ?? att.thumbUrl ?? att.url, ); const ratios = useImageRatios(visible, sources); const layout = computeAlbumLayout(ratios); return (
{/* aspect-ratio sets a definite box height; tiles are absolutely positioned by percentage so the mosaic fills it exactly (no CSS-grid `fr` quirks, no leftover black band). */}
{visible.map((att, i) => { const isLastSlot = i === MAX_VISIBLE - 1 && extra > 0; const tile = layout?.tiles[i]; if (!tile) return null; return (
{!isLastSlot ? ( ) : null}
); })}
{text ? ( {autolink(text)} ) : null}
); }