Skip to content

Commit f6f153f

Browse files
committed
feat(code): tree view for changed files list
Replace flat file list in ChangesPanel with a collapsible folder tree. Directories with a single child are compacted (e.g. "src/utils"). Extract shared TreeDirectoryRow and TreeFileRow components used by both the changes tree and the repo file tree (FileTreePanel), deduplicating directory and file row rendering. Generated-By: PostHog Code Task-Id: 60c3af6f-6f10-4722-a2c4-7a2a15303ba1
1 parent 77fd1d7 commit f6f153f

4 files changed

Lines changed: 490 additions & 264 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { FileIcon } from "@components/ui/FileIcon";
2+
import { CaretRight, FolderIcon, FolderOpenIcon } from "@phosphor-icons/react";
3+
import { Box, Flex } from "@radix-ui/themes";
4+
import type { ReactNode } from "react";
5+
6+
const TREE_ROW_ACTIVE_CLASS = "border-accent-8 border-y bg-accent-4";
7+
const TREE_ROW_INACTIVE_CLASS = "border-transparent border-y hover:bg-gray-3";
8+
const TREE_INDENT_PX = 12;
9+
const CARET_COL_SIZE = 16;
10+
11+
interface TreeDirectoryRowProps {
12+
name: string;
13+
depth: number;
14+
isExpanded: boolean;
15+
onToggle: () => void;
16+
isActive?: boolean;
17+
}
18+
19+
export function TreeDirectoryRow({
20+
name,
21+
depth,
22+
isExpanded,
23+
onToggle,
24+
isActive = false,
25+
}: TreeDirectoryRowProps) {
26+
return (
27+
<Flex
28+
align="center"
29+
gap="1"
30+
onClick={onToggle}
31+
className={isActive ? TREE_ROW_ACTIVE_CLASS : TREE_ROW_INACTIVE_CLASS}
32+
style={{
33+
paddingLeft: `${depth * TREE_INDENT_PX + 4}px`,
34+
paddingRight: "8px",
35+
height: "22px",
36+
cursor: "pointer",
37+
userSelect: "none",
38+
}}
39+
>
40+
<Box
41+
style={{
42+
width: `${CARET_COL_SIZE}px`,
43+
height: `${CARET_COL_SIZE}px`,
44+
display: "flex",
45+
alignItems: "center",
46+
justifyContent: "center",
47+
flexShrink: 0,
48+
}}
49+
>
50+
<CaretRight
51+
size={10}
52+
weight="bold"
53+
color="var(--gray-10)"
54+
style={{
55+
transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
56+
transition: "transform 0.1s ease",
57+
}}
58+
/>
59+
</Box>
60+
{isExpanded ? (
61+
<FolderOpenIcon
62+
size={14}
63+
weight="fill"
64+
color="var(--accent-9)"
65+
style={{ flexShrink: 0 }}
66+
/>
67+
) : (
68+
<FolderIcon
69+
size={14}
70+
color="var(--accent-9)"
71+
style={{ flexShrink: 0 }}
72+
/>
73+
)}
74+
<span
75+
className="select-none overflow-hidden text-ellipsis whitespace-nowrap text-[13px]"
76+
style={{ marginLeft: "4px" }}
77+
>
78+
{name}
79+
</span>
80+
</Flex>
81+
);
82+
}
83+
84+
interface TreeFileRowProps {
85+
fileName: string;
86+
depth: number;
87+
isActive?: boolean;
88+
onClick?: () => void;
89+
onDoubleClick?: () => void;
90+
onContextMenu?: (e: React.MouseEvent) => void;
91+
onMouseEnter?: () => void;
92+
onMouseLeave?: () => void;
93+
/** Extra content rendered after the filename (badges, buttons, etc.) */
94+
trailing?: ReactNode;
95+
}
96+
97+
export function TreeFileRow({
98+
fileName,
99+
depth,
100+
isActive = false,
101+
onClick,
102+
onDoubleClick,
103+
onContextMenu,
104+
onMouseEnter,
105+
onMouseLeave,
106+
trailing,
107+
}: TreeFileRowProps) {
108+
return (
109+
<Flex
110+
align="center"
111+
gap="1"
112+
onClick={onClick}
113+
onDoubleClick={onDoubleClick}
114+
onContextMenu={onContextMenu}
115+
onMouseEnter={onMouseEnter}
116+
onMouseLeave={onMouseLeave}
117+
className={isActive ? TREE_ROW_ACTIVE_CLASS : TREE_ROW_INACTIVE_CLASS}
118+
style={{
119+
paddingLeft: `${depth * TREE_INDENT_PX + 4}px`,
120+
paddingRight: "8px",
121+
height: "22px",
122+
cursor: "pointer",
123+
}}
124+
>
125+
{/* Spacer to align with folder caret column */}
126+
<Box
127+
style={{
128+
width: `${CARET_COL_SIZE}px`,
129+
height: `${CARET_COL_SIZE}px`,
130+
flexShrink: 0,
131+
}}
132+
/>
133+
<FileIcon filename={fileName} size={14} />
134+
<span
135+
className="select-none overflow-hidden text-ellipsis whitespace-nowrap text-[13px]"
136+
style={{ marginLeft: "4px", minWidth: 0, flex: 1 }}
137+
>
138+
{fileName}
139+
</span>
140+
{trailing}
141+
</Flex>
142+
);
143+
}

0 commit comments

Comments
 (0)