From a5eca10966482a0458b093958f762029343575ba Mon Sep 17 00:00:00 2001 From: "[dyad]" Date: Wed, 21 Jan 2026 08:45:03 +0100 Subject: [PATCH] [dyad] Add collapse/expand all to headlines - wrote 1 file(s) --- src/components/headline-tree.tsx | 80 +++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/src/components/headline-tree.tsx b/src/components/headline-tree.tsx index ac1acec..8a7968b 100644 --- a/src/components/headline-tree.tsx +++ b/src/components/headline-tree.tsx @@ -6,24 +6,48 @@ import type { HeadlineNode } from "@/app/actions"; import { KeywordHighlighter } from "./keyword-highlighter"; import { ChevronDown } from "lucide-react"; import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; interface HeadlineTreeProps { headlines: HeadlineNode[]; keyword?: string | null; } +const getCollapsiblePaths = ( + nodes: HeadlineNode[], + parentPath = "" +): string[] => { + let paths: string[] = []; + nodes.forEach((node, index) => { + const currentPath = parentPath ? `${parentPath}-${index}` : `${index}`; + if (node.level === 2 && node.children && node.children.length > 0) { + paths.push(currentPath); + } + if (node.children && node.children.length > 0) { + paths = paths.concat(getCollapsiblePaths(node.children, currentPath)); + } + }); + return paths; +}; + const HeadlineNodeDisplay = ({ node, level, + path, keyword, + collapsedStates, + onToggle, }: { node: HeadlineNode; level: number; + path: string; keyword?: string | null; + collapsedStates: Record; + onToggle: (path: string) => void; }) => { const isCollapsible = node.level === 2 && node.children && node.children.length > 0; - const [isCollapsed, setIsCollapsed] = useState(true); + const isCollapsed = collapsedStates[path]; return ( <> @@ -33,7 +57,7 @@ const HeadlineNodeDisplay = ({ isCollapsible && "cursor-pointer hover:bg-muted/50" )} style={{ paddingLeft: `${16 + level * 24}px` }} - onClick={() => isCollapsible && setIsCollapsed(!isCollapsed)} + onClick={() => isCollapsible && onToggle(path)} > ))} @@ -71,15 +98,64 @@ const HeadlineNodeDisplay = ({ }; export function HeadlineTree({ headlines, keyword }: HeadlineTreeProps) { + const [collapsedStates, setCollapsedStates] = useState< + Record + >(() => { + const initialStates: Record = {}; + const paths = getCollapsiblePaths(headlines); + paths.forEach((path) => { + initialStates[path] = true; + }); + return initialStates; + }); + + const handleToggle = (path: string) => { + setCollapsedStates((prev) => ({ + ...prev, + [path]: !prev[path], + })); + }; + + const collapseAll = () => { + const newStates: Record = {}; + Object.keys(collapsedStates).forEach((path) => { + newStates[path] = true; + }); + setCollapsedStates(newStates); + }; + + const expandAll = () => { + const newStates: Record = {}; + Object.keys(collapsedStates).forEach((path) => { + newStates[path] = false; + }); + setCollapsedStates(newStates); + }; + + const hasCollapsibleNodes = Object.keys(collapsedStates).length > 0; + return (
+ {hasCollapsibleNodes && ( +
+ + +
+ )}
{headlines.map((headline, index) => ( ))}