fix: assorted UI polish from review pass
- FilterPill: drop the inline value text from the active state. Pills now stay the same width whether or not a filter is set; the popover is the canonical place to read the value, and the title attribute surfaces it on hover. - TopBar: remove the search input — search lives in the filter bar now. - FilterBar: add a search input on the left, with the pill cluster centered between it and a flex-shrink-0 Clear-all on the right. - LeftSidebar / HeapsPanel: count badges use a fixed-width slot (h-5 min-w-[24px], tabular-nums) so counts line up in the same visual column across rows. Empty rows reserve the slot. - LeftSidebar: pull section counts (All Photos, Rated, Duplicates, Discarded) from a new useLibraryStatsQuery hook backed by the expanded /library/stats endpoint. Tags count was already wired. - backend/library: stats endpoint returns per-section counts that match the filter the sidebar applies on click. - Stats invalidation hooked into the standard photo-mutation paths. - RightSidebar header: h-12 to match TopBar height. - Timeline sticky date overlay: only show once the natural in-grid header has scrolled OUT of the viewport. Avoids the duplicate-label flash when both labels would be visible. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -257,13 +257,17 @@ export function Timeline() {
|
||||
return () => el.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
// Find the latest header whose start <= scrollTop. That's the label of
|
||||
// the group containing whatever is currently at the top of the viewport.
|
||||
// Find the latest header whose BOTTOM is above the viewport top. That's
|
||||
// the group whose natural in-grid header has scrolled out of view —
|
||||
// exactly the case where we want to pin the label as a sticky overlay.
|
||||
// If the natural header is still visible (scrolled but not yet past),
|
||||
// we return null and let the in-grid label do the work, avoiding the
|
||||
// duplicate-label flash.
|
||||
const stickyLabel = useMemo(() => {
|
||||
if (headerOffsets.length === 0) return null
|
||||
let current: string | null = null
|
||||
for (const h of headerOffsets) {
|
||||
if (h.offset <= scrollTop) current = h.label
|
||||
if (h.offset + HEADER_HEIGHT <= scrollTop) current = h.label
|
||||
else break
|
||||
}
|
||||
return current
|
||||
|
||||
Reference in New Issue
Block a user