fix(post-detail): actually center carousel images (previous fix was a no-op)

The items-center/justify-center I added to CarouselItem in the last
commit had no effect — verified by building an isolated static HTML repro
of the exact carousel.tsx DOM structure (embla-free, no auth needed) and
testing it directly in a browser, since PostDetailDialog itself sits
behind Clerk auth and can't be exercised locally.

Root cause was one level higher than CarouselItem: CarouselContent's own
ref div (src/components/ui/carousel.tsx) is hardcoded to
"overflow-hidden" with no height/flex class ever reaching it — nothing
propagates the media column's definite height down to it, so it renders
at its own auto/content height and just sits at the top of the Carousel
element's box via normal block flow. CarouselItem's centering was
already correctly placed, but had no gap to center within, since the
box it was centering into never itself moved off the top.

Fix: attach centering to the Carousel component's own outermost element
instead — the deepest point reachable via props, since CarouselContent's
outer div doesn't forward a className. Used CSS Grid (grid + items-center)
rather than flex specifically because grid's default justify-items:stretch
preserves the ref div's full width (required for embla's horizontal
scroll-snap math); flex's shrink-to-content default for un-stretched
items would likely have broken horizontal scrolling between images.

Verified via the isolated repro: short/wide image now centers vertically
within the media column, and the "always full width" marker stayed
100% wide across all three test cases (single tall, single wide, mixed
pair in one row).
This commit is contained in:
2026-08-14 20:31:21 +02:00
parent aabb7d3074
commit a070b37e22
+13 -3
View File
@@ -264,7 +264,17 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD
isCarousel ? (
<div className="relative w-full h-full">
<AiDisclosureBadge value={post.aiDisclosure} className="h-8" />
<Carousel setApi={setCarouselApi} className="h-full">
{/* grid items-center (not flex) on purpose: CarouselContent's own
ref div (src/components/ui/carousel.tsx) is hardcoded to
"overflow-hidden" with no height/flex class reaching it, so it
never fills this element's height — shorter images in a
mixed-aspect-ratio carousel rendered pinned to the top instead
of centered. This is the deepest point we can attach centering
to from outside. Grid over flex specifically because grid's
default justify-items:stretch keeps the ref div's width at
100% (required for embla's horizontal scroll math); flex's
shrink-to-content main-axis default would have broken that. */}
<Carousel setApi={setCarouselApi} className="grid h-full items-center">
<CarouselContent className="h-full">
{images.map((img) => (
<CarouselItem key={img.id} className="basis-full h-full flex items-center justify-center">
@@ -272,8 +282,8 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD
src={getMediaUrl(img.storageKey) ?? ""}
blurhash={img.blurhash}
alt={currentCaption ?? `${pet.name}'s post`}
className="block w-full h-full object-contain"
wrapperClassName="relative block w-full h-full"
className="block max-h-full max-w-full object-contain"
wrapperClassName="relative block w-fit h-fit"
/>
</CarouselItem>
))}