Files
kandev/apps/web/components/task/task-sidebar-scroll-area.tsx
T
2026-07-24 12:08:07 +01:00

61 lines
2.1 KiB
TypeScript

"use client";
import { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from "react";
import { ScrollArea } from "@kandev/ui/scroll-area";
const SCROLL_END_TOLERANCE_PX = 1;
export function TaskSidebarScrollArea({ children }: { children: ReactNode }) {
const scrollRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [canScrollDown, setCanScrollDown] = useState(false);
const updateScrollCue = useCallback(() => {
const element = scrollRef.current;
if (!element) return;
const remaining = element.scrollHeight - element.clientHeight - element.scrollTop;
setCanScrollDown(remaining > SCROLL_END_TOLERANCE_PX);
}, []);
useLayoutEffect(() => {
const scrollElement = scrollRef.current;
if (!scrollElement) return;
updateScrollCue();
scrollElement.addEventListener("scroll", updateScrollCue, { passive: true });
window.addEventListener("resize", updateScrollCue);
const observer =
typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updateScrollCue);
observer?.observe(scrollElement);
if (contentRef.current) observer?.observe(contentRef.current);
return () => {
scrollElement.removeEventListener("scroll", updateScrollCue);
window.removeEventListener("resize", updateScrollCue);
observer?.disconnect();
};
}, [updateScrollCue]);
return (
<ScrollArea
type="auto"
className="task-sidebar-scroll-root min-h-0 flex-1"
viewportProps={{
ref: scrollRef,
// Radix uses an intrinsic-width table wrapper so generic scroll areas
// can grow horizontally. Sidebar rows must stay viewport-width instead:
// otherwise a long title pushes its actions beyond the visible edge and
// never overflows its own ScrollOnOverflow container.
className: "task-sidebar-scroll [&>div]:!block [&>div]:!min-w-0 [&>div]:!w-full",
"data-can-scroll-down": canScrollDown,
"data-testid": "task-sidebar-scroll",
}}
>
<div ref={contentRef} className="space-y-4">
{children}
</div>
</ScrollArea>
);
}