Skip to content

Commit 41b1b0b

Browse files
committed
🤖 refactor: Convert FileTree to Tailwind CSS
- Replace all styled-components with Tailwind utility classes - Use cn() utility for conditional styling - Preserve all data attributes and event handlers - Keep dynamic padding calculation via inline style - Maintain expand/collapse animation and read state styling - ~25% code reduction (403 lines)
1 parent 803b0cf commit 41b1b0b

File tree

1 file changed

+65
-164
lines changed

1 file changed

+65
-164
lines changed

src/components/RightSidebar/CodeReview/FileTree.tsx

Lines changed: 65 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -3,149 +3,10 @@
33
*/
44

55
import React from "react";
6-
import styled from "@emotion/styled";
76
import type { FileTreeNode } from "@/utils/git/numstatParser";
87
import { usePersistedState } from "@/hooks/usePersistedState";
98
import { getFileTreeExpandStateKey } from "@/constants/storage";
10-
11-
const TreeContainer = styled.div`
12-
flex: 1;
13-
min-height: 0;
14-
padding: 12px;
15-
overflow-y: auto;
16-
font-family: var(--font-monospace);
17-
font-size: 12px;
18-
`;
19-
20-
const TreeNode = styled.div<{ depth: number; isSelected: boolean }>`
21-
padding: 4px 8px;
22-
padding-left: ${(props) => props.depth * 16 + 8}px;
23-
cursor: pointer;
24-
user-select: none;
25-
display: flex;
26-
align-items: center;
27-
gap: 8px;
28-
background: ${(props) => (props.isSelected ? "rgba(100, 150, 255, 0.2)" : "transparent")};
29-
border-radius: 4px;
30-
margin: 2px 0;
31-
32-
&:hover {
33-
background: ${(props) =>
34-
props.isSelected ? "rgba(100, 150, 255, 0.2)" : "rgba(255, 255, 255, 0.05)"};
35-
}
36-
`;
37-
38-
const FileName = styled.span<{ isFullyRead?: boolean; isUnknownState?: boolean }>`
39-
color: #ccc;
40-
flex: 1;
41-
${(props) =>
42-
props.isFullyRead &&
43-
`
44-
color: #666;
45-
text-decoration: line-through;
46-
text-decoration-color: var(--color-read);
47-
text-decoration-thickness: 2px;
48-
`}
49-
${(props) =>
50-
props.isUnknownState &&
51-
!props.isFullyRead &&
52-
`
53-
color: #666;
54-
`}
55-
`;
56-
57-
const DirectoryName = styled.span<{ isFullyRead?: boolean; isUnknownState?: boolean }>`
58-
color: #888;
59-
flex: 1;
60-
${(props) =>
61-
props.isFullyRead &&
62-
`
63-
color: #666;
64-
text-decoration: line-through;
65-
text-decoration-color: var(--color-read);
66-
text-decoration-thickness: 2px;
67-
`}
68-
${(props) =>
69-
props.isUnknownState &&
70-
!props.isFullyRead &&
71-
`
72-
color: #666;
73-
`}
74-
`;
75-
76-
const DirectoryStats = styled.span<{ isOpen: boolean }>`
77-
display: flex;
78-
gap: 8px;
79-
font-size: 11px;
80-
color: ${(props) => (props.isOpen ? "#666" : "inherit")};
81-
opacity: 0.7;
82-
`;
83-
84-
const Stats = styled.span`
85-
display: flex;
86-
gap: 8px;
87-
font-size: 11px;
88-
`;
89-
90-
const Additions = styled.span`
91-
color: #4ade80;
92-
`;
93-
94-
const Deletions = styled.span`
95-
color: #f87171;
96-
`;
97-
98-
const ToggleIcon = styled.span<{ isOpen: boolean }>`
99-
width: 12px;
100-
display: inline-block;
101-
transform: ${(props) => (props.isOpen ? "rotate(90deg)" : "rotate(0deg)")};
102-
transition: transform 0.2s ease;
103-
`;
104-
105-
const ClearButton = styled.button`
106-
padding: 2px 8px;
107-
background: transparent;
108-
color: #888;
109-
border: none;
110-
border-radius: 3px;
111-
font-size: 11px;
112-
cursor: pointer;
113-
transition: all 0.2s ease;
114-
font-family: var(--font-primary);
115-
margin-left: auto;
116-
117-
&:hover {
118-
background: rgba(255, 255, 255, 0.05);
119-
color: #ccc;
120-
}
121-
`;
122-
123-
const TreeHeader = styled.div`
124-
padding: 8px 12px;
125-
border-bottom: 1px solid #3e3e42;
126-
font-size: 12px;
127-
font-weight: 500;
128-
color: #ccc;
129-
font-family: var(--font-primary);
130-
display: flex;
131-
align-items: center;
132-
gap: 8px;
133-
`;
134-
135-
const CommonPrefix = styled.div`
136-
padding: 6px 12px;
137-
background: #1e1e1e;
138-
border-bottom: 1px solid #3e3e42;
139-
font-size: 11px;
140-
color: #888;
141-
font-family: var(--font-monospace);
142-
`;
143-
144-
const EmptyState = styled.div`
145-
padding: 20px;
146-
color: #888;
147-
text-align: center;
148-
`;
9+
import { cn } from "@/lib/utils";
14910

15011
/**
15112
* Compute read status for a directory by recursively checking all descendant files
@@ -265,48 +126,77 @@ const TreeNodeContent: React.FC<{
265126

266127
return (
267128
<>
268-
<TreeNode depth={depth} isSelected={isSelected} onClick={handleClick}>
129+
<div
130+
className={cn(
131+
"py-1 px-2 cursor-pointer select-none flex items-center gap-2 rounded my-0.5",
132+
isSelected ? "bg-[rgba(100,150,255,0.2)]" : "bg-transparent hover:bg-white/5"
133+
)}
134+
style={{ paddingLeft: `${depth * 16 + 8}px` }}
135+
onClick={handleClick}
136+
>
269137
{node.isDirectory ? (
270138
<>
271-
<ToggleIcon isOpen={isOpen} data-toggle onClick={handleToggleClick}>
139+
<span
140+
className="w-3 inline-block transition-transform duration-200"
141+
style={{ transform: isOpen ? "rotate(90deg)" : "rotate(0deg)" }}
142+
data-toggle
143+
onClick={handleToggleClick}
144+
>
272145
273-
</ToggleIcon>
274-
<DirectoryName isFullyRead={isFullyRead} isUnknownState={isUnknownState}>
146+
</span>
147+
<span
148+
className={cn(
149+
"flex-1",
150+
isFullyRead && "text-[#666] line-through [text-decoration-color:var(--color-read)] [text-decoration-thickness:2px]",
151+
isUnknownState && !isFullyRead && "text-[#666]",
152+
!isFullyRead && !isUnknownState && "text-[#888]"
153+
)}
154+
>
275155
{node.name || "/"}
276-
</DirectoryName>
156+
</span>
277157
{node.totalStats &&
278158
(node.totalStats.additions > 0 || node.totalStats.deletions > 0) && (
279-
<DirectoryStats isOpen={isOpen}>
159+
<span
160+
className="flex gap-2 text-[11px] opacity-70"
161+
style={{ color: isOpen ? "#666" : "inherit" }}
162+
>
280163
{node.totalStats.additions > 0 &&
281164
(isOpen ? (
282165
<span>+{node.totalStats.additions}</span>
283166
) : (
284-
<Additions>+{node.totalStats.additions}</Additions>
167+
<span className="text-[#4ade80]">+{node.totalStats.additions}</span>
285168
))}
286169
{node.totalStats.deletions > 0 &&
287170
(isOpen ? (
288171
<span>-{node.totalStats.deletions}</span>
289172
) : (
290-
<Deletions>-{node.totalStats.deletions}</Deletions>
173+
<span className="text-[#f87171]">-{node.totalStats.deletions}</span>
291174
))}
292-
</DirectoryStats>
175+
</span>
293176
)}
294177
</>
295178
) : (
296179
<>
297180
<span style={{ width: "12px" }} />
298-
<FileName isFullyRead={isFullyRead} isUnknownState={isUnknownState}>
181+
<span
182+
className={cn(
183+
"flex-1",
184+
isFullyRead && "text-[#666] line-through [text-decoration-color:var(--color-read)] [text-decoration-thickness:2px]",
185+
isUnknownState && !isFullyRead && "text-[#666]",
186+
!isFullyRead && !isUnknownState && "text-[#ccc]"
187+
)}
188+
>
299189
{node.name}
300-
</FileName>
190+
</span>
301191
{node.stats && (
302-
<Stats>
303-
{node.stats.additions > 0 && <Additions>+{node.stats.additions}</Additions>}
304-
{node.stats.deletions > 0 && <Deletions>-{node.stats.deletions}</Deletions>}
305-
</Stats>
192+
<span className="flex gap-2 text-[11px]">
193+
{node.stats.additions > 0 && <span className="text-[#4ade80]">+{node.stats.additions}</span>}
194+
{node.stats.deletions > 0 && <span className="text-[#f87171]">-{node.stats.deletions}</span>}
195+
</span>
306196
)}
307197
</>
308198
)}
309-
</TreeNode>
199+
</div>
310200

311201
{node.isDirectory &&
312202
isOpen &&
@@ -372,14 +262,25 @@ export const FileTree: React.FC<FileTreeExternalProps> = ({
372262

373263
return (
374264
<>
375-
<TreeHeader>
265+
<div className="py-2 px-3 border-b border-[#3e3e42] text-xs font-medium text-[#ccc] font-primary flex items-center gap-2">
376266
<span>Files Changed</span>
377-
{selectedPath && <ClearButton onClick={() => onSelectFile(null)}>Clear filter</ClearButton>}
378-
</TreeHeader>
379-
{commonPrefix && <CommonPrefix>{commonPrefix}/</CommonPrefix>}
380-
<TreeContainer>
267+
{selectedPath && (
268+
<button
269+
className="py-0.5 px-2 bg-transparent text-[#888] border-none rounded-[3px] text-[11px] cursor-pointer transition-all duration-200 font-primary ml-auto hover:bg-white/5 hover:text-[#ccc]"
270+
onClick={() => onSelectFile(null)}
271+
>
272+
Clear filter
273+
</button>
274+
)}
275+
</div>
276+
{commonPrefix && (
277+
<div className="py-1.5 px-3 bg-[#1e1e1e] border-b border-[#3e3e42] text-[11px] text-[#888] font-monospace">
278+
{commonPrefix}/
279+
</div>
280+
)}
281+
<div className="flex-1 min-h-0 p-3 overflow-y-auto font-monospace text-xs">
381282
{isLoading && !startNode ? (
382-
<EmptyState>Loading file tree...</EmptyState>
283+
<div className="py-5 text-[#888] text-center">Loading file tree...</div>
383284
) : startNode ? (
384285
startNode.children.map((child) => (
385286
<TreeNodeContent
@@ -395,9 +296,9 @@ export const FileTree: React.FC<FileTreeExternalProps> = ({
395296
/>
396297
))
397298
) : (
398-
<EmptyState>No files changed</EmptyState>
299+
<div className="py-5 text-[#888] text-center">No files changed</div>
399300
)}
400-
</TreeContainer>
301+
</div>
401302
</>
402303
);
403304
};

0 commit comments

Comments
 (0)