// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // SPDX-License-Identifier: MIT import { useEffect, useImperativeHandle, useLayoutEffect, useRef, type ReactNode, type RefObject, } from "react"; import { useStickToBottom } from "use-stick-to-bottom"; import { ScrollArea } from "~/components/ui/scroll-area"; import { cn } from "~/lib/utils"; export interface ScrollContainerProps { className?: string; children?: ReactNode; scrollShadow?: boolean; scrollShadowColor?: string; autoScrollToBottom?: boolean; ref?: RefObject; } export interface ScrollContainerRef { scrollToBottom(): void; } export function ScrollContainer({ className, children, scrollShadow = true, scrollShadowColor = "var(--background)", autoScrollToBottom = false, ref, }: ScrollContainerProps) { const { scrollRef, contentRef, scrollToBottom, isAtBottom } = useStickToBottom({ initial: "instant" }); useImperativeHandle(ref, () => ({ scrollToBottom() { if (isAtBottom) { scrollToBottom(); } }, })); const tempScrollRef = useRef(null); const tempContentRef = useRef(null); useEffect(() => { if (!autoScrollToBottom) { tempScrollRef.current = scrollRef.current; tempContentRef.current = contentRef.current; scrollRef.current = null; contentRef.current = null; } else if (tempScrollRef.current && tempContentRef.current) { scrollRef.current = tempScrollRef.current; contentRef.current = tempContentRef.current; } }, [autoScrollToBottom, contentRef, scrollRef]); useLayoutEffect(() => { if (contentRef.current) { if (contentRef.current.parentElement) { contentRef.current.parentElement.style.display = "block"; } } }, [contentRef]); return (
{scrollShadow && ( <>
)}
{children}
); }