ui/ux on catalogue

This commit is contained in:
2026-09-18 19:19:34 +05:30
parent 041dc37861
commit 93735e232a
7 changed files with 259 additions and 94 deletions

View File

@@ -0,0 +1,59 @@
import { useEffect, useRef, useState, type ReactNode } from 'react';
/**
* A row of page controls held under the nav bar while the page scrolls.
*
* Whatever sits above a page — its view tabs, its search, its action buttons —
* used to leave with the scroll. Reaching the bottom of a long list put every
* control over that list off the top of the window, and the only way back to
* them was to scroll back through everything just read. The further in somebody
* was, the further from the controls.
*
* The page still scrolls normally. Nothing here gets a scrollbar of its own —
* the row simply stays where it is. Positioning and the canvas it paints live
* in `.page-sticky`; this component exists for the one part CSS cannot answer,
* which is whether the row is currently pinned.
*
* `PageHeader` wraps itself in this, so every page built from it gets the
* behaviour without asking. Dispatch builds its own switcher rather than going
* through `PageHeader`, so it wraps itself — which is the reason this is a
* component and not four more lines inside the header.
*/
export function StickyRow({ children }: { children: ReactNode }) {
/**
* Pinned or not, so the hairline can be drawn only when it is.
*
* At rest the nav bar's own hairline sits just above this one, and two
* parallel lines across the page with nothing between them read as a mistake.
* The line is what separates the row from content passing UNDER it, so it
* belongs to the pinned state rather than to the row.
*
* Watched with an observer rather than a scroll handler: a scroll listener
* runs on every frame of every scroll to answer a question that changes
* twice. The negative top margin puts the observer's boundary a pixel above
* where the row comes to rest, so "no longer fully inside that box" means
* exactly "stuck".
*/
const rowRef = useRef<HTMLDivElement>(null);
const [isStuck, setIsStuck] = useState(false);
useEffect(() => {
const element = rowRef.current;
/* Guarded for any renderer without it — the row still sticks, it just
never grows its line, which is a missing hairline rather than a crash. */
if (!element || typeof IntersectionObserver === 'undefined') return;
const observer = new IntersectionObserver(
([entry]) => setIsStuck(Boolean(entry && !entry.isIntersecting)),
{ rootMargin: '-57px 0px 0px 0px', threshold: 1 },
);
observer.observe(element);
return () => observer.disconnect();
}, []);
return (
<div ref={rowRef} className="page-sticky" data-stuck={isStuck ? 'true' : 'false'}>
{children}
</div>
);
}