Skip to content

Commit e9aec7b

Browse files
committed
File list: Align Full mode header labels with data cells
`SortableHeader`'s 4px horizontal padding stays for hover-state breathing room, but an equal negative margin pulls each button back out of its column track. Result: header labels line up with the data cells beneath them, and hover backgrounds reach 4px into the inter-column gap on each side. `HEADER_CHROME_ACTIVE` drops from 20 → 12 (gap + caret only) and `HEADER_CHROME_INACTIVE` from 8 → 0, since the padding now sits outside the track. Tests and the colocated gotcha note updated to match.
1 parent 57ba47c commit e9aec7b

4 files changed

Lines changed: 31 additions & 18 deletions

File tree

apps/desktop/src/lib/file-explorer/selection/SortableHeader.svelte

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
align-items: center;
4848
gap: var(--spacing-xs);
4949
padding: 0 var(--spacing-xs);
50+
/* Negative horizontal margin pulls the button 4px outside the column
51+
track on each side. Combined with the 4px internal padding, the
52+
label still lines up with the data cells below, while the hover
53+
background gets breathing room and adjacent buttons sit closer. */
54+
margin: 0 calc(-1 * var(--spacing-xs));
5055
background: transparent;
5156
border: none;
5257
font: inherit;

apps/desktop/src/lib/file-explorer/views/CLAUDE.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,13 @@ layout recalc. `transform` uses GPU compositor for 60fps.
122122
200], don't re-fetch. If scrolled to [250, 300], expand fetch to [0, 550] to include buffer. `shouldResetCache()`
123123
handles this.
124124

125-
**Gotcha**: `HEADER_CHROME_ACTIVE/INACTIVE` in `measure-column-widths.ts` are tied to `SortableHeader`'s padding + flex
126-
gap + caret glyph (`--spacing-xs` = 4px × 3 + 8px caret = 20px active, 4px × 2 = 8px inactive) **Why**: If you change
127-
those CSS values or the caret size/markup, update the two constants or column widths drift. The values aren't derived
128-
from the live DOM because pretext measurement runs without a reference element — everything is computed from the
129-
pre-known chrome formula.
125+
**Gotcha**: `HEADER_CHROME_ACTIVE/INACTIVE` in `measure-column-widths.ts` are tied to `SortableHeader`'s flex gap +
126+
caret glyph (4px gap + 8px caret = 12px active, 0px inactive). The button keeps 4px horizontal padding for hover-state
127+
breathing room, but an equal negative margin (`margin: 0 calc(-1 * var(--spacing-xs))`) pulls it back out so the label
128+
still lines up with the data cells below — only gap+caret count toward the track width. **Why**: If you change those CSS
129+
values or the caret size/markup, update the two constants or column widths drift. The values aren't derived from the
130+
live DOM because pretext measurement runs without a reference element — everything is computed from the pre-known chrome
131+
formula.
130132

131133
**Gotcha**: Width transitions would "slide" on dir switches, because the header (FullList) and columns (BriefList)
132134
persist across navs **Why**: When `shouldResetCache` fires, both lists set a `skipTransition` flag and clear it after

apps/desktop/src/lib/file-explorer/views/measure-column-widths.test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,23 @@ describe('computeFullListColumnWidths', () => {
4747
_setMeasureForTests(fakeMeasure)
4848
const w = computeFullListColumnWidths({ ...baseArgs, entries: [] })
4949
// With sortBy='name', none of Ext/Size/Modified are active, so each gets
50-
// HEADER_CHROME_INACTIVE (8). "Ext" = 21 + 8 = 29; "Size" = 28 + 8 = 36
51-
// → clamped to MIN_SIZE_WIDTH (40); "Modified" = 56 + 8 = 64 → clamped to
52-
// MIN_DATE_WIDTH (70).
53-
expect(w.ext).toBe(29)
50+
// HEADER_CHROME_INACTIVE (0 — labels sit flush with column-track edges).
51+
// "Ext" = 21 → clamped to MIN_EXT_WIDTH (28); "Size" = 28 → clamped to
52+
// MIN_SIZE_WIDTH (40); "Modified" = 56 → clamped to MIN_DATE_WIDTH (70).
53+
expect(w.ext).toBe(28)
5454
expect(w.size).toBe(40)
5555
expect(w.date).toBe(70)
5656
})
5757

5858
it('widens the active sort column to reserve room for the caret', () => {
5959
_setMeasureForTests(fakeMeasure)
60+
// The ext column is the one whose floor (MIN_EXT_WIDTH = 28) sits below
61+
// "Ext" + active chrome (21 + 12 = 33), so the caret allowance is visible.
62+
// Size and Date floors swallow the caret allowance whole, so we test ext.
6063
const nameSorted = computeFullListColumnWidths({ ...baseArgs, entries: [] })
61-
const sizeSorted = computeFullListColumnWidths({ ...baseArgs, entries: [], sortBy: 'size' })
62-
// size col picks up the caret (+12 chrome) when sortBy==='size'; ext stays inactive.
63-
expect(sizeSorted.size).toBeGreaterThan(nameSorted.size)
64-
expect(sizeSorted.ext).toBe(nameSorted.ext)
64+
const extSorted = computeFullListColumnWidths({ ...baseArgs, entries: [], sortBy: 'extension' })
65+
expect(extSorted.ext).toBeGreaterThan(nameSorted.ext)
66+
expect(extSorted.size).toBe(nameSorted.size)
6567
})
6668

6769
it('widens size column when a large file is present', () => {

apps/desktop/src/lib/file-explorer/views/measure-column-widths.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,19 @@ const FONT = '12px -apple-system, BlinkMacSystemFont, sans-serif'
3333

3434
/**
3535
* Header overhead inside `SortableHeader` for the column currently being sorted:
36-
* 4px padding left + 4px padding right + 4px flex gap + 8px caret.
36+
* 4px flex gap + 8px caret. The button's 4px horizontal padding is canceled
37+
* by an equal negative horizontal margin, so the label lines up with the data
38+
* cells below — only the gap+caret count toward the column track width.
3739
*/
38-
const HEADER_CHROME_ACTIVE = 20
40+
const HEADER_CHROME_ACTIVE = 12
3941

4042
/**
41-
* Header overhead for a column that isn't being sorted: the caret is `display: none`,
42-
* which collapses both the glyph and the flex gap. Only the button's own padding remains.
43+
* Header overhead for a column that isn't being sorted: the caret is
44+
* `display: none`, which collapses both the glyph and the flex gap. The
45+
* button's padding is offset by the negative margin, so the label is flush
46+
* against the track edges and chrome is zero.
4347
*/
44-
const HEADER_CHROME_INACTIVE = 8
48+
const HEADER_CHROME_INACTIVE = 0
4549

4650
/** Extra px added to the final column width. Zero by design — `Math.ceil` already
4751
* rounds up, and the pretext measurement matches the browser's own text layout. */

0 commit comments

Comments
 (0)