From b80622cb1745f7c0cd576b686ea3e43f7ae08c40 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:22:49 +0200 Subject: [PATCH] Prevent grid items from jumping after closing reader Due to the reader and the "default" nav bar updating the same navbar width in the context, the grid item width calculation resulted in different grid item widths, which caused the items to "jump" after closing the reader --- src/App.tsx | 3 ++- src/components/MangaGrid.tsx | 17 +++++++------ src/components/context/NavbarContext.tsx | 5 ++++ src/components/navbar/DefaultNavBar.tsx | 13 ++++++++-- .../navbar/NavBarContextProvider.tsx | 5 ++++ src/components/navbar/ReaderNavBar.tsx | 8 +++--- .../navbar/navigation/DesktopSideBar.tsx | 3 +-- .../navbar/navigation/MobileBottomBar.tsx | 4 +-- src/components/tabs/TabsMenu.tsx | 25 +++++-------------- src/screens/Browse.tsx | 8 +++++- src/screens/Reader.tsx | 2 +- 11 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 02c24d5c..bda74e06 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -86,8 +86,9 @@ const MainApp = () => { id="appMainContainer" component="main" sx={{ - flexGrow: 1, minHeight: `calc(100vh - ${bottomBarHeight}px)`, + width: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`, + minWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`, maxWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`, position: 'relative', mb: `${bottomBarHeight}px`, diff --git a/src/components/MangaGrid.tsx b/src/components/MangaGrid.tsx index 7d3baf31..c7f711c0 100644 --- a/src/components/MangaGrid.tsx +++ b/src/components/MangaGrid.tsx @@ -32,7 +32,7 @@ import { AppStorage } from '@/util/AppStorage.ts'; import { MangaCardProps } from '@/components/manga/MangaCard.types.tsx'; import { MangaType } from '@/lib/graphql/generated/graphql.ts'; import { useResizeObserver } from '@/util/useResizeObserver.tsx'; -import { useDebounce } from '@/util/useDebounce.ts'; +import { useNavBarContext } from '@/components/context/NavbarContext.tsx'; const GridContainer = React.forwardRef(({ children, ...props }, ref) => ( @@ -253,12 +253,15 @@ export const MangaGrid: React.FC = ({ }) => { const { t } = useTranslation(); - const gridRef = useRef(null); + const { navBarWidth } = useNavBarContext(); - const [actualDimensions, setDimensions] = useState(document.documentElement.offsetWidth); - const dimensions = useDebounce(actualDimensions, 500); - const [gridItemWidth] = useLocalStorage('ItemWidth', 300); + const gridRef = useRef(null); const gridWrapperRef = useRef(null); + + const [dimensions, setDimensions] = useState( + gridWrapperRef.current?.offsetWidth ?? Math.max(0, document.documentElement.offsetWidth - navBarWidth), + ); + const [gridItemWidth] = useLocalStorage('ItemWidth', 300); const GridItemContainer = useMemo( () => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout), [dimensions, gridItemWidth, gridLayout], @@ -300,14 +303,14 @@ export const MangaGrid: React.FC = ({ const gridWidth = gridWrapperRef.current?.offsetWidth; if (!gridWidth) { - return document.documentElement.offsetWidth; + return document.documentElement.offsetWidth - navBarWidth; } return gridWidth; }; setDimensions(getDimensions()); - }, []), + }, [navBarWidth]), ); useResizeObserver( diff --git a/src/components/context/NavbarContext.tsx b/src/components/context/NavbarContext.tsx index a2817179..955a688c 100644 --- a/src/components/context/NavbarContext.tsx +++ b/src/components/context/NavbarContext.tsx @@ -34,6 +34,9 @@ type ContextType = { navBarWidth: number; setNavBarWidth: React.Dispatch>; + readerNavBarWidth: number; + setReaderNavBarWidth: React.Dispatch>; + bottomBarHeight: number; setBottomBarHeight: React.Dispatch>; }; @@ -52,6 +55,8 @@ export const NavBarContext = React.createContext({ setIsCollapsed: (): void => {}, navBarWidth: 0, setNavBarWidth: (): void => {}, + readerNavBarWidth: 0, + setReaderNavBarWidth: (): void => {}, bottomBarHeight: 0, setBottomBarHeight: (): void => {}, }); diff --git a/src/components/navbar/DefaultNavBar.tsx b/src/components/navbar/DefaultNavBar.tsx index bf752343..5b5221fb 100644 --- a/src/components/navbar/DefaultNavBar.tsx +++ b/src/components/navbar/DefaultNavBar.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useCallback, useContext, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; @@ -73,7 +73,7 @@ const navbarItems: Array = [ ]; export function DefaultNavBar() { - const { title, action, override, isCollapsed, setIsCollapsed, setAppBarHeight, navBarWidth } = + const { title, action, override, isCollapsed, setIsCollapsed, setAppBarHeight, navBarWidth, setNavBarWidth } = useContext(NavBarContext); const { pathname } = useLocation(); @@ -98,6 +98,15 @@ export function DefaultNavBar() { ); const NavBarComponent = useMemo(() => (isMobileWidth ? MobileBottomBar : DesktopSideBar), [isMobileWidth]); + useLayoutEffect(() => { + if (!isMobileWidth) { + // do not reset navbar width to prevent grid from jumping due to the changing grid item width + return; + } + + setNavBarWidth(0); + }, [isMobileWidth]); + const navBar = useMemo( () => , [NavBarComponent, visibleNavBarItems], diff --git a/src/components/navbar/NavBarContextProvider.tsx b/src/components/navbar/NavBarContextProvider.tsx index e2692eea..16341a31 100644 --- a/src/components/navbar/NavBarContextProvider.tsx +++ b/src/components/navbar/NavBarContextProvider.tsx @@ -26,6 +26,7 @@ export function NavBarContextProvider({ children }: IProps) { }); const [isCollapsed, setIsCollapsed] = useLocalStorage('NavBar::isCollapsed', false); const [navBarWidth, setNavBarWidth] = useState(0); + const [readerNavBarWidth, setReaderNavBarWidth] = useState(0); const [bottomBarHeight, setBottomBarHeight] = useState(0); const history = useHistory(); @@ -53,6 +54,8 @@ export function NavBarContextProvider({ children }: IProps) { setIsCollapsed, navBarWidth, setNavBarWidth, + readerNavBarWidth, + setReaderNavBarWidth, bottomBarHeight, setBottomBarHeight, }), @@ -70,6 +73,8 @@ export function NavBarContextProvider({ children }: IProps) { setIsCollapsed, navBarWidth, setNavBarWidth, + readerNavBarWidth, + setReaderNavBarWidth, bottomBarHeight, setBottomBarHeight, ], diff --git a/src/components/navbar/ReaderNavBar.tsx b/src/components/navbar/ReaderNavBar.tsx index 347e0b6c..8c442f26 100644 --- a/src/components/navbar/ReaderNavBar.tsx +++ b/src/components/navbar/ReaderNavBar.tsx @@ -12,7 +12,7 @@ import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft'; import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import Typography from '@mui/material/Typography'; import { useLocation, useNavigate } from 'react-router-dom'; import Slide from '@mui/material/Slide'; @@ -138,7 +138,7 @@ interface IProps { export function ReaderNavBar(props: IProps) { const { t } = useTranslation(); - const { setNavBarWidth } = useNavBarContext(); + const { setReaderNavBarWidth } = useNavBarContext(); const navigate = useNavigate(); const location = useLocation<{ @@ -155,10 +155,10 @@ export function ReaderNavBar(props: IProps) { return; } - setNavBarWidth(navBarRef.current.offsetWidth); + setReaderNavBarWidth(navBarRef.current.offsetWidth); }, [navBarRef.current]), ); - useEffect(() => () => setNavBarWidth(0), [navBarRef]); + useLayoutEffect(() => () => setReaderNavBarWidth(0), [navBarRef]); const { settings, diff --git a/src/components/navbar/navigation/DesktopSideBar.tsx b/src/components/navbar/navigation/DesktopSideBar.tsx index fc0bcb6e..de188ff3 100644 --- a/src/components/navbar/navigation/DesktopSideBar.tsx +++ b/src/components/navbar/navigation/DesktopSideBar.tsx @@ -17,7 +17,7 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import Divider from '@mui/material/Divider'; import { styled, useTheme } from '@mui/material/styles'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; import Box from '@mui/material/Box'; import ListItem from '@mui/material/ListItem'; import Tooltip from '@mui/material/Tooltip'; @@ -92,7 +92,6 @@ export const DesktopSideBar = ({ navBarItems }: { navBarItems: NavbarItem[] }) = ref, useCallback(() => setNavBarWidth(ref.current?.clientWidth ?? 0), [ref.current]), ); - useEffect(() => () => setNavBarWidth(0), []); return ( diff --git a/src/components/navbar/navigation/MobileBottomBar.tsx b/src/components/navbar/navigation/MobileBottomBar.tsx index 4c9e388a..737e0de6 100644 --- a/src/components/navbar/navigation/MobileBottomBar.tsx +++ b/src/components/navbar/navigation/MobileBottomBar.tsx @@ -10,7 +10,7 @@ import BottomNavigation from '@mui/material/BottomNavigation'; import BottomNavigationAction from '@mui/material/BottomNavigationAction'; import { useTranslation } from 'react-i18next'; import Paper from '@mui/material/Paper'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { NavbarItem } from '@/typings.ts'; import { useResizeObserver } from '@/util/useResizeObserver.tsx'; @@ -27,7 +27,7 @@ export const MobileBottomBar = ({ navBarItems }: { navBarItems: NavbarItem[] }) ref, useCallback(() => setBottomBarHeight(ref.current?.clientHeight ?? 0), [ref.current]), ); - useEffect(() => () => setBottomBarHeight(0), []); + useLayoutEffect(() => () => setBottomBarHeight(0), []); const [selectedNavBarItem, setSelectedNavBarItem] = useState( navBarItems.find((navBarItem) => navBarItem.path === location.pathname)?.path, diff --git a/src/components/tabs/TabsMenu.tsx b/src/components/tabs/TabsMenu.tsx index 86e51cf7..63c8eddb 100644 --- a/src/components/tabs/TabsMenu.tsx +++ b/src/components/tabs/TabsMenu.tsx @@ -8,8 +8,7 @@ import Tabs, { TabsProps } from '@mui/material/Tabs'; import { styled } from '@mui/material/styles'; -import { ForwardedRef, forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'; -import { useResizeObserver } from '@/util/useResizeObserver.tsx'; +import { ForwardedRef, forwardRef } from 'react'; import { useNavBarContext } from '@/components/context/NavbarContext.tsx'; const StyledTabsMenu = styled(Tabs)(({ theme }) => ({ @@ -27,33 +26,21 @@ const StyledTabsMenu = styled(Tabs)(({ theme }) => ({ export const TabsMenu = forwardRef( ( - { children, tabsCount, ...props }: TabsProps & { tabsCount: number }, + { children, tabsCount, sx, ...props }: TabsProps & { tabsCount: number }, ref: ForwardedRef, ) => { const { appBarHeight } = useNavBarContext(); - const tabsMenuRef = useRef(null); - useImperativeHandle(ref, () => tabsMenuRef.current!); - const [width, setWidth] = useState(); - useResizeObserver( - tabsMenuRef, - useCallback(() => setWidth(tabsMenuRef.current?.clientWidth), [tabsMenuRef.current]), - ); - - // Visual Hack: 160px is min-width for viewport width of >600 - const scrollableTabs = !width ? false : width < tabsCount * 160; - return ( {children} diff --git a/src/screens/Browse.tsx b/src/screens/Browse.tsx index 8a1b6a64..7d458c73 100644 --- a/src/screens/Browse.tsx +++ b/src/screens/Browse.tsx @@ -37,7 +37,13 @@ export function Browse() { return ( - setTabNum(newTab)}> + setTabNum(newTab)} + > diff --git a/src/screens/Reader.tsx b/src/screens/Reader.tsx index a204eb42..233b2746 100644 --- a/src/screens/Reader.tsx +++ b/src/screens/Reader.tsx @@ -194,7 +194,7 @@ export function Reader() { const isLastPage = curPage === chapter.pageCount - 1; const curPageDebounced = useDebounce(curPage, isLastPage ? 0 : 1000); const [pageToScrollTo, setPageToScrollTo] = useState(undefined); - const { setOverride, setTitle, navBarWidth: currentNavBarWidth } = useContext(NavBarContext); + const { setOverride, setTitle, readerNavBarWidth: currentNavBarWidth } = useContext(NavBarContext); const [retrievingNextChapter, setRetrievingNextChapter] = useState(false); const { data: mangaChaptersData,