feat(web): add ScrollFade component for improved horizontal scrolling experience

This commit is contained in:
2026-03-11 11:49:03 +08:00
parent 1619ed22a0
commit ee329c7b9b
2 changed files with 116 additions and 35 deletions

View File

@@ -0,0 +1,54 @@
"use client";
import { useRef, useState, useEffect, type ReactNode } from "react";
interface ScrollFadeProps {
children: ReactNode;
className?: string;
/** Tailwind max-width class, e.g. "max-w-2xs". Defaults to none. */
maxWidth?: string;
}
/**
* Wraps children in a horizontally-scrollable container that shows left/right
* gradient fade indicators when there is overflowed content in that direction.
*/
export function ScrollFade({ children, className, maxWidth }: ScrollFadeProps) {
const scrollRef = useRef<HTMLDivElement>(null);
const [showLeft, setShowLeft] = useState(false);
const [showRight, setShowRight] = useState(false);
const update = () => {
const el = scrollRef.current;
if (!el) return;
setShowLeft(el.scrollLeft > 0);
setShowRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
};
useEffect(() => {
update();
const el = scrollRef.current;
if (!el) return;
const ro = new ResizeObserver(update);
ro.observe(el);
return () => ro.disconnect();
}, [children]);
return (
<div className={`relative ${maxWidth ?? ""}`}>
<div
ref={scrollRef}
onScroll={update}
className={`flex gap-1.5 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden ${className ?? ""}`}
>
{children}
</div>
{showLeft && (
<div className="pointer-events-none absolute inset-y-0 left-0 w-8 bg-linear-to-r from-surface to-transparent group-hover:from-surface-hover" />
)}
{showRight && (
<div className="pointer-events-none absolute inset-y-0 right-0 w-8 bg-linear-to-l from-surface to-transparent group-hover:from-surface-secondary" />
)}
</div>
);
}