mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-28 12:11:53 +01:00
Auto-scroll component for websocket/grpc/sse
This commit is contained in:
89
src-web/components/core/AutoScroller.tsx
Normal file
89
src-web/components/core/AutoScroller.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import type { ReactElement, UIEvent } from 'react';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { IconButton } from './IconButton';
|
||||
|
||||
interface Props<T> {
|
||||
data: T[];
|
||||
render: (item: T, index: number) => ReactElement<HTMLElement>;
|
||||
}
|
||||
|
||||
export function AutoScroller<T>({ data, render }: Props<T>) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [autoScroll, setAutoScroll] = useState<boolean>(true);
|
||||
|
||||
// The virtualizer
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: data.length,
|
||||
getScrollElement: () => containerRef.current,
|
||||
estimateSize: () => 27, // react-virtual requires a height, so we'll give it one
|
||||
});
|
||||
|
||||
// Scroll to new items
|
||||
const handleScroll = useCallback(
|
||||
(e: UIEvent<HTMLDivElement>) => {
|
||||
const el = e.currentTarget;
|
||||
|
||||
// Set auto-scroll when container is scrolled
|
||||
const pixelsFromBottom = el.scrollHeight - (el.scrollTop + el.clientHeight);
|
||||
const newAutoScroll = pixelsFromBottom <= 0;
|
||||
if (newAutoScroll !== autoScroll) {
|
||||
setAutoScroll(newAutoScroll);
|
||||
}
|
||||
},
|
||||
[autoScroll],
|
||||
);
|
||||
|
||||
// Scroll to bottom on count change
|
||||
useLayoutEffect(() => {
|
||||
if (!autoScroll) return;
|
||||
|
||||
const el = containerRef.current;
|
||||
if (el == null) return;
|
||||
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}, [autoScroll, data.length]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative">
|
||||
{!autoScroll && (
|
||||
<div className="absolute bottom-0 right-0 m-2">
|
||||
<IconButton
|
||||
title="Lock scroll to bottom"
|
||||
icon="arrow_down"
|
||||
size="sm"
|
||||
iconSize="md"
|
||||
variant="border"
|
||||
className={'!bg-surface z-10'}
|
||||
onClick={() => setAutoScroll((v) => !v)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div ref={containerRef} className="h-full w-full overflow-y-auto" onScroll={handleScroll}>
|
||||
<div
|
||||
style={{
|
||||
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
// height: `${virtualItem.size}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
{render(data[virtualItem.index]!, virtualItem.index)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -61,6 +61,7 @@ const icons = {
|
||||
keyboard: lucide.KeyboardIcon,
|
||||
left_panel_hidden: lucide.PanelLeftOpenIcon,
|
||||
left_panel_visible: lucide.PanelLeftCloseIcon,
|
||||
lock: lucide.LockIcon,
|
||||
magic_wand: lucide.Wand2Icon,
|
||||
minus: lucide.MinusIcon,
|
||||
minus_circle: lucide.MinusCircleIcon,
|
||||
|
||||
@@ -84,7 +84,7 @@ export const JsonAttributeTree = ({
|
||||
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
||||
|
||||
const labelEl = (
|
||||
<span className={classNames(labelClassName, 'select-text group-hover:text-text-subtle')}>
|
||||
<span className={classNames(labelClassName, 'cursor-text select-text group-hover:text-text-subtle')}>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -115,7 +115,7 @@ export const JsonAttributeTree = ({
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-primary mr-1.5 pl-4 whitespace-nowrap select-text">
|
||||
<span className="text-primary mr-1.5 pl-4 whitespace-nowrap cursor-text select-text">
|
||||
{attrKey}:
|
||||
</span>
|
||||
{labelEl}
|
||||
|
||||
Reference in New Issue
Block a user