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
This commit is contained in:
schroda
2024-07-29 17:22:49 +02:00
parent b17fcd30af
commit b80622cb17
11 changed files with 54 additions and 39 deletions

View File

@@ -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`,

View File

@@ -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<HTMLDivElement, GridTypeMap['props']>(({ children, ...props }, ref) => (
<Grid {...props} ref={ref} container sx={{ paddingLeft: '5px', paddingRight: '13px' }}>
@@ -253,12 +253,15 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
}) => {
const { t } = useTranslation();
const gridRef = useRef<HTMLDivElement>(null);
const { navBarWidth } = useNavBarContext();
const [actualDimensions, setDimensions] = useState(document.documentElement.offsetWidth);
const dimensions = useDebounce(actualDimensions, 500);
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
const gridRef = useRef<HTMLDivElement>(null);
const gridWrapperRef = useRef<HTMLDivElement>(null);
const [dimensions, setDimensions] = useState(
gridWrapperRef.current?.offsetWidth ?? Math.max(0, document.documentElement.offsetWidth - navBarWidth),
);
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
const GridItemContainer = useMemo(
() => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout),
[dimensions, gridItemWidth, gridLayout],
@@ -300,14 +303,14 @@ export const MangaGrid: React.FC<IMangaGridProps> = ({
const gridWidth = gridWrapperRef.current?.offsetWidth;
if (!gridWidth) {
return document.documentElement.offsetWidth;
return document.documentElement.offsetWidth - navBarWidth;
}
return gridWidth;
};
setDimensions(getDimensions());
}, []),
}, [navBarWidth]),
);
useResizeObserver(

View File

@@ -34,6 +34,9 @@ type ContextType = {
navBarWidth: number;
setNavBarWidth: React.Dispatch<React.SetStateAction<number>>;
readerNavBarWidth: number;
setReaderNavBarWidth: React.Dispatch<React.SetStateAction<number>>;
bottomBarHeight: number;
setBottomBarHeight: React.Dispatch<React.SetStateAction<number>>;
};
@@ -52,6 +55,8 @@ export const NavBarContext = React.createContext<ContextType>({
setIsCollapsed: (): void => {},
navBarWidth: 0,
setNavBarWidth: (): void => {},
readerNavBarWidth: 0,
setReaderNavBarWidth: (): void => {},
bottomBarHeight: 0,
setBottomBarHeight: (): void => {},
});

View File

@@ -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<NavbarItem> = [
];
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 navBarItems={visibleNavBarItems} />,
[NavBarComponent, visibleNavBarItems],

View File

@@ -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,
],

View File

@@ -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,

View File

@@ -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 (
<Drawer variant="permanent" sx={{ width: navBarWidth }}>

View File

@@ -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,

View File

@@ -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<HTMLDivElement | null>,
) => {
const { appBarHeight } = useNavBarContext();
const tabsMenuRef = useRef<HTMLDivElement | null>(null);
useImperativeHandle(ref, () => tabsMenuRef.current!);
const [width, setWidth] = useState<number>();
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 (
<StyledTabsMenu
{...props}
sx={{ ...props.sx, top: appBarHeight }}
ref={tabsMenuRef}
sx={{ ...sx, top: appBarHeight }}
ref={ref}
indicatorColor="primary"
textColor="primary"
centered={!scrollableTabs}
variant={scrollableTabs ? 'scrollable' : 'fullWidth'}
variant="scrollable"
scrollButtons
allowScrollButtonsMobile
{...props}
>
{children}
</StyledTabsMenu>

View File

@@ -37,7 +37,13 @@ export function Browse() {
return (
<TabsWrapper>
<TabsMenu ref={tabsMenuRef} value={tabNum} tabsCount={2} onChange={(e, newTab) => setTabNum(newTab)}>
<TabsMenu
ref={tabsMenuRef}
variant="fullWidth"
value={tabNum}
tabsCount={2}
onChange={(e, newTab) => setTabNum(newTab)}
>
<Tab sx={{ textTransform: 'none' }} label={t('source.title_one')} />
<Tab sx={{ textTransform: 'none' }} label={t('extension.title_other')} />
<Tab sx={{ textTransform: 'none' }} label={t('migrate.title')} />

View File

@@ -194,7 +194,7 @@ export function Reader() {
const isLastPage = curPage === chapter.pageCount - 1;
const curPageDebounced = useDebounce(curPage, isLastPage ? 0 : 1000);
const [pageToScrollTo, setPageToScrollTo] = useState<number | undefined>(undefined);
const { setOverride, setTitle, navBarWidth: currentNavBarWidth } = useContext(NavBarContext);
const { setOverride, setTitle, readerNavBarWidth: currentNavBarWidth } = useContext(NavBarContext);
const [retrievingNextChapter, setRetrievingNextChapter] = useState(false);
const {
data: mangaChaptersData,