pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix sticky and scrolling of SidebarPart

Pas ba7c079b 14a46c4b

+62 -32
+17
src/pages/Settings.tsx
··· 170 170 export function SettingsPage() { 171 171 const [searchQuery, setSearchQuery] = useState(""); 172 172 const [selectedCategory, setSelectedCategory] = useState<string | null>(null); 173 + const prevCategoryRef = useRef<string | null>(null); 173 174 174 175 useEffect(() => { 175 176 const hash = window.location.hash; ··· 193 194 } 194 195 // eslint-disable-next-line react-hooks/exhaustive-deps 195 196 }, []); 197 + 198 + // Scroll to top when category changes (but not on initial load or when searching) 199 + useEffect(() => { 200 + if ( 201 + prevCategoryRef.current !== null && 202 + prevCategoryRef.current !== selectedCategory && 203 + !searchQuery.trim() 204 + ) { 205 + // Only scroll to top if we're actually switching categories (not initial load) 206 + // Use requestAnimationFrame to ensure DOM has updated 207 + requestAnimationFrame(() => { 208 + window.scrollTo({ top: 0, behavior: "smooth" }); 209 + }); 210 + } 211 + prevCategoryRef.current = selectedCategory; 212 + }, [selectedCategory, searchQuery]); 196 213 197 214 const { t } = useTranslation(); 198 215 const activeTheme = useThemeStore((s) => s.theme);
+45 -32
src/pages/parts/settings/SidebarPart.tsx
··· 1 1 import { useCallback, useEffect, useMemo, useState } from "react"; 2 2 import { useTranslation } from "react-i18next"; 3 - import Sticky from "react-sticky-el"; 4 3 5 4 import { Icons } from "@/components/Icon"; 6 5 import { SidebarLink, SidebarSection } from "@/components/layout/Sidebar"; ··· 8 7 import { useIsMobile } from "@/hooks/useIsMobile"; 9 8 10 9 import { AppInfoPart } from "./AppInfoPart"; 11 - 12 - const rem = 16; 13 10 14 11 export function SidebarPart(props: { 15 12 selectedCategory: string | null; ··· 54 51 useEffect(() => { 55 52 // Only track active link when searching (to show all sections) 56 53 if (props.searchQuery.trim()) { 54 + let ticking = false; 57 55 const recheck = () => { 58 - const windowHeight = 59 - window.innerHeight || document.documentElement.clientHeight; 60 - const centerTarget = windowHeight / 4; 56 + if (!ticking) { 57 + window.requestAnimationFrame(() => { 58 + const windowHeight = 59 + window.innerHeight || document.documentElement.clientHeight; 60 + const centerTarget = windowHeight / 4; 61 61 62 - const viewList = settingLinks 63 - .map((link) => { 64 - const el = document.getElementById(link.id); 65 - if (!el) return { distance: Infinity, link: link.id }; 66 - const rect = el.getBoundingClientRect(); 67 - const distanceTop = Math.abs(centerTarget - rect.top); 68 - const distanceBottom = Math.abs(centerTarget - rect.bottom); 69 - const distance = Math.min(distanceBottom, distanceTop); 70 - return { distance, link: link.id }; 71 - }) 72 - .sort((a, b) => a.distance - b.distance); 62 + const viewList = settingLinks 63 + .map((link) => { 64 + const el = document.getElementById(link.id); 65 + if (!el) return { distance: Infinity, link: link.id }; 66 + const rect = el.getBoundingClientRect(); 67 + const distanceTop = Math.abs(centerTarget - rect.top); 68 + const distanceBottom = Math.abs(centerTarget - rect.bottom); 69 + const distance = Math.min(distanceBottom, distanceTop); 70 + return { distance, link: link.id }; 71 + }) 72 + .sort((a, b) => a.distance - b.distance); 73 73 74 - // Check if user has scrolled past the bottom of the page 75 - if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { 76 - setActiveLink(settingLinks[settingLinks.length - 1].id); 77 - return; 74 + // Check if user has scrolled past the bottom of the page 75 + if ( 76 + window.innerHeight + window.scrollY >= 77 + document.body.offsetHeight 78 + ) { 79 + setActiveLink(settingLinks[settingLinks.length - 1].id); 80 + } else { 81 + // shortest distance to the part of the screen we want is the active link 82 + setActiveLink(viewList[0]?.link ?? ""); 83 + } 84 + ticking = false; 85 + }); 86 + ticking = true; 78 87 } 79 - // shortest distance to the part of the screen we want is the active link 80 - setActiveLink(viewList[0]?.link ?? ""); 81 88 }; 82 - document.addEventListener("scroll", recheck); 89 + window.addEventListener("scroll", recheck, { passive: true }); 83 90 recheck(); 84 91 85 92 return () => { 86 - document.removeEventListener("scroll", recheck); 93 + window.removeEventListener("scroll", recheck); 87 94 }; 88 95 } 89 96 // When not searching, set active link to selected category ··· 101 108 102 109 return ( 103 110 <div className="text-settings-sidebar-type-inactive sidebar-boundary"> 104 - <Sticky 105 - topOffset={-6 * rem} 106 - stickyClassName="pt-[6rem]" 107 - disabled={isMobile} 108 - hideOnBoundaryHit={false} 109 - boundaryElement=".sidebar-boundary" 111 + <div 112 + className={ 113 + isMobile ? "" : "sticky top-32 self-start will-change-transform" 114 + } 115 + style={ 116 + isMobile 117 + ? undefined 118 + : { 119 + // Use CSS transform for better performance 120 + transform: "translateZ(0)", 121 + } 122 + } 110 123 > 111 124 <SidebarSection title={t("global.pages.settings")}> 112 125 <SidebarLink ··· 137 150 <div className="hidden lg:block"> 138 151 <AppInfoPart /> 139 152 </div> 140 - </Sticky> 153 + </div> 141 154 </div> 142 155 ); 143 156 }