polish(channel): tighten toolbar-to-grid gap + restore top scroll fade
Two UAT nits on the channel page: - the chip-row-to-cards gap was ~36px (toolbar pb-3 + wrapper pb-2 + grid pt-3 stacking); drop the wrapper/grid pads so the toolbar's own pb-3 is the only gap (~15px). - restore the page scroller's TOP edge fade. It was dropped when the chrome was a sticky sub-header (the fade bit into it); now the chrome is a fixed band ABOVE the scroller, so the top fade correctly hints at cards scrolling up under the toolbar. Reverts the useScrollFade edges option. Suite 15/15, tsc + knip clean.
This commit is contained in:
@@ -655,7 +655,7 @@ export default function Feed({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={channelScoped ? "px-4 sm:px-6 pb-4 pt-3" : "px-4 pb-4 pt-2"}>
|
||||
<div className={channelScoped ? "px-4 sm:px-6 pb-4" : "px-4 pb-4 pt-2"}>
|
||||
{/* The toolbar (+ active-filter chips) is FIXED chrome: it portals into a PageToolbarSlot so
|
||||
it stays put while the cards scroll (bug 4). On the feed page that slot is the shell's top
|
||||
band; on a channel page ChannelPage overrides the slot with its own sticky sub-header, so
|
||||
@@ -663,7 +663,7 @@ export default function Feed({
|
||||
baseline (channel-scoped, whole-catalog, show-all), so each would read as a "filter you
|
||||
applied" when it's just the page being itself. */}
|
||||
<PageToolbar>
|
||||
<div className={channelScoped ? "px-4 sm:px-6 pb-2" : "px-4 pt-4 pb-1"}>
|
||||
<div className={channelScoped ? "px-4 sm:px-6" : "px-4 pt-4 pb-1"}>
|
||||
{toolbar}
|
||||
{!channelScoped && (
|
||||
<ActiveFilterChips
|
||||
|
||||
@@ -67,9 +67,7 @@ export default function PageScroller({
|
||||
children: ReactNode;
|
||||
scrollKey?: string;
|
||||
}) {
|
||||
// Only the bottom edge fades: the top is bounded by the shell's fixed chrome (header / toolbar
|
||||
// band) or a module's sticky sub-header, so a top fade would just eat into that chrome.
|
||||
const fade = useScrollFade(20, { top: false });
|
||||
const fade = useScrollFade();
|
||||
const fadeRef = fade.ref;
|
||||
const elRef = useRef<HTMLElement | null>(null);
|
||||
// Stable identity: an inline ref callback changes every render, which makes React detach and
|
||||
|
||||
@@ -7,17 +7,10 @@ import { useEffect, useState, type CSSProperties } from "react";
|
||||
// to attach to the scroller and the mask `style` to spread onto it. Uses a node-state callback
|
||||
// ref (not useRef) so the effect (re)runs whenever the element attaches — e.g. a SidePanel that
|
||||
// mounts collapsed and only renders its scroll body once expanded still wires up correctly.
|
||||
// `edges` gates which ends fade. The page scroller passes `{ top: false }`: its top is delimited by
|
||||
// the fixed/sticky module chrome (header, toolbar band, a sticky sub-header), so a top fade there
|
||||
// would only bite into that chrome instead of hinting at more content.
|
||||
export function useScrollFade(
|
||||
fadePx = 20,
|
||||
edges: { top?: boolean; bottom?: boolean } = {},
|
||||
): {
|
||||
export function useScrollFade(fadePx = 20): {
|
||||
ref: (node: HTMLElement | null) => void;
|
||||
style: CSSProperties;
|
||||
} {
|
||||
const { top: fadeTop = true, bottom: fadeBottom = true } = edges;
|
||||
const [el, setEl] = useState<HTMLElement | null>(null);
|
||||
const [fade, setFade] = useState({ up: false, down: false });
|
||||
|
||||
@@ -60,8 +53,8 @@ export function useScrollFade(
|
||||
};
|
||||
}, [el]);
|
||||
|
||||
const topPx = fadeTop && fade.up ? fadePx : 0;
|
||||
const bottomPx = fadeBottom && fade.down ? fadePx : 0;
|
||||
const mask = `linear-gradient(to bottom, transparent, #000 ${topPx}px, #000 calc(100% - ${bottomPx}px), transparent)`;
|
||||
const mask = `linear-gradient(to bottom, transparent, #000 ${fade.up ? fadePx : 0}px, #000 calc(100% - ${
|
||||
fade.down ? fadePx : 0
|
||||
}px), transparent)`;
|
||||
return { ref: setEl, style: { maskImage: mask, WebkitMaskImage: mask } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user