mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-21 06:52:18 +00:00
fix sticky and scrolling of SidebarPart
This commit is contained in:
parent
14a46c4b85
commit
ba7c079b75
2 changed files with 62 additions and 32 deletions
|
|
@ -170,6 +170,7 @@ export function AccountSettings(props: {
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
|
const prevCategoryRef = useRef<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hash = window.location.hash;
|
const hash = window.location.hash;
|
||||||
|
|
@ -194,6 +195,22 @@ export function SettingsPage() {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Scroll to top when category changes (but not on initial load or when searching)
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
prevCategoryRef.current !== null &&
|
||||||
|
prevCategoryRef.current !== selectedCategory &&
|
||||||
|
!searchQuery.trim()
|
||||||
|
) {
|
||||||
|
// Only scroll to top if we're actually switching categories (not initial load)
|
||||||
|
// Use requestAnimationFrame to ensure DOM has updated
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
prevCategoryRef.current = selectedCategory;
|
||||||
|
}, [selectedCategory, searchQuery]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const activeTheme = useThemeStore((s) => s.theme);
|
const activeTheme = useThemeStore((s) => s.theme);
|
||||||
const setTheme = useThemeStore((s) => s.setTheme);
|
const setTheme = useThemeStore((s) => s.setTheme);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Sticky from "react-sticky-el";
|
|
||||||
|
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { SidebarLink, SidebarSection } from "@/components/layout/Sidebar";
|
import { SidebarLink, SidebarSection } from "@/components/layout/Sidebar";
|
||||||
|
|
@ -9,8 +8,6 @@ import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
|
|
||||||
import { AppInfoPart } from "./AppInfoPart";
|
import { AppInfoPart } from "./AppInfoPart";
|
||||||
|
|
||||||
const rem = 16;
|
|
||||||
|
|
||||||
export function SidebarPart(props: {
|
export function SidebarPart(props: {
|
||||||
selectedCategory: string | null;
|
selectedCategory: string | null;
|
||||||
setSelectedCategory: (category: string | null) => void;
|
setSelectedCategory: (category: string | null) => void;
|
||||||
|
|
@ -54,36 +51,46 @@ export function SidebarPart(props: {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only track active link when searching (to show all sections)
|
// Only track active link when searching (to show all sections)
|
||||||
if (props.searchQuery.trim()) {
|
if (props.searchQuery.trim()) {
|
||||||
|
let ticking = false;
|
||||||
const recheck = () => {
|
const recheck = () => {
|
||||||
const windowHeight =
|
if (!ticking) {
|
||||||
window.innerHeight || document.documentElement.clientHeight;
|
window.requestAnimationFrame(() => {
|
||||||
const centerTarget = windowHeight / 4;
|
const windowHeight =
|
||||||
|
window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
const centerTarget = windowHeight / 4;
|
||||||
|
|
||||||
const viewList = settingLinks
|
const viewList = settingLinks
|
||||||
.map((link) => {
|
.map((link) => {
|
||||||
const el = document.getElementById(link.id);
|
const el = document.getElementById(link.id);
|
||||||
if (!el) return { distance: Infinity, link: link.id };
|
if (!el) return { distance: Infinity, link: link.id };
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const distanceTop = Math.abs(centerTarget - rect.top);
|
const distanceTop = Math.abs(centerTarget - rect.top);
|
||||||
const distanceBottom = Math.abs(centerTarget - rect.bottom);
|
const distanceBottom = Math.abs(centerTarget - rect.bottom);
|
||||||
const distance = Math.min(distanceBottom, distanceTop);
|
const distance = Math.min(distanceBottom, distanceTop);
|
||||||
return { distance, link: link.id };
|
return { distance, link: link.id };
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.distance - b.distance);
|
.sort((a, b) => a.distance - b.distance);
|
||||||
|
|
||||||
// Check if user has scrolled past the bottom of the page
|
// Check if user has scrolled past the bottom of the page
|
||||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
if (
|
||||||
setActiveLink(settingLinks[settingLinks.length - 1].id);
|
window.innerHeight + window.scrollY >=
|
||||||
return;
|
document.body.offsetHeight
|
||||||
|
) {
|
||||||
|
setActiveLink(settingLinks[settingLinks.length - 1].id);
|
||||||
|
} else {
|
||||||
|
// shortest distance to the part of the screen we want is the active link
|
||||||
|
setActiveLink(viewList[0]?.link ?? "");
|
||||||
|
}
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
}
|
}
|
||||||
// shortest distance to the part of the screen we want is the active link
|
|
||||||
setActiveLink(viewList[0]?.link ?? "");
|
|
||||||
};
|
};
|
||||||
document.addEventListener("scroll", recheck);
|
window.addEventListener("scroll", recheck, { passive: true });
|
||||||
recheck();
|
recheck();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("scroll", recheck);
|
window.removeEventListener("scroll", recheck);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// When not searching, set active link to selected category
|
// When not searching, set active link to selected category
|
||||||
|
|
@ -101,12 +108,18 @@ export function SidebarPart(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-settings-sidebar-type-inactive sidebar-boundary">
|
<div className="text-settings-sidebar-type-inactive sidebar-boundary">
|
||||||
<Sticky
|
<div
|
||||||
topOffset={-6 * rem}
|
className={
|
||||||
stickyClassName="pt-[6rem]"
|
isMobile ? "" : "sticky top-32 self-start will-change-transform"
|
||||||
disabled={isMobile}
|
}
|
||||||
hideOnBoundaryHit={false}
|
style={
|
||||||
boundaryElement=".sidebar-boundary"
|
isMobile
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
// Use CSS transform for better performance
|
||||||
|
transform: "translateZ(0)",
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SidebarSection title={t("global.pages.settings")}>
|
<SidebarSection title={t("global.pages.settings")}>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
|
|
@ -137,7 +150,7 @@ export function SidebarPart(props: {
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<AppInfoPart />
|
<AppInfoPart />
|
||||||
</div>
|
</div>
|
||||||
</Sticky>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue