Skip to content

Commit 46f9996

Browse files
committed
fix: ui responsiveness
1 parent 70c25b9 commit 46f9996

3 files changed

Lines changed: 139 additions & 11 deletions

File tree

src/web/src/App.css

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,13 @@ button.icon-only {
175175
grid-template-areas:
176176
'header header header'
177177
'left resizer right';
178-
grid-template-columns: var(--left-col, minmax(320px, 1fr)) 6px 1fr;
178+
/* Clamp the left column so saved px widths don't break on smaller viewports */
179+
grid-template-columns: clamp(240px, var(--left-col, 40%), 55%) 6px 1fr;
179180
grid-template-rows: auto 1fr;
180181
gap: 1.25rem;
182+
/* Mobile browser chrome friendly */
181183
height: 100vh;
184+
height: 100dvh;
182185
padding: 2rem;
183186
padding-bottom: 4.5rem; /* space for fixed status footer */
184187
box-sizing: border-box;
@@ -238,6 +241,12 @@ button.icon-only {
238241
user-select: none;
239242
}
240243

244+
.column-resizer:focus-visible {
245+
outline: 2px solid #646cff;
246+
outline-offset: 2px;
247+
border-radius: 4px;
248+
}
249+
241250
.column-resizer:hover {
242251
background:
243252
linear-gradient(
@@ -422,3 +431,56 @@ button.icon-only {
422431
text-decoration: underline;
423432
text-underline-offset: 2px;
424433
}
434+
435+
/* ---------- Responsiveness ---------- */
436+
/* Stack columns on narrower screens; hide the resizer */
437+
@media (max-width: 1100px) {
438+
.app-container {
439+
grid-template-areas:
440+
'header'
441+
'left'
442+
'right';
443+
grid-template-columns: 1fr;
444+
grid-template-rows: auto auto 1fr;
445+
}
446+
.column-resizer { display: none; }
447+
.left-panel, .right-panel { padding: 0; }
448+
}
449+
450+
/* Tighten padding and let header wrap nicely on small devices */
451+
@media (max-width: 700px) {
452+
.app-container {
453+
padding: 1rem;
454+
padding-bottom: 4.5rem;
455+
}
456+
.header { flex-wrap: wrap; gap: 0.5rem; }
457+
.header img[alt="GitContext"] { height: 40px; }
458+
}
459+
460+
/* Selected Files: rows wrap on small screens; token count drops under filename */
461+
.selected-file-row {
462+
display: flex;
463+
align-items: center;
464+
gap: 8px;
465+
padding: 4px 0;
466+
flex-wrap: nowrap;
467+
}
468+
.selected-file-row .tokens {
469+
opacity: 0.8;
470+
min-width: 64px;
471+
text-align: right;
472+
}
473+
@media (max-width: 700px) {
474+
.selected-file-row { flex-wrap: wrap; }
475+
.selected-file-row .tokens {
476+
order: 3;
477+
width: 100%;
478+
min-width: 0;
479+
text-align: left;
480+
}
481+
}
482+
483+
/* Make generic icon-only targets a bit friendlier to tap */
484+
@media (hover: none) and (pointer: coarse) {
485+
button.icon-only { padding: 0.75em; }
486+
}

src/web/src/App.tsx

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,32 @@ function App() {
135135
if (!handle) return
136136

137137
const minLeft = 240 // px
138-
const maxLeft = Math.max(480, Math.floor((appEl.clientWidth - 100) * 0.85))
138+
const computeMaxLeft = () => Math.max(480, Math.floor((appEl.clientWidth - 100) * 0.85))
139+
let maxLeft = computeMaxLeft()
140+
141+
// ARIA setup for accessibility
142+
try {
143+
handle.setAttribute('role', 'separator')
144+
handle.setAttribute('aria-orientation', 'vertical')
145+
handle.setAttribute('aria-valuemin', String(minLeft))
146+
handle.setAttribute('aria-valuemax', String(maxLeft))
147+
;(handle as HTMLElement).tabIndex = 0
148+
const current = Number((getComputedStyle(appEl).getPropertyValue('--left-col') || '').replace('px','')) || minLeft
149+
handle.setAttribute('aria-valuenow', String(current))
150+
handle.setAttribute('aria-label', 'Resize panels')
151+
} catch {}
152+
153+
const applyLeft = (px: number) => {
154+
const clamped = Math.min(Math.max(px, minLeft), maxLeft)
155+
appEl.style.setProperty('--left-col', `${clamped}px`)
156+
try {
157+
localStorage.setItem('gc.leftCol', String(clamped))
158+
} catch (e) {
159+
logError('leftColSave', e)
160+
}
161+
try { handle.setAttribute('aria-valuenow', String(clamped)) } catch {}
162+
}
163+
139164
let dragging = false
140165

141166
const onPointerDown = (e: PointerEvent) => {
@@ -149,26 +174,60 @@ function App() {
149174
const rect = appEl.getBoundingClientRect()
150175
const x = e.clientX - rect.left
151176
const clamped = Math.min(Math.max(x - 12, minLeft), maxLeft)
152-
appEl.style.setProperty('--left-col', `${clamped}px`)
153-
try {
154-
localStorage.setItem('gc.leftCol', String(clamped))
155-
} catch (e) {
156-
logError('leftColSave', e)
157-
}
177+
applyLeft(clamped)
158178
}
159179
const onPointerUp = (e: PointerEvent) => {
160180
dragging = false
161181
appEl.classList.remove('resizing')
162182
;(e.target as HTMLElement).releasePointerCapture?.(e.pointerId)
163183
}
164184

185+
// Keyboard support: ArrowLeft/Right, Home/End
186+
const onKeyDown = (e: KeyboardEvent) => {
187+
const step = e.ctrlKey ? 50 : 16
188+
const curr = Number((getComputedStyle(appEl).getPropertyValue('--left-col') || '').replace('px','')) || minLeft
189+
switch (e.key) {
190+
case 'ArrowLeft':
191+
applyLeft(curr - step)
192+
e.preventDefault()
193+
break
194+
case 'ArrowRight':
195+
applyLeft(curr + step)
196+
e.preventDefault()
197+
break
198+
case 'Home':
199+
applyLeft(minLeft)
200+
e.preventDefault()
201+
break
202+
case 'End':
203+
applyLeft(maxLeft)
204+
e.preventDefault()
205+
break
206+
}
207+
}
208+
209+
// Clamp saved width on window resize so layout never overflows
210+
const onWindowResize = () => {
211+
const nextMax = computeMaxLeft()
212+
if (nextMax !== maxLeft) {
213+
maxLeft = nextMax
214+
try { handle.setAttribute('aria-valuemax', String(maxLeft)) } catch {}
215+
const curr = Number((getComputedStyle(appEl).getPropertyValue('--left-col') || '').replace('px','')) || minLeft
216+
applyLeft(curr) // re-clamp
217+
}
218+
}
219+
165220
handle.addEventListener('pointerdown', onPointerDown)
221+
handle.addEventListener('keydown', onKeyDown)
166222
window.addEventListener('pointermove', onPointerMove)
167223
window.addEventListener('pointerup', onPointerUp)
224+
window.addEventListener('resize', onWindowResize)
168225
return () => {
169226
handle.removeEventListener('pointerdown', onPointerDown)
227+
handle.removeEventListener('keydown', onKeyDown)
170228
window.removeEventListener('pointermove', onPointerMove)
171229
window.removeEventListener('pointerup', onPointerUp)
230+
window.removeEventListener('resize', onWindowResize)
172231
}
173232
}, [uiHasResizer])
174233

@@ -859,7 +918,14 @@ function App() {
859918
</div>
860919

861920
{/* Resizer handle between columns */}
862-
<div className="column-resizer" id="gc-col-resizer" />
921+
<div
922+
className="column-resizer"
923+
id="gc-col-resizer"
924+
role="separator"
925+
aria-orientation="vertical"
926+
aria-label="Resize panels"
927+
tabIndex={0}
928+
/>
863929

864930
<div className="right-panel">
865931
<div className="panel-section">

src/web/src/components/SelectedFilesPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function SelectedFilesPanel({ gitClient, baseRef, compareRef, selectedPat
112112
) : (
113113
<div style={{ border: '1px solid color-mix(in hsl, currentColor 20%, transparent)', borderRadius: 8, padding: '0.5rem', flex: 1, minHeight: 0, overflowY: 'auto' }}>
114114
{items.map((it) => (
115-
<div key={it.path} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0' }}>
115+
<div key={it.path} className="selected-file-row">
116116
<span title={pathTreeTooltip(it.path)} style={{ display: 'flex', alignItems: 'center', gap: 6, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
117117
<StatusIcon status={it.status} />
118118
<span style={{ display: 'flex', alignItems: 'center', gap: 4, overflow: 'hidden', textOverflow: 'ellipsis' }}>
@@ -124,7 +124,7 @@ export function SelectedFilesPanel({ gitClient, baseRef, compareRef, selectedPat
124124
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.name}</span>
125125
</span>
126126
</span>
127-
<span style={{ opacity: 0.8, minWidth: 64, textAlign: 'right' }}>{it.tokens.toLocaleString()}</span>
127+
<span className="tokens">{it.tokens.toLocaleString()}</span>
128128
<button
129129
type="button"
130130
onClick={() => onPreview(it.path, it.status)}

0 commit comments

Comments
 (0)