feat(18-03): create DecisionList component for initiative detail

Collapsible list of key decisions with expand/collapse per item and
show more/less toggle when exceeding maxVisible threshold. Renders
in a Card with empty-state placeholder.
This commit is contained in:
Lukas May
2026-02-04 21:32:50 +01:00
parent 8cfc197378
commit 3baba49edd

View File

@@ -0,0 +1,96 @@
import { useState } from "react";
import { ChevronDown, ChevronRight } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
export interface Decision {
topic: string;
decision: string;
reason: string;
}
interface DecisionListProps {
decisions: Decision[];
maxVisible?: number;
}
export function DecisionList({ decisions, maxVisible = 3 }: DecisionListProps) {
const [expandedIndices, setExpandedIndices] = useState<Set<number>>(
new Set()
);
const [showAll, setShowAll] = useState(false);
function toggleDecision(index: number) {
setExpandedIndices((prev) => {
const next = new Set(prev);
if (next.has(index)) {
next.delete(index);
} else {
next.add(index);
}
return next;
});
}
const visibleDecisions = showAll
? decisions
: decisions.slice(0, maxVisible);
const hasMore = decisions.length > maxVisible;
return (
<Card>
<CardHeader>
<CardTitle className="text-base">Key Decisions</CardTitle>
</CardHeader>
<CardContent>
{decisions.length === 0 ? (
<p className="text-sm text-muted-foreground">
No decisions recorded
</p>
) : (
<div className="space-y-1">
{visibleDecisions.map((d, i) => {
const isExpanded = expandedIndices.has(i);
return (
<div key={i}>
<button
type="button"
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm font-medium hover:bg-accent/50"
onClick={() => toggleDecision(i)}
>
{isExpanded ? (
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
) : (
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
{d.topic}
</button>
{isExpanded && (
<div className="ml-8 space-y-1 pb-2 text-sm">
<p>{d.decision}</p>
<p className="text-muted-foreground">
Reason: {d.reason}
</p>
</div>
)}
</div>
);
})}
{hasMore && (
<Button
variant="ghost"
size="sm"
className="mt-1 w-full"
onClick={() => setShowAll((prev) => !prev)}
>
{showAll
? "Show less"
: `Show ${decisions.length - maxVisible} more`}
</Button>
)}
</div>
)}
</CardContent>
</Card>
);
}