feat: recover scrolled position after Library re-opening (#6624)

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Arnost Pleskot 2023-05-31 10:22:02 +02:00 committed by GitHub
parent a91e401554
commit 1e3c94a37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 129 additions and 49 deletions

View File

@ -1,4 +1,4 @@
import React, { useCallback, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { serializeLibraryAsJSON } from "../data/json";
import { t } from "../i18n";
import {
@ -15,6 +15,7 @@ import { duplicateElements } from "../element/newElement";
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
import LibraryMenuSection from "./LibraryMenuSection";
import { useScrollPosition } from "../hooks/useScrollPosition";
import { useLibraryCache } from "../hooks/useLibraryItemSvg";
import "./LibraryMenuItems.scss";
@ -39,6 +40,15 @@ export default function LibraryMenuItems({
id: string;
}) {
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
const libraryContainerRef = useRef<HTMLDivElement>(null);
const scrollPosition = useScrollPosition<HTMLDivElement>(libraryContainerRef);
// This effect has to be called only on first render, therefore `scrollPosition` isn't in the dependency array
useEffect(() => {
if (scrollPosition > 0) {
libraryContainerRef.current?.scrollTo(0, scrollPosition);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const { svgCache } = useLibraryCache();
const unpublishedItems = libraryItems.filter(
@ -183,6 +193,7 @@ export default function LibraryMenuItems({
flex: publishedItems.length > 0 ? 1 : "0 1 auto",
marginBottom: 0,
}}
ref={libraryContainerRef}
>
<>
{!isLibraryEmpty && (

View File

@ -58,9 +58,11 @@ function LibraryRow({
const EmptyLibraryRow = () => (
<Stack.Row className="library-menu-items-container__row" gap={1}>
<Stack.Col>
<div className={clsx("library-unit")} />
</Stack.Col>
{Array.from({ length: ITEMS_PER_ROW }).map((_, index) => (
<Stack.Col key={index}>
<div className={clsx("library-unit", "library-unit--skeleton")} />
</Stack.Col>
))}
</Stack.Row>
);

View File

@ -20,6 +20,27 @@
border-color: var(--color-primary);
border-width: 1px;
}
&--skeleton {
opacity: 0.5;
background: linear-gradient(
-45deg,
var(--color-gray-10),
var(--color-gray-20),
var(--color-gray-10)
);
background-size: 200% 200%;
animation: library-unit__skeleton-opacity-animation 0.3s linear;
}
}
&.theme--dark .library-unit--skeleton {
background-image: linear-gradient(
-45deg,
var(--color-gray-100),
var(--color-gray-80),
var(--color-gray-100)
);
}
.library-unit__dragger {
@ -142,4 +163,18 @@
transform: scale(0.85);
}
}
@keyframes library-unit__skeleton-opacity-animation {
0% {
opacity: 0;
}
75% {
opacity: 0;
}
100% {
opacity: 0.5;
}
}
}

View File

@ -58,6 +58,7 @@ export const LibraryUnit = ({
"library-unit__active": elements,
"library-unit--hover": elements && isHovered,
"library-unit--selected": selected,
"library-unit--skeleton": !svg,
})}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}

View File

@ -1,6 +1,6 @@
import "./Stack.scss";
import React from "react";
import React, { forwardRef } from "react";
import clsx from "clsx";
type StackProps = {
@ -10,53 +10,52 @@ type StackProps = {
justifyContent?: "center" | "space-around" | "space-between";
className?: string | boolean;
style?: React.CSSProperties;
ref: React.RefObject<HTMLDivElement>;
};
const RowStack = ({
children,
gap,
align,
justifyContent,
className,
style,
}: StackProps) => {
return (
<div
className={clsx("Stack Stack_horizontal", className)}
style={{
"--gap": gap,
alignItems: align,
justifyContent,
...style,
}}
>
{children}
</div>
);
};
const RowStack = forwardRef(
(
{ children, gap, align, justifyContent, className, style }: StackProps,
ref: React.ForwardedRef<HTMLDivElement>,
) => {
return (
<div
className={clsx("Stack Stack_horizontal", className)}
style={{
"--gap": gap,
alignItems: align,
justifyContent,
...style,
}}
ref={ref}
>
{children}
</div>
);
},
);
const ColStack = ({
children,
gap,
align,
justifyContent,
className,
style,
}: StackProps) => {
return (
<div
className={clsx("Stack Stack_vertical", className)}
style={{
"--gap": gap,
justifyItems: align,
justifyContent,
...style,
}}
>
{children}
</div>
);
};
const ColStack = forwardRef(
(
{ children, gap, align, justifyContent, className, style }: StackProps,
ref: React.ForwardedRef<HTMLDivElement>,
) => {
return (
<div
className={clsx("Stack Stack_vertical", className)}
style={{
"--gap": gap,
justifyItems: align,
justifyContent,
...style,
}}
ref={ref}
>
{children}
</div>
);
},
);
export default {
Row: RowStack,

View File

@ -0,0 +1,32 @@
import { useEffect } from "react";
import { atom, useAtom } from "jotai";
import throttle from "lodash.throttle";
const scrollPositionAtom = atom<number>(0);
export const useScrollPosition = <T extends HTMLElement>(
elementRef: React.RefObject<T>,
) => {
const [scrollPosition, setScrollPosition] = useAtom(scrollPositionAtom);
useEffect(() => {
const { current: element } = elementRef;
if (!element) {
return;
}
const handleScroll = throttle(() => {
const { scrollTop } = element;
setScrollPosition(scrollTop);
}, 200);
element.addEventListener("scroll", handleScroll);
return () => {
handleScroll.cancel();
element.removeEventListener("scroll", handleScroll);
};
}, [elementRef, setScrollPosition]);
return scrollPosition;
};