fix(post-detail): fix carousel image centering at the actual root cause

Two prior attempts today (aabb7d3, a070b37) both failed in production
despite each being verified in isolation — confirmed via DevTools that
the deployed DOM/CSS exactly matched what had tested correctly, yet the
real page still rendered short images pinned to the top.

The real, unfixable-from-outside root cause: CarouselContent's own ref
div (src/components/ui/carousel.tsx) is hardcoded to className=
"overflow-hidden" with no way to reach it via props, so it never gets an
explicit height — every centering attempt built on top of it (grid,
flex, items-center at various levels) still bottomed out on that div
having only an indefinite, percentage-against-an-auto-parent height,
which is a genuinely ambiguous CSS case browsers can resolve
inconsistently.

Fix: give CarouselContent a new optional viewportClassName prop that
reaches that specific div, and pass h-full through it from
PostDetailDialog. Carousel's own root div already has a definite height
(traced up through the dialog's fixed dvh values), so h-full on the ref
div now resolves to a real pixel value instead of falling back to auto —
eliminating the ambiguity instead of working around it. Carousel/
CarouselItem revert to plain h-full + flex items-center/justify-center,
no grid trick needed anymore.

Re-verified via an isolated static HTML/CSS repro (embla-free, no auth
needed) before deploying this time.
This commit is contained in:
admin committed 2026-08-15 09:02:36 +02:00
1 parent a070b37e22
commit 4c1931fd64
2 files changed
+19 -14

No files matched your search

+13 -12
View File
@@ -264,18 +264,19 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD
isCarousel ? (
<div className="relative w-full h-full">
<AiDisclosureBadge value={post.aiDisclosure} className="h-8" />
{/* 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">
{/* CarouselContent's own ref div (src/components/ui/carousel.tsx)
was hardcoded to "overflow-hidden" with no way to give it a
height — every className/grid/flex trick attached to its
ancestors or CarouselItem still had nothing but an indefinite
(percentage-against-auto-parent) height to work with at that
div specifically, so shorter images in a mixed-aspect-ratio
carousel rendered pinned to the top instead of centered no
matter what. Fixed at the source via CarouselContent's new
viewportClassName prop, which reaches that div directly —
h-full there now resolves against Carousel's own (genuinely
definite) height instead of falling back to auto. */}
<Carousel setApi={setCarouselApi} className="h-full">
<CarouselContent viewportClassName="h-full" className="h-full">
{images.map((img) => (
<CarouselItem key={img.id} className="basis-full h-full flex items-center justify-center">
<BlurImage
+6 -2
View File
@@ -132,13 +132,17 @@ function Carousel({
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
function CarouselContent({
className,
viewportClassName,
...props
}: React.ComponentProps<"div"> & { viewportClassName?: string }) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
className={cn("overflow-hidden", viewportClassName)}
data-slot="carousel-content"
>
<div