web: replace react-virtual with react-virtuouso

This commit is contained in:
Abdullah Atta
2023-12-13 15:33:15 +05:00
parent baa2d1a389
commit cfd53a8b2c
3 changed files with 141 additions and 45 deletions

View File

@@ -33,7 +33,7 @@
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.0-beta.68",
"@tanstack/react-virtual": "^3.0.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
@@ -72,6 +72,7 @@
"react-modal": "3.16.1",
"react-qrcode-logo": "^2.9.0",
"react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2",
"timeago.js": "4.0.2",
"tinycolor2": "^1.6.0",
"w3c-keyname": "^2.2.6",
@@ -37715,6 +37716,8 @@
},
"node_modules/@tanstack/react-virtual": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.1.tgz",
"integrity": "sha512-IFOFuRUTaiM/yibty9qQ9BfycQnYXIDHGP2+cU+0LrFFGNhVxCXSQnaY6wkX8uJVteFEBjUondX0Hmpp7TNcag==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.1.3"
@@ -37730,6 +37733,8 @@
},
"node_modules/@tanstack/virtual-core": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz",
"integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==",
"license": "MIT",
"funding": {
"type": "github",
@@ -43449,6 +43454,18 @@
"react-dom": "16.x || 17.x || 18.x"
}
},
"node_modules/react-virtuoso": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.6.2.tgz",
"integrity": "sha512-vvlqvzPif+MvBrJ09+hJJrVY0xJK9yran+A+/1iwY78k0YCVKsyoNPqoLxOxzYPggspNBNXqUXEcvckN29OxyQ==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16 || >=17 || >= 18",
"react-dom": ">=16 || >=17 || >= 18"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"license": "MIT",

View File

@@ -32,7 +32,7 @@
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.0-beta.68",
"@tanstack/react-virtual": "^3.0.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
@@ -71,6 +71,7 @@
"react-modal": "3.16.1",
"react-qrcode-logo": "^2.9.0",
"react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2",
"timeago.js": "4.0.2",
"tinycolor2": "^1.6.0",
"w3c-keyname": "^2.2.6",

View File

@@ -17,18 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useRef, useState } from "react";
import { Flex, Button } from "@theme-ui/components";
import { forwardRef, useEffect, useRef, useState } from "react";
import { Flex, Button, Box } from "@theme-ui/components";
import { Plus } from "../icons";
import {
useStore as useSelectionStore,
store as selectionStore
} from "../../stores/selection-store";
import GroupHeader from "../group-header";
import { ListItemWrapper } from "./list-profiles";
import { DEFAULT_ITEM_HEIGHT, ListItemWrapper } from "./list-profiles";
import Announcements from "../announcements";
import { ListLoader } from "../loaders/list-loader";
import { FlexScrollContainer } from "../scroll-container";
import ScrollContainer from "../scroll-container";
import { useKeyboardListNavigation } from "../../hooks/use-keyboard-list-navigation";
import { Context } from "./types";
import {
@@ -37,9 +37,29 @@ import {
Item,
isGroupHeader
} from "@notesnook/core";
import { VirtualizedList } from "../virtualized-list";
import { ScrollToOptions, Virtualizer } from "@tanstack/react-virtual";
import { useResolvedItem } from "./resolved-item";
import {
ItemProps,
ScrollerProps,
Virtuoso,
VirtuosoHandle
} from "react-virtuoso";
import Skeleton from "react-loading-skeleton";
export const CustomScrollbarsVirtualList = forwardRef<
HTMLDivElement,
ScrollerProps
>(function CustomScrollbarsVirtualList(props, ref) {
return (
<ScrollContainer
{...props}
forwardedRef={(sRef) => {
if (typeof ref === "function") ref(sRef);
else if (ref) ref.current = sRef;
}}
/>
);
});
type ListContainerProps = {
group?: GroupingKey;
@@ -68,7 +88,8 @@ function ListContainer(props: ListContainerProps) {
(store) => store.toggleSelectionMode
);
const listRef = useRef<Virtualizer<Element, Element>>();
const listRef = useRef<VirtuosoHandle>(null);
const listContainerRef = useRef(null);
useEffect(() => {
return () => {
@@ -119,25 +140,24 @@ function ListContainer(props: ListContainerProps) {
</>
) : (
<>
<FlexScrollContainer
style={{ display: "flex", flexDirection: "column", flex: 1 }}
<Flex
ref={listContainerRef}
variant="columnFill"
data-test-id={`${group}-list`}
>
{header ? header : <Announcements />}
<VirtualizedList
virtualizerRef={listRef}
estimatedSize={50}
getItemKey={(index) => items.getKey(index)}
items={items.ids}
mode="dynamic"
tabIndex={-1}
overscan={10}
<Virtuoso
ref={listRef}
computeItemKey={(index) => items.getKey(index)}
defaultItemHeight={DEFAULT_ITEM_HEIGHT}
totalCount={items.ids.length}
onBlur={() => setFocusedGroupIndex(-1)}
onKeyDown={(e) => onKeyDown(e.nativeEvent)}
itemWrapperProps={(_, index) => ({
onFocus: () => onFocus(index),
onMouseDown: (e) => onMouseDown(e.nativeEvent, index)
})}
components={{
Scroller: CustomScrollbarsVirtualList,
Item: VirtuosoItem,
Header: () => (header ? header : <Announcements />)
}}
increaseViewportBy={{ top: 10, bottom: 10 }}
context={{
items,
group,
@@ -147,11 +167,15 @@ function ListContainer(props: ListContainerProps) {
scrollToIndex: listRef.current?.scrollToIndex,
focusGroup: setFocusedGroupIndex,
context,
compact
compact,
onMouseDown,
onFocus
}}
renderItem={ItemRenderer}
itemContent={(index, _data, context) => (
<ItemRenderer context={context} index={index} />
)}
/>
</FlexScrollContainer>
</Flex>
</>
)}
{button && (
@@ -194,6 +218,9 @@ type ListContext = {
focusGroup: (index: number) => void;
context?: Context;
compact?: boolean;
onMouseDown: (e: MouseEvent, itemIndex: number) => void;
onFocus: (itemIndex: number) => void;
};
function ItemRenderer({
index,
@@ -214,7 +241,37 @@ function ItemRenderer({
compact
} = context;
const resolvedItem = useResolvedItem({ index, items });
if (!resolvedItem) return <div style={{ height: 50, width: "100%" }}></div>;
if (!resolvedItem || !resolvedItem.item)
return (
<Box key="list-item-skeleton" sx={{ py: 2, px: 1 }}>
<Skeleton
enableAnimation={false}
height={16}
width={`50%`}
style={{ marginBottom: 5 }}
/>
<Skeleton height={12} count={2} />
<Flex>
<Skeleton enableAnimation={false} height={10} inline width={50} />
<Skeleton
enableAnimation={false}
height={10}
inline
width={10}
circle
style={{ marginLeft: 5 }}
/>
<Skeleton
enableAnimation={false}
height={10}
inline
width={10}
circle
style={{ marginLeft: 5 }}
/>
</Flex>
</Box>
);
return (
<>
@@ -241,7 +298,7 @@ function ItemRenderer({
groups={async () => (items.groups ? items.groups() : [])}
onJump={(index) => {
scrollToIndex?.(index, {
align: "center",
// align: "center",
behavior: "auto"
});
focusGroup(index);
@@ -260,6 +317,26 @@ function ItemRenderer({
);
}
function VirtuosoItem({
item: _item,
context,
...props
}: ItemProps<string> & {
context?: ListContext;
}) {
return (
<div
{...props}
onFocus={() => context?.onFocus(props["data-item-index"])}
onMouseDown={(e) =>
context?.onMouseDown(e.nativeEvent, props["data-item-index"])
}
>
{props.children}
</div>
);
}
/**
* Scroll the element at the specified index into view and
* wait until it renders into the DOM. This function keeps
@@ -268,28 +345,29 @@ function ItemRenderer({
* 50ms interval.
*/
function waitForElement(
list: Virtualizer<Element, Element>,
list: VirtuosoHandle,
index: number,
elementId: string,
callback: (element: HTMLElement) => void
) {
let waitInterval = 0;
let maxAttempts = 3;
list.scrollToIndex(index);
function scrollDone() {
if (!maxAttempts) return;
clearTimeout(waitInterval);
list.scrollIntoView({
index,
done: function scrollDone() {
if (!maxAttempts) return;
clearTimeout(waitInterval);
const element = document.getElementById(elementId);
if (!element) {
--maxAttempts;
waitInterval = setTimeout(() => {
scrollDone();
}, 50) as unknown as number;
return;
const element = document.getElementById(elementId);
if (!element) {
--maxAttempts;
waitInterval = setTimeout(() => {
scrollDone();
}, 50) as unknown as number;
return;
}
callback(element);
}
callback(element);
}
scrollDone();
});
}