From 30e0900e49cf1aa5cf267d68fd3b9e69ab8208ee Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 8 Apr 2026 21:01:01 +0200 Subject: [PATCH] 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) --- frontend/src/components/filter/FilterBar.tsx | 30 ++++++++++++------- frontend/src/components/filter/FilterPill.tsx | 13 ++++++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/filter/FilterBar.tsx b/frontend/src/components/filter/FilterBar.tsx index 43c80fa..89b3c2f 100644 --- a/frontend/src/components/filter/FilterBar.tsx +++ b/frontend/src/components/filter/FilterBar.tsx @@ -116,7 +116,10 @@ export function FilterBar() { const anyActive = hasActiveFilters(filterState) return ( -
+ // 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. +
{/* Search — left of the pill cluster. Same id as before so the * global "/" focus shortcut still finds it. */}
@@ -377,16 +380,21 @@ export function FilterBar() {
- {/* Clear-all — pinned right of the pill cluster. */} - {anyActive && ( - - )} + {/* 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. */} +
+ {anyActive && ( + + )} +
) } diff --git a/frontend/src/components/filter/FilterPill.tsx b/frontend/src/components/filter/FilterPill.tsx index cba62ac..ae80291 100644 --- a/frontend/src/components/filter/FilterPill.tsx +++ b/frontend/src/components/filter/FilterPill.tsx @@ -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}`} > ) : ( - + + + )}