diff --git a/src/components/GlassyRecordCard.css b/src/components/GlassyRecordCard.css index 28ad14f..895c5f9 100644 --- a/src/components/GlassyRecordCard.css +++ b/src/components/GlassyRecordCard.css @@ -104,6 +104,10 @@ color: var(--record-title-hover); } +.glassy-record-card--open .glassy-record-card__button { + grid-template-columns: minmax(0, 1fr); +} + .glassy-record-card__copy, .glassy-record-card__aside { min-width: 0; @@ -157,12 +161,21 @@ min-height: 1.45em; max-height: 1.45em; overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; color: var(--muted); font-size: 0.9rem; line-height: 1.45; overflow-wrap: anywhere; } +.glassy-record-card--open .glassy-record-card__summary { + max-height: none; + overflow: visible; + text-overflow: clip; + white-space: normal; +} + .glassy-record-card__association { align-self: flex-start; max-width: 100%; diff --git a/src/components/GlassyRecordCard.tsx b/src/components/GlassyRecordCard.tsx index ce3aa25..3c6a56d 100644 --- a/src/components/GlassyRecordCard.tsx +++ b/src/components/GlassyRecordCard.tsx @@ -5,7 +5,7 @@ import { ChevronDown } from "lucide-react"; import { Chip } from "@/components/Chip"; import { GlassyCard } from "@/components/GlassyCard"; import { StageChip } from "@/components/StageChip"; -import { proposalSummaryPreview } from "@/lib/textPreview"; +import { normalizePreviewText } from "@/lib/textPreview"; import { cn } from "@/lib/utils"; import type { Stage } from "@/types/stages"; import "./GlassyRecordCard.css"; @@ -40,7 +40,7 @@ export function GlassyRecordCard({ title, }: GlassyRecordCardProps) { const renderedSummary = - typeof summary === "string" ? proposalSummaryPreview(summary) : summary; + typeof summary === "string" ? normalizePreviewText(summary) : summary; return ( { + const finalSentence = "The complete final sentence remains available."; + const summary = `${"Detailed proposal context. ".repeat(16)}${finalSentence}`; + const html = renderToStaticMarkup( + + undefined} + stage="pool" + summary={summary} + title="Long proposal" + > +

Proposal details

+
+
, + ); + + assert.match(html, /The complete final sentence remains available\./); + assert.match(html, /glassy-record-card--open/); +}); + +test("record cards use visual ellipsis only while collapsed", () => { + const css = readFileSync( + join(process.cwd(), "src/components/GlassyRecordCard.css"), + "utf8", + ); + const collapsedRule = css.match( + /\.glassy-record-card__summary\s*\{([^}]*)\}/, + )?.[1]; + const expandedRule = css.match( + /\.glassy-record-card--open \.glassy-record-card__summary\s*\{([^}]*)\}/, + )?.[1]; + + assert.ok(collapsedRule); + assert.match(collapsedRule, /max-height:\s*1\.45em/); + assert.match(collapsedRule, /text-overflow:\s*ellipsis/); + assert.match(collapsedRule, /white-space:\s*nowrap/); + + assert.ok(expandedRule); + assert.match(expandedRule, /max-height:\s*none/); + assert.match(expandedRule, /overflow:\s*visible/); + assert.match(expandedRule, /white-space:\s*normal/); + + assert.match( + css, + /\.glassy-record-card--open \.glassy-record-card__button\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\)/s, + ); +}); diff --git a/tests/unit/text-preview.test.ts b/tests/unit/text-preview.test.ts index 92acac3..38bb2c1 100644 --- a/tests/unit/text-preview.test.ts +++ b/tests/unit/text-preview.test.ts @@ -1,7 +1,20 @@ import { test } from "@rstest/core"; import assert from "node:assert/strict"; -import { proposalSummaryPreview } from "../../src/lib/textPreview.ts"; +import { + normalizePreviewText, + PROPOSAL_SUMMARY_PREVIEW_MAX, + proposalSummaryPreview, +} from "../../src/lib/textPreview.ts"; + +test("normalizes preview text without shortening it", () => { + const summary = `A complete summary ${"with substantial detail ".repeat(20)}`; + + const normalized = normalizePreviewText(` ${summary}\n\n`); + + assert.equal(normalized, summary.trim()); + assert.ok(normalized.length > PROPOSAL_SUMMARY_PREVIEW_MAX); +}); test("proposal summary preview caps text without adding ellipsis", () => { const summary = [