Files
legal-ai/web-ui/src/components/compose/subsection-card.tsx
Chaim ca6ec48580 Render research prose as Markdown with RTL tables
The analysis-and-research.md content has markdown tables (ציר דיוני)
and inline formatting like **label:** strong runs, which were
rendering as raw pipes and dashes because the compose page used
whitespace-pre-line on plain text.

Add a reusable <Markdown> component backed by react-markdown +
remark-gfm with a custom `components` map that styles paragraphs,
lists, blockquotes, strong runs, and especially GFM tables for RTL
+ aligned columns:

- table: table-auto + border-collapse, wrapped in overflow-x-auto
  so very wide tables don't push the parent card out
- th: whitespace-nowrap so the header row sets column widths and
  every row border lines up row-to-row
- everything text-right + the whole block gets dir="rtl" at the root

Use it in three places on the compose screen:
- ProseSection (represented_party, procedural_background,
  agreed_facts, disputed_facts)
- Conclusions card
- SubsectionCard field content — threshold_claims and issues have
  the same markdown shape in their fields[]

react-markdown + remark-gfm added (~30KB gzipped).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 19:43:37 +00:00

109 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { ChevronDown } from "lucide-react";
import { ChairEditor } from "@/components/compose/chair-editor";
import { PrecedentsSection } from "@/components/compose/precedents-section";
import { Markdown } from "@/components/ui/markdown";
import type { ResearchSubsection } from "@/lib/api/research";
import type { CasePrecedent } from "@/lib/api/precedents";
import type { PracticeArea } from "@/lib/practice-area";
export function SubsectionCard({
caseNumber,
item,
defaultOpen = false,
precedents = [],
practiceArea,
}: {
caseNumber: string;
item: ResearchSubsection;
defaultOpen?: boolean;
precedents?: CasePrecedent[];
practiceArea?: PracticeArea | null;
}) {
const [open, setOpen] = useState(defaultOpen);
const isFilled = Boolean(item.chair_position?.trim());
return (
<article className="rounded-lg border border-rule bg-surface shadow-sm overflow-hidden">
<button
type="button"
onClick={() => setOpen((o) => !o)}
className="
w-full flex items-center gap-3 px-4 py-3 text-right
hover:bg-gold-wash/30 transition-colors
focus:outline-none focus-visible:bg-gold-wash/40
"
aria-expanded={open}
>
<span
className="
inline-flex items-center justify-center shrink-0
w-7 h-7 rounded-full
bg-navy text-parchment font-display font-bold text-sm
tabular-nums
"
>
{item.number}
</span>
<span className="flex-1 text-navy font-semibold text-base leading-snug">
{item.title}
</span>
<span
className={`
text-[0.72rem] rounded-full px-2.5 py-0.5 border shrink-0
${
isFilled
? "bg-success-bg text-success border-success/40"
: "bg-rule-soft text-ink-muted border-rule"
}
`}
>
{isFilled ? "✓ עמדה נקבעה" : "ממתין לעמדה"}
</span>
<ChevronDown
className={`w-4 h-4 text-ink-muted transition-transform ${open ? "rotate-180" : ""}`}
aria-hidden
/>
</button>
{open && (
<div className="border-t border-rule px-5 py-4 space-y-4 bg-parchment/40">
{item.fields.length > 0 && (
<dl className="space-y-3">
{item.fields.map((f, i) => (
<div key={i}>
<dt className="text-[0.72rem] uppercase tracking-wider text-gold-deep font-semibold mb-1">
{f.label}
</dt>
<dd>
<Markdown content={f.content} />
</dd>
</div>
))}
</dl>
)}
<ChairEditor
caseNumber={caseNumber}
sectionId={item.id}
initialValue={item.chair_position ?? ""}
/>
<div className="border-t border-rule pt-4 mt-2">
<h4 className="text-[0.78rem] uppercase tracking-wider text-gold-deep font-semibold mb-2">
פסיקה תומכת
</h4>
<PrecedentsSection
caseNumber={caseNumber}
sectionId={item.id}
precedents={precedents}
practiceArea={practiceArea}
emptyHelperText="עדיין לא צורפה פסיקה לסעיף זה"
/>
</div>
</div>
)}
</article>
);
}