Files
Arkie-Library-Frontend/src/components/messageStream/bubbles/AlbumBubble.tsx
TerryM 92e6ce9dd8 feat(video): explicit fullscreen button, simplify download pill sizing
- 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.
2026-05-30 01:21:35 +08:00

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>
);
}