From e434cef83d6d4e90b4dee0cb6dc62a2eb293c899 Mon Sep 17 00:00:00 2001 From: Cyber Preacher <72062250+Cyber-preacher@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:07:07 +0400 Subject: [PATCH] Center proposal card metadata without shifting on expansion Measure the collapsed header to align pills, dates, and chevrons while preserving their position when summaries expand. Retain the stacked mobile layout and cover variable content and viewport sizes in Playwright. --- src/components/GlassyRecordCard.css | 3 + src/components/GlassyRecordCard.tsx | 39 +++++++- tests/e2e/proposal-card-alignment.spec.ts | 112 ++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/proposal-card-alignment.spec.ts diff --git a/src/components/GlassyRecordCard.css b/src/components/GlassyRecordCard.css index 4aa8d45..26ea3a3 100644 --- a/src/components/GlassyRecordCard.css +++ b/src/components/GlassyRecordCard.css @@ -86,6 +86,7 @@ .glassy-record-card__button { display: grid; + align-items: start; width: 100%; grid-template-columns: minmax(10rem, 1fr) minmax(36rem, 42rem); gap: 1rem; @@ -118,6 +119,7 @@ .glassy-record-card__aside { display: grid; align-self: start; + margin-top: var(--record-aside-offset, 0px); grid-template-columns: minmax(15rem, 1fr) minmax(8.75rem, 9.5rem) 7.75rem 1.25rem; @@ -225,6 +227,7 @@ .glassy-record-card__aside { display: flex; + margin-top: 0; flex-wrap: wrap; justify-content: flex-start; } diff --git a/src/components/GlassyRecordCard.tsx b/src/components/GlassyRecordCard.tsx index 740b0a7..35d6b62 100644 --- a/src/components/GlassyRecordCard.tsx +++ b/src/components/GlassyRecordCard.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from "react"; +import { useLayoutEffect, useRef, type ReactNode } from "react"; import { ChevronDown } from "lucide-react"; @@ -40,6 +40,42 @@ export function GlassyRecordCard({ summary, title, }: GlassyRecordCardProps) { + const headerRef = useRef(null); + useLayoutEffect(() => { + const header = headerRef.current; + if (!header) return; + const copy = header.querySelector(".glassy-record-card__copy"); + const summary = header.querySelector( + ".glassy-record-card__summary", + ); + const aside = header.querySelector( + ".glassy-record-card__aside", + ); + if (!copy || !summary || !aside) return; + + const alignMetadata = () => { + // Anchor to the collapsed header even while the full summary is visible. + const summaryLineHeight = Number.parseFloat( + getComputedStyle(summary).minHeight, + ); + const expandedHeight = Math.max( + 0, + summary.getBoundingClientRect().height - summaryLineHeight, + ); + const collapsedHeight = + copy.getBoundingClientRect().height - expandedHeight; + const offset = Math.max( + 0, + (collapsedHeight - aside.getBoundingClientRect().height) / 2, + ); + header.style.setProperty("--record-aside-offset", `${offset}px`); + }; + alignMetadata(); + const observer = new ResizeObserver(alignMetadata); + for (const element of [copy, summary, aside]) observer.observe(element); + return () => observer.disconnect(); + }, []); + const renderedSummary = typeof summary === "string" ? normalizePreviewText(summary) : summary; @@ -55,6 +91,7 @@ export function GlassyRecordCard({ )} >