From f84f846e768e66ec8c784a6686db619b03f9df60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:31:51 -0600 Subject: [PATCH] feat: scroll functionality --- .../src/components/Scroller/index.module.css | 19 ++++++-- .../src/components/Scroller/index.tsx | 48 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/packages/website-ue/src/components/Scroller/index.module.css b/packages/website-ue/src/components/Scroller/index.module.css index 4c1dd7d0a..9bc2bb39d 100644 --- a/packages/website-ue/src/components/Scroller/index.module.css +++ b/packages/website-ue/src/components/Scroller/index.module.css @@ -1,6 +1,7 @@ .scroller { display: flex; flex-direction: column; + gap: var(--space-3); .content { overflow: auto; @@ -15,11 +16,15 @@ } .bar-wrapper { + opacity: 0; padding: var(--space-1); - background-color: blue; + background-color: var(--gray-3); + + transition: opacity var(--duration-brief); + + border-radius: var(--max-size); .bar-container { - background-color: red; height: 10px; position: relative; @@ -29,7 +34,8 @@ min-width: min(100%, var(--space-6)); position: absolute; - background-color: green; + border-radius: var(--max-size); + background-color: var(--gray-7); cursor: grab; @@ -39,4 +45,11 @@ } } } + + &:hover, + &:focus-within { + .bar-wrapper { + opacity: 1; + } + } } diff --git a/packages/website-ue/src/components/Scroller/index.tsx b/packages/website-ue/src/components/Scroller/index.tsx index dd70994bd..e704c81c5 100644 --- a/packages/website-ue/src/components/Scroller/index.tsx +++ b/packages/website-ue/src/components/Scroller/index.tsx @@ -10,32 +10,72 @@ export function Scroller({ children, className, ...props }: ScrollerProps) { const content = useRef(null!); useEffect(() => { - function updateSize() { - bar.current.style.width = `${(content.current.clientWidth / content.current.scrollWidth) * 100}%`; + function updateBarSize() { + const visible = content.current.clientWidth; + const total = content.current.scrollWidth; + + if (total <= 0) return; + + bar.current.style.width = `${(visible / total) * 100}%`; + } + + function syncBarToScroll() { + const maxTravel = container.current.clientWidth - bar.current.clientWidth; + const maxScroll = + content.current.scrollWidth - content.current.clientWidth; + + if (maxScroll <= 0 || maxTravel <= 0) { + bar.current.style.left = "0"; + return; + } + + const ratio = content.current.scrollLeft / maxScroll; + bar.current.style.left = `${ratio * maxTravel}px`; } let lastX = 0; + function handlePointerDown(event: PointerEvent) { lastX = event.clientX; + bar.current.setPointerCapture(event.pointerId); window.addEventListener("pointermove", handlePointerMove); window.addEventListener("pointerup", handlePointerUp); } + function handlePointerMove(event: PointerEvent) { const dx = event.clientX - lastX; lastX = event.clientX; + + const maxTravel = container.current.clientWidth - bar.current.clientWidth; + if (maxTravel <= 0) return; + + const maxScroll = + content.current.scrollWidth - content.current.clientWidth; + const delta = (dx / maxTravel) * maxScroll; + + content.current.scrollLeft += delta; + + syncBarToScroll(); } - function handlePointerUp() { + + function handlePointerUp(event: PointerEvent) { + bar.current.releasePointerCapture(event.pointerId); window.removeEventListener("pointermove", handlePointerMove); window.removeEventListener("pointerup", handlePointerUp); } - updateSize(); + updateBarSize(); + syncBarToScroll(); bar.current.addEventListener("pointerdown", handlePointerDown); + content.current.addEventListener("scroll", syncBarToScroll); + window.addEventListener("resize", updateBarSize); return () => { bar.current.removeEventListener("pointerdown", handlePointerDown); + content.current.removeEventListener("scroll", syncBarToScroll); + window.removeEventListener("resize", updateBarSize); }; }, []);