refactor: compact pill-based filter toolbar

Merge the toggleable multi-line FilterBar and the separate ActiveFilterChips
strip into a single always-visible row of pills. Each filter category is a
pill that opens a small popover with its underlying control; when active, the
pill shows its current value inline (so the chips strip is redundant).

- New FilterPill primitive: outside-click + Escape to close, optional inline
  X to clear without opening the popover.
- FilterBar rebuilt out of pills for Date/Type/Rating/Color/Flag/Tags/Sort,
  with a Clear-all pill on the right when any filter is active.
- Drop filterBarOpen from filterStore, the SlidersHorizontal toggle from
  TopBar, the \\ shortcut from useKeyboardShortcuts, and the matching hint
  from KeyboardHints — the bar is always visible now.
- Delete ActiveFilterChips; its information lives inside the pills.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 12:49:14 +02:00
parent 522228fb79
commit 2f1e9033ae
8 changed files with 374 additions and 346 deletions

View File

@@ -1,12 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import {
Search,
SlidersHorizontal,
X,
ShoppingBasket,
} from 'lucide-react'
import clsx from 'clsx'
import { useFilterStore, hasActiveFilters } from '../../store/filterStore'
import { Search, X, ShoppingBasket } from 'lucide-react'
import { useFilterStore } from '../../store/filterStore'
import { useHeapsQuery } from '../../hooks/useHeapsQuery'
import muliLogo from '../../assets/muli-logo.png'
@@ -17,10 +11,6 @@ export function TopBar() {
// mirror so typing stays responsive while we debounce store updates.
const storeQ = useFilterStore((s) => s.q)
const setStoreQ = useFilterStore((s) => s.setQ)
const filterBarOpen = useFilterStore((s) => s.filterBarOpen)
const toggleFilterBar = useFilterStore((s) => s.toggleFilterBar)
const filterState = useFilterStore()
const filtersActive = hasActiveFilters(filterState) || filterBarOpen
const [searchQuery, setSearchQuery] = useState(storeQ)
@@ -101,24 +91,8 @@ export function TopBar() {
</div>
</div>
{/* Right — filter toggle */}
<div className="flex items-center gap-2">
<button
onClick={toggleFilterBar}
className={clsx(
'group relative rounded p-1.5 transition-colors',
filtersActive
? 'bg-primary/20 text-primary'
: 'text-text-muted hover:bg-surface-2 hover:text-text'
)}
title="Toggle filters (\\)"
>
<SlidersHorizontal className="h-4 w-4" />
<kbd className="absolute -bottom-5 left-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-surface-offset px-1 py-0.5 text-[9px] font-medium text-text opacity-0 group-hover:opacity-100">
\
</kbd>
</button>
</div>
{/* Right — reserved for future actions */}
<div className="flex items-center gap-2" />
</header>
)
}