- VideoBubble: add a Maximize2 fullscreen button at the top-right of the inline player (mirrors the download pill on the left), so the user explicitly opts into fullscreen instead of any tile click being promoted to one. Removed the outer onClick that opened fullscreen on any tap. - AttachmentDownloadPill: drop the adaptive (cqw-based) sizing branch and the corresponding 'adaptive' prop. The pill is now a uniform 30px tall in every host (album tile, single image, video tile), matching the design without needing query containers on parents. - AlbumBubble: remove the now-unused 'adaptive' prop and the containerType: inline-size hook that supported it.
96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
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 (
|
|
<div className="flex flex-col">
|
|
{/* 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). */}
|
|
<div
|
|
className="relative w-full overflow-hidden bg-black"
|
|
style={{
|
|
aspectRatio: layout?.aspectRatio,
|
|
maxHeight: ALBUM_MAX_HEIGHT,
|
|
}}
|
|
>
|
|
{visible.map((att, i) => {
|
|
const isLastSlot = i === MAX_VISIBLE - 1 && extra > 0;
|
|
const tile = layout?.tiles[i];
|
|
if (!tile) return null;
|
|
return (
|
|
<div
|
|
key={att.id}
|
|
className="absolute overflow-hidden"
|
|
style={{
|
|
left: `${tile.left * 100}%`,
|
|
top: `${tile.top * 100}%`,
|
|
width: `calc(${tile.width * 100}% - ${ALBUM_GAP}px)`,
|
|
height: `calc(${tile.height * 100}% - ${ALBUM_GAP}px)`,
|
|
}}
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={() => openLightbox(images, i, text, post.id)}
|
|
className="group block h-full w-full"
|
|
aria-label={
|
|
isLastSlot ? `View all ${images.length} images` : "View image"
|
|
}
|
|
>
|
|
<BubbleImage
|
|
src={sources[i]}
|
|
fallbackSrc={[att.thumbUrl, att.url]}
|
|
loading="lazy"
|
|
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.03]"
|
|
/>
|
|
{isLastSlot ? (
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center gap-0.5 bg-black/60 text-white backdrop-blur-[1px] transition group-hover:bg-black/50">
|
|
<span className="text-3xl font-bold leading-none md:text-4xl">
|
|
+{extra}
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
</button>
|
|
{!isLastSlot ? (
|
|
<AttachmentDownloadPill postId={post.id} attachment={att} />
|
|
) : null}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
{text ? (
|
|
<CollapsibleText
|
|
wrapperClassName="px-4 pt-3"
|
|
className="message-stream-copyable-text select-text whitespace-pre-wrap break-words text-[14px] leading-6 text-neutral-100"
|
|
>
|
|
{autolink(text)}
|
|
</CollapsibleText>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|