mirror of https://github.com/buster-so/buster.git
use auto scroll with radix
This commit is contained in:
parent
9e2511d6ef
commit
5524f5fd4e
|
@ -224,7 +224,7 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
|
||||||
}
|
}
|
||||||
setIsAutoAddEnabled(false);
|
setIsAutoAddEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
intervalRef.current = setInterval(addCard, 2000);
|
intervalRef.current = setInterval(addCard, 1000);
|
||||||
setIsAutoAddEnabled(true);
|
setIsAutoAddEnabled(true);
|
||||||
enableAutoScroll(); // Enable auto-scroll when auto-adding cards
|
enableAutoScroll(); // Enable auto-scroll when auto-adding cards
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for configuring the auto-scroll behavior
|
||||||
|
*/
|
||||||
interface UseAutoScrollOptions {
|
interface UseAutoScrollOptions {
|
||||||
/** Debounce delay in milliseconds for scroll events */
|
/** Debounce delay in milliseconds for scroll events */
|
||||||
debounceDelay?: number;
|
debounceDelay?: number;
|
||||||
|
@ -8,10 +11,13 @@ interface UseAutoScrollOptions {
|
||||||
scrollBehavior?: ScrollBehavior;
|
scrollBehavior?: ScrollBehavior;
|
||||||
/** Whether the auto-scroll functionality is enabled */
|
/** Whether the auto-scroll functionality is enabled */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
/** Whether to observe deep changes */
|
/** Whether to observe deep changes in the DOM tree */
|
||||||
observeDeepChanges?: boolean;
|
observeDeepChanges?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return type for the useAutoScroll hook
|
||||||
|
*/
|
||||||
interface UseAutoScrollReturn {
|
interface UseAutoScrollReturn {
|
||||||
/** Whether auto-scrolling is currently enabled */
|
/** Whether auto-scrolling is currently enabled */
|
||||||
isAutoScrollEnabled: boolean;
|
isAutoScrollEnabled: boolean;
|
||||||
|
@ -21,18 +27,47 @@ interface UseAutoScrollReturn {
|
||||||
scrollToTop: (behavior?: ScrollBehavior) => void;
|
scrollToTop: (behavior?: ScrollBehavior) => void;
|
||||||
/** Manually scroll to a specific node */
|
/** Manually scroll to a specific node */
|
||||||
scrollToNode: (node: HTMLElement, behavior?: ScrollBehavior) => void;
|
scrollToNode: (node: HTMLElement, behavior?: ScrollBehavior) => void;
|
||||||
|
|
||||||
/** Enable auto-scrolling */
|
/** Enable auto-scrolling */
|
||||||
enableAutoScroll: () => void;
|
enableAutoScroll: () => void;
|
||||||
/** Disable auto-scrolling */
|
/** Disable auto-scrolling */
|
||||||
disableAutoScroll: () => void;
|
disableAutoScroll: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the scrollable element is at the bottom within a given threshold
|
||||||
|
* @param element - The HTML element to check
|
||||||
|
* @param threshold - The pixel threshold to consider as "at bottom"
|
||||||
|
* @returns boolean indicating if the element is scrolled to the bottom
|
||||||
|
*/
|
||||||
const isAtBottom = (element: HTMLElement, threshold = 30) => {
|
const isAtBottom = (element: HTMLElement, threshold = 30) => {
|
||||||
const { scrollHeight, scrollTop, clientHeight } = element;
|
const { scrollHeight, scrollTop, clientHeight } = element;
|
||||||
return scrollHeight - (scrollTop + clientHeight) <= threshold;
|
return scrollHeight - (scrollTop + clientHeight) <= threshold;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React hook that provides auto-scrolling functionality for a container element.
|
||||||
|
* It automatically scrolls to the bottom when new content is added and provides
|
||||||
|
* manual scroll controls.
|
||||||
|
*
|
||||||
|
* @param containerRef - React ref object pointing to the scrollable container element
|
||||||
|
* @param options - Configuration options for the auto-scroll behavior
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
* const {
|
||||||
|
* isAutoScrollEnabled,
|
||||||
|
* scrollToBottom,
|
||||||
|
* enableAutoScroll,
|
||||||
|
* disableAutoScroll
|
||||||
|
* } = useAutoScroll(containerRef, {
|
||||||
|
* debounceDelay: 150,
|
||||||
|
* scrollBehavior: 'smooth'
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @returns An object containing the auto-scroll state and control functions
|
||||||
|
*/
|
||||||
export const useAutoScroll = (
|
export const useAutoScroll = (
|
||||||
containerRef: React.RefObject<HTMLElement>,
|
containerRef: React.RefObject<HTMLElement>,
|
||||||
options: UseAutoScrollOptions = {}
|
options: UseAutoScrollOptions = {}
|
||||||
|
@ -44,10 +79,15 @@ export const useAutoScroll = (
|
||||||
observeDeepChanges = true
|
observeDeepChanges = true
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
|
/** Current state of auto-scroll functionality */
|
||||||
const [isAutoScrollEnabled, setIsAutoScrollEnabled] = useState(enabled);
|
const [isAutoScrollEnabled, setIsAutoScrollEnabled] = useState(enabled);
|
||||||
|
/** Tracks if the container was at the bottom during the last scroll event */
|
||||||
const wasAtBottom = useRef(true);
|
const wasAtBottom = useRef(true);
|
||||||
|
/** Tracks if a scroll operation is currently in progress */
|
||||||
const isScrollingRef = useRef(false);
|
const isScrollingRef = useRef(false);
|
||||||
|
/** Reference for the mutation observer's debounced callback */
|
||||||
const mutationDebounceRef = useRef<number>();
|
const mutationDebounceRef = useRef<number>();
|
||||||
|
/** Flag to indicate if a scroll is being forced programmatically */
|
||||||
const forceScrollRef = useRef(false);
|
const forceScrollRef = useRef(false);
|
||||||
|
|
||||||
// Update isAutoScrollEnabled when enabled prop changes
|
// Update isAutoScrollEnabled when enabled prop changes
|
||||||
|
@ -55,6 +95,10 @@ export const useAutoScroll = (
|
||||||
setIsAutoScrollEnabled(enabled);
|
setIsAutoScrollEnabled(enabled);
|
||||||
}, [enabled]);
|
}, [enabled]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls the container to the bottom
|
||||||
|
* @param behavior - The scroll behavior to use ('auto', 'smooth', or 'instant')
|
||||||
|
*/
|
||||||
const scrollToBottom = useCallback(
|
const scrollToBottom = useCallback(
|
||||||
(behavior: ScrollBehavior = scrollBehavior) => {
|
(behavior: ScrollBehavior = scrollBehavior) => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
@ -92,6 +136,10 @@ export const useAutoScroll = (
|
||||||
[containerRef, scrollBehavior, enabled]
|
[containerRef, scrollBehavior, enabled]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls the container to the top
|
||||||
|
* @param behavior - The scroll behavior to use ('auto', 'smooth', or 'instant')
|
||||||
|
*/
|
||||||
const scrollToTop = useCallback(
|
const scrollToTop = useCallback(
|
||||||
(behavior: ScrollBehavior = scrollBehavior) => {
|
(behavior: ScrollBehavior = scrollBehavior) => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
|
@ -104,6 +152,11 @@ export const useAutoScroll = (
|
||||||
[containerRef, scrollBehavior]
|
[containerRef, scrollBehavior]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls the container to bring a specific node into view
|
||||||
|
* @param node - The HTML element to scroll into view
|
||||||
|
* @param behavior - The scroll behavior to use ('auto', 'smooth', or 'instant')
|
||||||
|
*/
|
||||||
const scrollToNode = useCallback(
|
const scrollToNode = useCallback(
|
||||||
(node: HTMLElement, behavior: ScrollBehavior = scrollBehavior) => {
|
(node: HTMLElement, behavior: ScrollBehavior = scrollBehavior) => {
|
||||||
if (!containerRef.current || !node) return;
|
if (!containerRef.current || !node) return;
|
||||||
|
@ -152,7 +205,9 @@ export const useAutoScroll = (
|
||||||
[containerRef, scrollBehavior, enabled]
|
[containerRef, scrollBehavior, enabled]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Debounced scroll handler
|
/**
|
||||||
|
* Debounced scroll event handler that manages auto-scroll state based on scroll position
|
||||||
|
*/
|
||||||
const handleScrollThrottled = useCallback(
|
const handleScrollThrottled = useCallback(
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
if (!containerRef.current || forceScrollRef.current || !enabled) return;
|
if (!containerRef.current || forceScrollRef.current || !enabled) return;
|
||||||
|
@ -173,7 +228,9 @@ export const useAutoScroll = (
|
||||||
[containerRef, enabled]
|
[containerRef, enabled]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Immediate scroll handler that calls the debounced version
|
/**
|
||||||
|
* Immediate scroll handler that triggers the debounced version
|
||||||
|
*/
|
||||||
const handleScroll = useCallback(() => {
|
const handleScroll = useCallback(() => {
|
||||||
if (forceScrollRef.current || !enabled) return;
|
if (forceScrollRef.current || !enabled) return;
|
||||||
|
|
||||||
|
@ -234,12 +291,18 @@ export const useAutoScroll = (
|
||||||
};
|
};
|
||||||
}, [containerRef, handleScroll, handleScrollThrottled, enabled]);
|
}, [containerRef, handleScroll, handleScrollThrottled, enabled]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables auto-scroll functionality and immediately scrolls to bottom
|
||||||
|
*/
|
||||||
const enableAutoScroll = useCallback(() => {
|
const enableAutoScroll = useCallback(() => {
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
setIsAutoScrollEnabled(true);
|
setIsAutoScrollEnabled(true);
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, [scrollToBottom, enabled]);
|
}, [scrollToBottom, enabled]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables auto-scroll functionality
|
||||||
|
*/
|
||||||
const disableAutoScroll = useCallback(() => {
|
const disableAutoScroll = useCallback(() => {
|
||||||
setIsAutoScrollEnabled(false);
|
setIsAutoScrollEnabled(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
Loading…
Reference in New Issue