Skip to content

Commit ebc616b

Browse files
committed
🤖 refactor: Convert HunkViewer to Tailwind CSS
- Replace all styled-components with Tailwind utility classes - Use cn() utility for conditional border and selected states - Preserve all data attributes and event handlers - Keep dynamic states for read/selected with proper colors - Maintain CSS Grid layout for diff lines - ~30% code reduction (377 lines)
1 parent 41b1b0b commit ebc616b

File tree

1 file changed

+37
-179
lines changed

1 file changed

+37
-179
lines changed

src/components/RightSidebar/CodeReview/HunkViewer.tsx

Lines changed: 37 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44

55
import React, { useState, useMemo } from "react";
6-
import styled from "@emotion/styled";
76
import type { DiffHunk } from "@/types/review";
87
import { SelectableDiffRenderer } from "../../shared/DiffRenderer";
98
import {
@@ -14,6 +13,7 @@ import { Tooltip, TooltipWrapper } from "../../Tooltip";
1413
import { usePersistedState } from "@/hooks/usePersistedState";
1514
import { getReviewExpandStateKey } from "@/constants/storage";
1615
import { KEYBINDS, formatKeybind } from "@/utils/ui/keybinds";
16+
import { cn } from "@/lib/utils";
1717

1818
interface HunkViewerProps {
1919
hunk: DiffHunk;
@@ -28,158 +28,6 @@ interface HunkViewerProps {
2828
searchConfig?: SearchHighlightConfig;
2929
}
3030

31-
const HunkContainer = styled.div<{ isSelected: boolean; isRead: boolean }>`
32-
background: #1e1e1e;
33-
border: 1px solid #3e3e42;
34-
border-radius: 4px;
35-
margin-bottom: 12px;
36-
overflow: hidden;
37-
cursor: pointer;
38-
transition: all 0.2s ease;
39-
40-
/* Remove default focus ring - keyboard navigation uses isSelected state */
41-
&:focus,
42-
&:focus-visible {
43-
outline: none;
44-
}
45-
46-
${(props) =>
47-
props.isRead &&
48-
`
49-
border-color: var(--color-read);
50-
`}
51-
52-
${(props) =>
53-
props.isSelected &&
54-
`
55-
border-color: var(--color-review-accent);
56-
box-shadow: 0 0 0 1px var(--color-review-accent);
57-
`}
58-
`;
59-
60-
const HunkHeader = styled.div`
61-
/* Keep grayscale to avoid clashing with green/red LoC indicators */
62-
background: #252526;
63-
padding: 8px 12px;
64-
border-bottom: 1px solid #3e3e42;
65-
display: flex;
66-
justify-content: space-between;
67-
align-items: center;
68-
font-family: var(--font-monospace);
69-
font-size: 12px;
70-
gap: 8px;
71-
`;
72-
73-
const FilePath = styled.div`
74-
color: #cccccc;
75-
font-weight: 500;
76-
white-space: nowrap;
77-
overflow: hidden;
78-
text-overflow: ellipsis;
79-
min-width: 0;
80-
`;
81-
82-
const LineInfo = styled.div`
83-
display: flex;
84-
align-items: center;
85-
gap: 8px;
86-
font-size: 11px;
87-
white-space: nowrap;
88-
flex-shrink: 0;
89-
`;
90-
91-
const LocStats = styled.span`
92-
display: flex;
93-
gap: 8px;
94-
font-size: 11px;
95-
`;
96-
97-
const Additions = styled.span`
98-
color: #4ade80;
99-
`;
100-
101-
const Deletions = styled.span`
102-
color: #f87171;
103-
`;
104-
105-
const LineCount = styled.span`
106-
color: #888888;
107-
`;
108-
109-
const HunkContent = styled.div`
110-
padding: 6px 8px;
111-
font-family: var(--font-monospace);
112-
font-size: 11px;
113-
line-height: 1.4;
114-
overflow-x: auto;
115-
background: var(--color-code-bg);
116-
117-
/* CSS Grid ensures all diff lines span the same width (width of longest line) */
118-
display: grid;
119-
grid-template-columns: minmax(min-content, 1fr);
120-
`;
121-
122-
const CollapsedIndicator = styled.div`
123-
padding: 8px 12px;
124-
text-align: center;
125-
color: #888;
126-
font-size: 11px;
127-
font-style: italic;
128-
cursor: pointer;
129-
130-
&:hover {
131-
color: #ccc;
132-
}
133-
`;
134-
135-
const RenameInfo = styled.div`
136-
padding: 12px;
137-
color: #888;
138-
font-size: 11px;
139-
display: flex;
140-
align-items: center;
141-
gap: 8px;
142-
background: rgba(100, 150, 255, 0.05);
143-
144-
&::before {
145-
content: "→";
146-
font-size: 14px;
147-
color: #6496ff;
148-
}
149-
`;
150-
151-
const ReadIndicator = styled.span`
152-
display: inline-flex;
153-
align-items: center;
154-
color: var(--color-read);
155-
font-size: 14px;
156-
margin-right: 4px;
157-
`;
158-
159-
const ToggleReadButton = styled.button`
160-
background: transparent;
161-
border: 1px solid #3e3e42;
162-
border-radius: 3px;
163-
padding: 2px 6px;
164-
color: #888;
165-
font-size: 11px;
166-
cursor: pointer;
167-
transition: all 0.2s ease;
168-
display: flex;
169-
align-items: center;
170-
gap: 4px;
171-
172-
&:hover {
173-
background: rgba(255, 255, 255, 0.05);
174-
border-color: var(--color-read);
175-
color: var(--color-read);
176-
}
177-
178-
&:active {
179-
transform: scale(0.95);
180-
}
181-
`;
182-
18331
export const HunkViewer = React.memo<HunkViewerProps>(
18432
({
18533
hunk,
@@ -288,57 +136,67 @@ export const HunkViewer = React.memo<HunkViewerProps>(
288136
hunk.changeType === "renamed" && hunk.oldPath && additions === 0 && deletions === 0;
289137

290138
return (
291-
<HunkContainer
292-
isSelected={isSelected ?? false}
293-
isRead={isRead}
139+
<div
140+
className={cn(
141+
"bg-[#1e1e1e] border rounded mb-3 overflow-hidden cursor-pointer transition-all duration-200",
142+
"focus:outline-none focus-visible:outline-none",
143+
isRead ? "border-[var(--color-read)]" : "border-[#3e3e42]",
144+
isSelected && "border-[var(--color-review-accent)] shadow-[0_0_0_1px_var(--color-review-accent)]"
145+
)}
294146
onClick={onClick}
295147
role="button"
296148
tabIndex={0}
297149
data-hunk-id={hunkId}
298150
>
299-
<HunkHeader>
151+
<div className="bg-[#252526] py-2 px-3 border-b border-[#3e3e42] flex justify-between items-center font-monospace text-xs gap-2">
300152
{isRead && (
301153
<TooltipWrapper inline>
302-
<ReadIndicator aria-label="Marked as read"></ReadIndicator>
154+
<span className="inline-flex items-center text-[var(--color-read)] text-sm mr-1" aria-label="Marked as read">
155+
156+
</span>
303157
<Tooltip align="center" position="top">
304158
Marked as read
305159
</Tooltip>
306160
</TooltipWrapper>
307161
)}
308-
<FilePath dangerouslySetInnerHTML={{ __html: highlightedFilePath }} />
309-
<LineInfo>
162+
<div
163+
className="text-[#cccccc] font-medium whitespace-nowrap overflow-hidden text-ellipsis min-w-0"
164+
dangerouslySetInnerHTML={{ __html: highlightedFilePath }}
165+
/>
166+
<div className="flex items-center gap-2 text-[11px] whitespace-nowrap flex-shrink-0">
310167
{!isPureRename && (
311-
<LocStats>
312-
{additions > 0 && <Additions>+{additions}</Additions>}
313-
{deletions > 0 && <Deletions>-{deletions}</Deletions>}
314-
</LocStats>
168+
<span className="flex gap-2 text-[11px]">
169+
{additions > 0 && <span className="text-[#4ade80]">+{additions}</span>}
170+
{deletions > 0 && <span className="text-[#f87171]">-{deletions}</span>}
171+
</span>
315172
)}
316-
<LineCount>
173+
<span className="text-[#888888]">
317174
({lineCount} {lineCount === 1 ? "line" : "lines"})
318-
</LineCount>
175+
</span>
319176
{onToggleRead && (
320177
<TooltipWrapper inline>
321-
<ToggleReadButton
178+
<button
179+
className="bg-transparent border border-[#3e3e42] rounded-[3px] py-0.5 px-1.5 text-[#888] text-[11px] cursor-pointer transition-all duration-200 flex items-center gap-1 hover:bg-white/5 hover:border-[var(--color-read)] hover:text-[var(--color-read)] active:scale-95"
322180
data-hunk-id={hunkId}
323181
onClick={handleToggleRead}
324182
aria-label={`Mark as read (${formatKeybind(KEYBINDS.TOGGLE_HUNK_READ)})`}
325183
>
326184
{isRead ? "○" : "◉"}
327-
</ToggleReadButton>
185+
</button>
328186
<Tooltip align="right" position="top">
329187
Mark as read ({formatKeybind(KEYBINDS.TOGGLE_HUNK_READ)})
330188
</Tooltip>
331189
</TooltipWrapper>
332190
)}
333-
</LineInfo>
334-
</HunkHeader>
191+
</div>
192+
</div>
335193

336194
{isPureRename ? (
337-
<RenameInfo>
195+
<div className="p-3 text-[#888] text-[11px] flex items-center gap-2 bg-[rgba(100,150,255,0.05)] before:content-['→'] before:text-sm before:text-[#6496ff]">
338196
Renamed from <code>{hunk.oldPath}</code>
339-
</RenameInfo>
197+
</div>
340198
) : isExpanded ? (
341-
<HunkContent>
199+
<div className="py-1.5 px-2 font-monospace text-[11px] leading-[1.4] overflow-x-auto bg-code-bg grid grid-cols-[minmax(min-content,1fr)]">
342200
<SelectableDiffRenderer
343201
content={hunk.content}
344202
filePath={hunk.filePath}
@@ -355,20 +213,20 @@ export const HunkViewer = React.memo<HunkViewerProps>(
355213
}}
356214
searchConfig={searchConfig}
357215
/>
358-
</HunkContent>
216+
</div>
359217
) : (
360-
<CollapsedIndicator onClick={handleToggleExpand}>
218+
<div className="py-2 px-3 text-center text-[#888] text-[11px] italic cursor-pointer hover:text-[#ccc]" onClick={handleToggleExpand}>
361219
{isRead && "Hunk marked as read. "}Click to expand ({lineCount} lines) or press{" "}
362220
{formatKeybind(KEYBINDS.TOGGLE_HUNK_COLLAPSE)}
363-
</CollapsedIndicator>
221+
</div>
364222
)}
365223

366224
{hasManualState && isExpanded && !isPureRename && (
367-
<CollapsedIndicator onClick={handleToggleExpand}>
225+
<div className="py-2 px-3 text-center text-[#888] text-[11px] italic cursor-pointer hover:text-[#ccc]" onClick={handleToggleExpand}>
368226
Click here or press {formatKeybind(KEYBINDS.TOGGLE_HUNK_COLLAPSE)} to collapse
369-
</CollapsedIndicator>
227+
</div>
370228
)}
371-
</HunkContainer>
229+
</div>
372230
);
373231
}
374232
);

0 commit comments

Comments
 (0)