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 containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [isAutoAddEnabled, setIsAutoAddEnabled] = useState(false);
|
const [isAutoAddEnabled, setIsAutoAddEnabled] = useState(false);
|
||||||
|
const [enabled, setEnabled] = useState(false);
|
||||||
const intervalRef = useRef<NodeJS.Timeout>();
|
const intervalRef = useRef<NodeJS.Timeout>();
|
||||||
const { isAutoScrollEnabled, scrollToBottom, scrollToTop, enableAutoScroll, disableAutoScroll } =
|
const { isAutoScrollEnabled, scrollToBottom, scrollToTop, enableAutoScroll, disableAutoScroll } =
|
||||||
useAutoScroll(containerRef);
|
useAutoScroll(containerRef, { enabled, observeSubTree: true });
|
||||||
|
|
||||||
const addMessage = () => {
|
const addMessage = () => {
|
||||||
const newMessage: Message = {
|
const newMessage: Message = {
|
||||||
|
@ -80,6 +81,13 @@ const AutoScrollDemo = () => {
|
||||||
}`}>
|
}`}>
|
||||||
Auto Add {isAutoAddEnabled ? 'ON' : 'OFF'}
|
Auto Add {isAutoAddEnabled ? 'ON' : 'OFF'}
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-4">
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
|
@ -210,7 +218,7 @@ export const ScrollAreaComponentWithAutoScroll: Story = {
|
||||||
scrollToTop,
|
scrollToTop,
|
||||||
enableAutoScroll,
|
enableAutoScroll,
|
||||||
disableAutoScroll
|
disableAutoScroll
|
||||||
} = useAutoScroll(containerRef, { observeDeepChanges: true });
|
} = useAutoScroll(containerRef, {});
|
||||||
|
|
||||||
const addCard = useCallback(() => {
|
const addCard = useCallback(() => {
|
||||||
setCards((prev) => [...prev, generateCard(prev.length + 1)]);
|
setCards((prev) => [...prev, generateCard(prev.length + 1)]);
|
||||||
|
@ -309,7 +317,7 @@ export const RapidTextAppend: Story = {
|
||||||
const intervalRef = useRef<NodeJS.Timeout>();
|
const intervalRef = useRef<NodeJS.Timeout>();
|
||||||
const { isAutoScrollEnabled, enableAutoScroll, disableAutoScroll } = useAutoScroll(
|
const { isAutoScrollEnabled, enableAutoScroll, disableAutoScroll } = useAutoScroll(
|
||||||
containerRef,
|
containerRef,
|
||||||
{ observeDeepChanges: true }
|
{ observeSubTree: true, observeCharacterData: true, observeAttributes: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const addWord = useCallback(() => {
|
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 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.
|
* 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.
|
* Duration in milliseconds to continue animations after a mutation.
|
||||||
|
@ -72,7 +74,9 @@ export const useAutoScroll = (
|
||||||
enabled = true,
|
enabled = true,
|
||||||
bottomThreshold = 50,
|
bottomThreshold = 50,
|
||||||
chaseEasing = 0.2,
|
chaseEasing = 0.2,
|
||||||
observeDeepChanges = true,
|
observeSubTree = true,
|
||||||
|
observeCharacterData = false,
|
||||||
|
observeAttributes = false,
|
||||||
animationCooldown = 500
|
animationCooldown = 500
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
|
@ -150,7 +154,8 @@ export const useAutoScroll = (
|
||||||
// Set up the mutation observer
|
// Set up the mutation observer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container || !isAutoScrollEnabled || !enabled) return;
|
|
||||||
|
if (!container || !isAutoScrollEnabled) return;
|
||||||
|
|
||||||
// Clean up previous observer if it exists
|
// Clean up previous observer if it exists
|
||||||
if (observerRef.current) {
|
if (observerRef.current) {
|
||||||
|
@ -169,9 +174,9 @@ export const useAutoScroll = (
|
||||||
// Configure observer to watch for changes
|
// Configure observer to watch for changes
|
||||||
const observerConfig = {
|
const observerConfig = {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: observeDeepChanges,
|
subtree: observeSubTree,
|
||||||
characterData: observeDeepChanges,
|
characterData: observeCharacterData,
|
||||||
attributes: observeDeepChanges
|
attributes: observeAttributes
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start observing
|
// Start observing
|
||||||
|
@ -201,19 +206,22 @@ export const useAutoScroll = (
|
||||||
isAutoScrollEnabled,
|
isAutoScrollEnabled,
|
||||||
containerRef,
|
containerRef,
|
||||||
startScrollAnimation,
|
startScrollAnimation,
|
||||||
observeDeepChanges,
|
observeSubTree,
|
||||||
|
observeCharacterData,
|
||||||
|
observeAttributes,
|
||||||
animationCooldown
|
animationCooldown
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Listen for user–initiated events. Only disable auto–scroll if the container isn't near the bottom.
|
// Listen for user–initiated events. Only disable auto–scroll if the container isn't near the bottom.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container || !isAutoScrollEnabled || !enabled) return;
|
if (!container || !isAutoScrollEnabled) return;
|
||||||
|
|
||||||
const disableAutoScrollHandler = () => {
|
const disableAutoScrollHandler = () => {
|
||||||
// Only disable auto–scroll if we're not near the bottom.
|
// Only disable auto–scroll if we're not near the bottom.
|
||||||
if (!isAtBottom(container, bottomThreshold)) {
|
if (!isAtBottom(container, bottomThreshold)) {
|
||||||
setIsAutoScrollEnabled(false);
|
setIsAutoScrollEnabled(false);
|
||||||
|
console.log('disableAutoScrollHandler', isAutoScrollEnabled, enabled);
|
||||||
|
|
||||||
// Stop any ongoing animations
|
// Stop any ongoing animations
|
||||||
if (rAFIdRef.current) {
|
if (rAFIdRef.current) {
|
||||||
|
@ -235,7 +243,7 @@ export const useAutoScroll = (
|
||||||
container.removeEventListener('touchstart', disableAutoScrollHandler);
|
container.removeEventListener('touchstart', disableAutoScrollHandler);
|
||||||
container.removeEventListener('mousedown', 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.
|
// Listen for scroll events. If the user scrolls back close to the bottom, re-enable auto–scroll.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -252,11 +260,7 @@ export const useAutoScroll = (
|
||||||
return () => {
|
return () => {
|
||||||
container.removeEventListener('scroll', onScroll);
|
container.removeEventListener('scroll', onScroll);
|
||||||
};
|
};
|
||||||
}, [containerRef, bottomThreshold]);
|
}, [containerRef, isAutoScrollEnabled, bottomThreshold]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsAutoScrollEnabled(enabled);
|
|
||||||
}, [enabled]);
|
|
||||||
|
|
||||||
// Exposed functions.
|
// Exposed functions.
|
||||||
|
|
||||||
|
|
|
@ -13,26 +13,20 @@ const autoClass = 'mx-auto max-w-[600px] w-full';
|
||||||
export const ChatContent: React.FC<{}> = React.memo(() => {
|
export const ChatContent: React.FC<{}> = React.memo(() => {
|
||||||
const chatId = useChatIndividualContextSelector((state) => state.chatId);
|
const chatId = useChatIndividualContextSelector((state) => state.chatId);
|
||||||
const chatMessageIds = useChatIndividualContextSelector((state) => state.chatMessageIds);
|
const chatMessageIds = useChatIndividualContextSelector((state) => state.chatMessageIds);
|
||||||
|
|
||||||
const containerRef = useRef<HTMLElement | null>(null);
|
const containerRef = useRef<HTMLElement | null>(null);
|
||||||
const [autoMessages, setAutoMessages] = useState<string[]>([]);
|
|
||||||
const { isAutoScrollEnabled, scrollToBottom } = useAutoScroll(containerRef, {
|
const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(containerRef, {
|
||||||
observeDeepChanges: true
|
observeSubTree: true,
|
||||||
|
enabled: false
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = document.getElementById(CHAT_CONTENT_CONTAINER_ID);
|
const container = document.getElementById(CHAT_CONTENT_CONTAINER_ID);
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
console.log('ADD IN A TODO ABOUT IS COMPLETED STREAM');
|
|
||||||
containerRef.current = container;
|
containerRef.current = container;
|
||||||
|
enableAutoScroll();
|
||||||
setInterval(() => {
|
|
||||||
setAutoMessages((prev) => [...prev, 'This is a test ' + prev.length]);
|
|
||||||
}, 22220);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log('isAutoScrollEnabled', isAutoScrollEnabled);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-40 flex h-full w-full flex-col overflow-hidden">
|
<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 || ''} />
|
<ChatMessageBlock key={messageId} messageId={messageId} chatId={chatId || ''} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<ChatInputWrapper>
|
<ChatInputWrapper>
|
||||||
|
|
|
@ -9,18 +9,23 @@ export const ChatScrollToBottom: React.FC<{
|
||||||
scrollToBottom: ReturnType<typeof useAutoScroll>['scrollToBottom'];
|
scrollToBottom: ReturnType<typeof useAutoScroll>['scrollToBottom'];
|
||||||
}> = React.memo(({ isAutoScrollEnabled, scrollToBottom }) => {
|
}> = React.memo(({ isAutoScrollEnabled, scrollToBottom }) => {
|
||||||
return (
|
return (
|
||||||
<AppTooltip title="Stick to bottom">
|
<div
|
||||||
<button
|
className={cn(
|
||||||
onClick={scrollToBottom}
|
'absolute -top-9 right-3 z-10',
|
||||||
className={cn(
|
isAutoScrollEnabled
|
||||||
'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',
|
? 'pointer-events-none scale-90 opacity-0'
|
||||||
isAutoScrollEnabled
|
: 'pointer-events-auto scale-100 cursor-pointer opacity-100'
|
||||||
? '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
|
||||||
<ChevronDown />
|
onClick={scrollToBottom}
|
||||||
</button>
|
className={
|
||||||
</AppTooltip>
|
'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