fix: lock filter bar height + freeze pill positions

- FilterPill: fixed h-7 + py-0 on the button so neither the X clear icon
  nor the chevron can stretch the pill vertically when active state
  swaps them in. The chevron is now wrapped in the same h-4 w-4 slot as
  the clear X so swapping doesn't change footprint horizontally either.
- FilterBar: fixed h-11 on the bar itself so any future per-pill drift
  can't grow the row.
- Clear-all: wrapped in a fixed w-56 right slot that mirrors the search
  input on the left. The pill cluster sits in the centered flex-1
  middle slot, so it stays perfectly centered whether or not Clear-all
  is rendered. The button itself is right-aligned within the slot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 21:01:01 +02:00
parent c01c3b02ce
commit 30e0900e49
2 changed files with 29 additions and 14 deletions

View File

@@ -116,7 +116,10 @@ export function FilterBar() {
const anyActive = hasActiveFilters(filterState)
return (
<div className="flex items-center gap-3 border-b border-border bg-surface px-3 py-1.5">
// Fixed bar height + py-0 so neither the active filter pills nor the
// clear-all button can stretch the bar vertically. The fixed h-11
// matches the h-7 pills + 8px symmetric vertical padding.
<div className="flex h-11 items-center gap-3 border-b border-border bg-surface px-3 py-0">
{/* Search — left of the pill cluster. Same id as before so the
* global "/" focus shortcut still finds it. */}
<div className="relative w-56 flex-shrink-0">
@@ -377,16 +380,21 @@ export function FilterBar() {
</FilterPill>
</div>
{/* Clear-all — pinned right of the pill cluster. */}
{anyActive && (
<button
onClick={clearAll}
className="flex-shrink-0 whitespace-nowrap rounded-full border border-border px-2.5 py-1 text-xs text-text-muted hover:bg-surface-2 hover:text-text"
title="Clear all filters in this section"
>
Clear all
</button>
)}
{/* Right slot — fixed-width so the pill cluster stays perfectly
* centered in the bar regardless of whether Clear-all is visible.
* Width matches the search input on the left for symmetric
* framing. The button itself is right-aligned within the slot. */}
<div className="flex w-56 flex-shrink-0 justify-end">
{anyActive && (
<button
onClick={clearAll}
className="flex h-7 items-center whitespace-nowrap rounded-full border border-border px-2.5 text-xs text-text-muted hover:bg-surface-2 hover:text-text"
title="Clear all filters in this section"
>
Clear all
</button>
)}
</div>
</div>
)
}

View File

@@ -100,7 +100,10 @@ export function FilterPill({
// canonical place to read/edit the filter value.
title={isActive && value ? `${label}: ${value}` : label}
className={clsx(
'flex items-center gap-1 rounded-full border px-2.5 py-1 text-xs transition-colors',
// Fixed height + py-0 so neither the X clear icon nor the
// chevron can stretch the pill vertically when the active
// state swaps them in.
'flex h-7 items-center gap-1 rounded-full border px-2.5 py-0 text-xs transition-colors',
isActive
? 'border-primary/40 bg-primary/15 text-primary'
: 'border-border bg-surface-2 text-text-muted hover:bg-surface-offset hover:text-text'
@@ -122,14 +125,18 @@ export function FilterPill({
onClear()
}
}}
className="ml-1 inline-flex h-5 w-5 cursor-pointer items-center justify-center rounded-full hover:bg-primary/30"
// Same h-4 w-4 as the chevron slot below so swapping the
// two doesn't change the pill's footprint.
className="ml-0.5 inline-flex h-4 w-4 cursor-pointer items-center justify-center rounded-full hover:bg-primary/30"
title={`Clear ${label}`}
aria-label={`Clear ${label}`}
>
<X className="h-3 w-3" />
</span>
) : (
<ChevronDown className="h-3 w-3 opacity-60" />
<span className="inline-flex h-4 w-4 items-center justify-center">
<ChevronDown className="h-3 w-3 opacity-60" />
</span>
)}
</button>