mirror of https://github.com/buster-so/buster.git
finalized auto scrolling 🏴☠️
This commit is contained in:
parent
55d2a1dc3c
commit
4344a5ee09
|
@ -14,9 +14,10 @@ const AutoScrollDemo = () => {
|
|||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [isAutoAddEnabled, setIsAutoAddEnabled] = useState(false);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const intervalRef = useRef<NodeJS.Timeout>();
|
||||
const { isAutoScrollEnabled, scrollToBottom, scrollToTop, enableAutoScroll, disableAutoScroll } =
|
||||
useAutoScroll(containerRef);
|
||||
useAutoScroll(containerRef, { enabled, observeSubTree: true });
|
||||
|
||||
const addMessage = () => {
|
||||
const newMessage: Message = {
|
||||
|
@ -80,6 +81,13 @@ const AutoScrollDemo = () => {
|
|||
}`}>
|
||||
Auto Add {isAutoAddEnabled ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEnabled(!enabled)}
|
||||
className={`rounded px-4 py-2 text-white ${
|
||||
enabled ? 'bg-green-500 hover:bg-green-600' : 'bg-red-500 hover:bg-red-600'
|
||||
}`}>
|
||||
Enabled {enabled ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
|
@ -210,7 +218,7 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
|
|||
scrollToTop,
|
||||
enableAutoScroll,
|
||||
disableAutoScroll
|
||||
} = useAutoScroll(containerRef, { observeDeepChanges: true });
|
||||
} = useAutoScroll(containerRef, {});
|
||||
|
||||
const addCard = useCallback(() => {
|
||||
setCards((prev) => [...prev, generateCard(prev.length + 1)]);
|
||||
|
@ -309,7 +317,7 @@ export const RapidTextAppend: Story = {
|
|||
const intervalRef = useRef<NodeJS.Timeout>();
|
||||
const { isAutoScrollEnabled, enableAutoScroll, disableAutoScroll } = useAutoScroll(
|
||||
containerRef,
|
||||
{ observeDeepChanges: true }
|
||||
{ observeSubTree: true, observeCharacterData: true, observeAttributes: false }
|
||||
);
|
||||
|
||||
const addWord = useCallback(() => {
|
||||
|
|
|
@ -20,7 +20,9 @@ interface UseAutoScrollOptions {
|
|||
* If true, the hook will observe changes to the container's content and scroll position.
|
||||
* If false, the hook will only observe changes to the container's scroll position.
|
||||
*/
|
||||
observeDeepChanges?: boolean;
|
||||
observeSubTree?: boolean;
|
||||
observeCharacterData?: boolean;
|
||||
observeAttributes?: boolean;
|
||||
|
||||
/**
|
||||
* Duration in milliseconds to continue animations after a mutation.
|
||||
|
@ -72,7 +74,9 @@ export const useAutoScroll = (
|
|||
enabled = true,
|
||||
bottomThreshold = 50,
|
||||
chaseEasing = 0.2,
|
||||
observeDeepChanges = true,
|
||||
observeSubTree = true,
|
||||
observeCharacterData = false,
|
||||
observeAttributes = false,
|
||||
animationCooldown = 500
|
||||
} = options;
|
||||
|
||||
|
@ -150,7 +154,8 @@ export const useAutoScroll = (
|
|||
// Set up the mutation observer
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container || !isAutoScrollEnabled || !enabled) return;
|
||||
|
||||
if (!container || !isAutoScrollEnabled) return;
|
||||
|
||||
// Clean up previous observer if it exists
|
||||
if (observerRef.current) {
|
||||
|
@ -169,9 +174,9 @@ export const useAutoScroll = (
|
|||
// Configure observer to watch for changes
|
||||
const observerConfig = {
|
||||
childList: true,
|
||||
subtree: observeDeepChanges,
|
||||
characterData: observeDeepChanges,
|
||||
attributes: observeDeepChanges
|
||||
subtree: observeSubTree,
|
||||
characterData: observeCharacterData,
|
||||
attributes: observeAttributes
|
||||
};
|
||||
|
||||
// Start observing
|
||||
|
@ -201,19 +206,22 @@ export const useAutoScroll = (
|
|||
isAutoScrollEnabled,
|
||||
containerRef,
|
||||
startScrollAnimation,
|
||||
observeDeepChanges,
|
||||
observeSubTree,
|
||||
observeCharacterData,
|
||||
observeAttributes,
|
||||
animationCooldown
|
||||
]);
|
||||
|
||||
// Listen for user–initiated events. Only disable auto–scroll if the container isn't near the bottom.
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container || !isAutoScrollEnabled || !enabled) return;
|
||||
if (!container || !isAutoScrollEnabled) return;
|
||||
|
||||
const disableAutoScrollHandler = () => {
|
||||
// Only disable auto–scroll if we're not near the bottom.
|
||||
if (!isAtBottom(container, bottomThreshold)) {
|
||||
setIsAutoScrollEnabled(false);
|
||||
console.log('disableAutoScrollHandler', isAutoScrollEnabled, enabled);
|
||||
|
||||
// Stop any ongoing animations
|
||||
if (rAFIdRef.current) {
|
||||
|
@ -235,7 +243,7 @@ export const useAutoScroll = (
|
|||
container.removeEventListener('touchstart', disableAutoScrollHandler);
|
||||
container.removeEventListener('mousedown', disableAutoScrollHandler);
|
||||
};
|
||||
}, [containerRef, bottomThreshold, enabled]);
|
||||
}, [containerRef, bottomThreshold, isAutoScrollEnabled]);
|
||||
|
||||
// Listen for scroll events. If the user scrolls back close to the bottom, re-enable auto–scroll.
|
||||
useEffect(() => {
|
||||
|
@ -252,11 +260,7 @@ export const useAutoScroll = (
|
|||
return () => {
|
||||
container.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
}, [containerRef, bottomThreshold]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsAutoScrollEnabled(enabled);
|
||||
}, [enabled]);
|
||||
}, [containerRef, isAutoScrollEnabled, bottomThreshold]);
|
||||
|
||||
// Exposed functions.
|
||||
|
||||
|
|
|
@ -13,26 +13,20 @@ const autoClass = 'mx-auto max-w-[600px] w-full';
|
|||
export const ChatContent: React.FC<{}> = React.memo(() => {
|
||||
const chatId = useChatIndividualContextSelector((state) => state.chatId);
|
||||
const chatMessageIds = useChatIndividualContextSelector((state) => state.chatMessageIds);
|
||||
|
||||
const containerRef = useRef<HTMLElement | null>(null);
|
||||
const [autoMessages, setAutoMessages] = useState<string[]>([]);
|
||||
const { isAutoScrollEnabled, scrollToBottom } = useAutoScroll(containerRef, {
|
||||
observeDeepChanges: true
|
||||
|
||||
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(containerRef, {
|
||||
observeSubTree: true,
|
||||
enabled: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const container = document.getElementById(CHAT_CONTENT_CONTAINER_ID);
|
||||
if (!container) return;
|
||||
console.log('ADD IN A TODO ABOUT IS COMPLETED STREAM');
|
||||
containerRef.current = container;
|
||||
|
||||
setInterval(() => {
|
||||
setAutoMessages((prev) => [...prev, 'This is a test ' + prev.length]);
|
||||
}, 22220);
|
||||
enableAutoScroll();
|
||||
}, []);
|
||||
|
||||
console.log('isAutoScrollEnabled', isAutoScrollEnabled);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-40 flex h-full w-full flex-col overflow-hidden">
|
||||
|
@ -41,14 +35,6 @@ export const ChatContent: React.FC<{}> = React.memo(() => {
|
|||
<ChatMessageBlock key={messageId} messageId={messageId} chatId={chatId || ''} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="mx-2 flex flex-wrap gap-1 overflow-hidden">
|
||||
{autoMessages.map((message, index) => (
|
||||
<span key={index} className="w-fit rounded border p-0.5 text-red-700">
|
||||
{message}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChatInputWrapper>
|
||||
|
|
|
@ -9,18 +9,23 @@ export const ChatScrollToBottom: React.FC<{
|
|||
scrollToBottom: ReturnType<typeof useAutoScroll>['scrollToBottom'];
|
||||
}> = React.memo(({ isAutoScrollEnabled, scrollToBottom }) => {
|
||||
return (
|
||||
<AppTooltip title="Stick to bottom">
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
className={cn(
|
||||
'bg-background/90 hover:bg-item-hover/90 absolute -top-9 right-3 z-10 rounded-full border p-2 shadow transition-all duration-300 hover:scale-105 hover:shadow-md',
|
||||
isAutoScrollEnabled
|
||||
? 'pointer-events-none scale-90 opacity-0'
|
||||
: 'pointer-events-auto scale-100 cursor-pointer opacity-100'
|
||||
)}>
|
||||
<ChevronDown />
|
||||
</button>
|
||||
</AppTooltip>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute -top-9 right-3 z-10',
|
||||
isAutoScrollEnabled
|
||||
? 'pointer-events-none scale-90 opacity-0'
|
||||
: 'pointer-events-auto scale-100 cursor-pointer opacity-100'
|
||||
)}>
|
||||
<AppTooltip title="Stick to bottom" sideOffset={12} delayDuration={500}>
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
className={
|
||||
'bg-background/90 hover:bg-item-hover/90 cursor-pointer rounded-full border p-2 shadow transition-all duration-300 hover:scale-105 hover:shadow-md'
|
||||
}>
|
||||
<ChevronDown />
|
||||
</button>
|
||||
</AppTooltip>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue