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 { serializeLibraryAsJSON } from "../data/json";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import {
|
import {
|
||||||
@ -15,6 +15,7 @@ import { duplicateElements } from "../element/newElement";
|
|||||||
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
||||||
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
|
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
|
||||||
import LibraryMenuSection from "./LibraryMenuSection";
|
import LibraryMenuSection from "./LibraryMenuSection";
|
||||||
|
import { useScrollPosition } from "../hooks/useScrollPosition";
|
||||||
import { useLibraryCache } from "../hooks/useLibraryItemSvg";
|
import { useLibraryCache } from "../hooks/useLibraryItemSvg";
|
||||||
|
|
||||||
import "./LibraryMenuItems.scss";
|
import "./LibraryMenuItems.scss";
|
||||||
@ -39,6 +40,15 @@ export default function LibraryMenuItems({
|
|||||||
id: string;
|
id: string;
|
||||||
}) {
|
}) {
|
||||||
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
|
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 { svgCache } = useLibraryCache();
|
||||||
|
|
||||||
const unpublishedItems = libraryItems.filter(
|
const unpublishedItems = libraryItems.filter(
|
||||||
@ -183,6 +193,7 @@ export default function LibraryMenuItems({
|
|||||||
flex: publishedItems.length > 0 ? 1 : "0 1 auto",
|
flex: publishedItems.length > 0 ? 1 : "0 1 auto",
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
}}
|
}}
|
||||||
|
ref={libraryContainerRef}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{!isLibraryEmpty && (
|
{!isLibraryEmpty && (
|
||||||
|
@ -58,9 +58,11 @@ function LibraryRow({
|
|||||||
|
|
||||||
const EmptyLibraryRow = () => (
|
const EmptyLibraryRow = () => (
|
||||||
<Stack.Row className="library-menu-items-container__row" gap={1}>
|
<Stack.Row className="library-menu-items-container__row" gap={1}>
|
||||||
<Stack.Col>
|
{Array.from({ length: ITEMS_PER_ROW }).map((_, index) => (
|
||||||
<div className={clsx("library-unit")} />
|
<Stack.Col key={index}>
|
||||||
</Stack.Col>
|
<div className={clsx("library-unit", "library-unit--skeleton")} />
|
||||||
|
</Stack.Col>
|
||||||
|
))}
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -20,6 +20,27 @@
|
|||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
border-width: 1px;
|
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 {
|
.library-unit__dragger {
|
||||||
@ -142,4 +163,18 @@
|
|||||||
transform: scale(0.85);
|
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__active": elements,
|
||||||
"library-unit--hover": elements && isHovered,
|
"library-unit--hover": elements && isHovered,
|
||||||
"library-unit--selected": selected,
|
"library-unit--selected": selected,
|
||||||
|
"library-unit--skeleton": !svg,
|
||||||
})}
|
})}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./Stack.scss";
|
import "./Stack.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React, { forwardRef } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
type StackProps = {
|
type StackProps = {
|
||||||
@ -10,53 +10,52 @@ type StackProps = {
|
|||||||
justifyContent?: "center" | "space-around" | "space-between";
|
justifyContent?: "center" | "space-around" | "space-between";
|
||||||
className?: string | boolean;
|
className?: string | boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
ref: React.RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RowStack = ({
|
const RowStack = forwardRef(
|
||||||
children,
|
(
|
||||||
gap,
|
{ children, gap, align, justifyContent, className, style }: StackProps,
|
||||||
align,
|
ref: React.ForwardedRef<HTMLDivElement>,
|
||||||
justifyContent,
|
) => {
|
||||||
className,
|
return (
|
||||||
style,
|
<div
|
||||||
}: StackProps) => {
|
className={clsx("Stack Stack_horizontal", className)}
|
||||||
return (
|
style={{
|
||||||
<div
|
"--gap": gap,
|
||||||
className={clsx("Stack Stack_horizontal", className)}
|
alignItems: align,
|
||||||
style={{
|
justifyContent,
|
||||||
"--gap": gap,
|
...style,
|
||||||
alignItems: align,
|
}}
|
||||||
justifyContent,
|
ref={ref}
|
||||||
...style,
|
>
|
||||||
}}
|
{children}
|
||||||
>
|
</div>
|
||||||
{children}
|
);
|
||||||
</div>
|
},
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const ColStack = ({
|
const ColStack = forwardRef(
|
||||||
children,
|
(
|
||||||
gap,
|
{ children, gap, align, justifyContent, className, style }: StackProps,
|
||||||
align,
|
ref: React.ForwardedRef<HTMLDivElement>,
|
||||||
justifyContent,
|
) => {
|
||||||
className,
|
return (
|
||||||
style,
|
<div
|
||||||
}: StackProps) => {
|
className={clsx("Stack Stack_vertical", className)}
|
||||||
return (
|
style={{
|
||||||
<div
|
"--gap": gap,
|
||||||
className={clsx("Stack Stack_vertical", className)}
|
justifyItems: align,
|
||||||
style={{
|
justifyContent,
|
||||||
"--gap": gap,
|
...style,
|
||||||
justifyItems: align,
|
}}
|
||||||
justifyContent,
|
ref={ref}
|
||||||
...style,
|
>
|
||||||
}}
|
{children}
|
||||||
>
|
</div>
|
||||||
{children}
|
);
|
||||||
</div>
|
},
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Row: RowStack,
|
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