feat: recover scrolled position after Library re-opening (#6624)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
a91e401554
commit
1e3c94a37a
@ -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 && (
|
||||
|
@ -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>
|
||||
);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)}
|
||||
|
@ -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,
|
||||
|
32
src/hooks/useScrollPosition.ts
Normal file
32
src/hooks/useScrollPosition.ts
Normal 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;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user