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