Skip to content

Commit

Permalink
FeaturePanel: Scroll to top after click on the link (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvaclavik authored Sep 25, 2024
1 parent 8227c24 commit 117ef06
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 36 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@types/autosuggest-highlight": "^3.2.3",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.7",
"@types/react-custom-scrollbars": "^4.0.13",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/typescript-estree": "^8.2.0",
"babel-eslint": "^10.1.0",
Expand Down
33 changes: 29 additions & 4 deletions src/components/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import Cookies from 'js-cookie';

import nextCookies from 'next-cookies';
Expand Down Expand Up @@ -38,6 +38,7 @@ import {
getClimbingAreas,
} from '../../services/climbing-areas/getClimbingAreas';
import { DirectionsBox } from '../Directions/DirectionsBox';
import { Scrollbars } from 'react-custom-scrollbars';

const usePersistMapView = () => {
const { view } = useMapStateContext();
Expand Down Expand Up @@ -86,11 +87,32 @@ type IndexWithProvidersProps = {
climbingAreas: Array<ClimbingArea>;
};

const useScrollToTopWhenRouteChanged = () => {
const scrollRef = useRef<Scrollbars>(null);
const router = useRouter();

useEffect(() => {
const routeChangeComplete = () => {
if (scrollRef?.current) {
scrollRef.current.scrollToTop();
}
};
router.events.on('routeChangeComplete', routeChangeComplete);

return () => {
router.events.off('routeChangeComplete', routeChangeComplete);
};
}, [router.events]);

return scrollRef;
};

const IndexWithProviders = ({ climbingAreas }: IndexWithProvidersProps) => {
const isMobileMode = useMobileMode();
const { feature, featureShown } = useFeatureContext();
const router = useRouter();
const isMounted = useIsClient();
const scrollRef = useScrollToTopWhenRouteChanged();
useUpdateViewFromFeature();
usePersistMapView();
useUpdateViewFromHash();
Expand All @@ -110,9 +132,12 @@ const IndexWithProviders = ({ climbingAreas }: IndexWithProvidersProps) => {
<Loading />
{!directions && <SearchBox />}
{directions && <DirectionsBox />}
{featureShown && !isMobileMode && isMounted && <FeaturePanelOnSide />}

{featureShown && isMobileMode && <FeaturePanelInDrawer />}
{featureShown && !isMobileMode && isMounted && (
<FeaturePanelOnSide scrollRef={scrollRef} />
)}
{featureShown && isMobileMode && (
<FeaturePanelInDrawer scrollRef={scrollRef} />
)}
{isClimbingDialogShown && (
<ClimbingContextProvider feature={feature}>
<ClimbingCragDialog
Expand Down
20 changes: 14 additions & 6 deletions src/components/FeaturePanel/Climbing/utils/useScrollShadow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { CSSProperties, useEffect, useRef, useState } from 'react';
import React, {
CSSProperties,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import styled from '@emotion/styled';
import { css } from '@emotion/react';
import { convertHexToRgba } from '../../../utils/colorUtils';
Expand Down Expand Up @@ -84,14 +90,16 @@ const GradientRight = styled.div<GradientProps>`
);
`;

export const useScrollShadow = (deps = []) => {
const scrollElementRef = useRef<HTMLDivElement>(null);
export const useScrollShadow = (deps = [], ref = undefined) => {
const tempRef = useRef<HTMLDivElement>(null);
const scrollElementRef = ref || tempRef;

const [isScrolledToTop, setIsScrolledToTop] = useState(true);
const [isScrolledToBottom, setIsScrolledToBottom] = useState(true);
const [isScrolledToLeft, setIsScrolledToLeft] = useState(true);
const [isScrolledToRight, setIsScrolledToRight] = useState(true);

const setShadows = () => {
const setShadows = useCallback(() => {
if (scrollElementRef?.current) {
const {
scrollTop,
Expand All @@ -109,7 +117,7 @@ export const useScrollShadow = (deps = []) => {
setIsScrolledToLeft(scrollLeft <= 0);
setIsScrolledToRight(Math.ceil(scrollLeft + clientWidth) >= scrollWidth);
}
};
}, [scrollElementRef]);

useEffect(() => {
setShadows();
Expand All @@ -124,7 +132,7 @@ export const useScrollShadow = (deps = []) => {
window.removeEventListener('resize', handleShadows);
window.removeEventListener('orientationchange', handleShadows);
};
}, []);
}, [setShadows]);

const onScroll = () => {
setShadows();
Expand Down
22 changes: 15 additions & 7 deletions src/components/FeaturePanel/CragsInArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Router from 'next/router';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { useFeatureContext } from '../utils/FeatureContext';
import { getOsmappLink, getUrlOsmId } from '../../services/helpers';
import { Feature, isInstant, OsmId } from '../../services/types';
import { Feature, isInstant } from '../../services/types';
import { useMobileMode } from '../helpers';
import { getLabel } from '../../helpers/featureLabel';

Expand All @@ -19,12 +19,14 @@ const ArrowIcon = styled(ArrowForwardIosIcon)`
opacity: 0.2;
margin-left: 12px;
`;

const HeadingRow = styled.div`
display: flex;
flex-direction: row;
align-items: center;
padding: 0 12px;
`;

const Container = styled.div`
overflow: auto;
flex-direction: column;
Expand All @@ -41,14 +43,17 @@ const Container = styled.div`
}
}
`;

const CragList = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;

const StyledLink = styled(Link)`
text-decoration: none !important;
`;

const Content = styled.div`
flex: 1;
`;
Expand All @@ -59,14 +64,17 @@ const CragName = styled.div`
font-size: 20px;
color: ${({ theme }) => theme.palette.primary.main};
`;

const Attributes = styled.div`
display: flex;
gap: 8px;
`;

const NumberOfRoutes = styled.div`
font-size: 13px;
color: ${({ theme }) => theme.palette.secondary.main};
`;

const Header = ({
imagesCount,
label,
Expand Down Expand Up @@ -104,11 +112,6 @@ const Gallery = ({ images }) => {
);
};

const getOnClickWithHash = (apiId: OsmId) => (e) => {
e.preventDefault();
Router.push(`/${getUrlOsmId(apiId)}${window.location.hash}`);
};

const CragItem = ({ feature }: { feature: Feature }) => {
const mobileMode = useMobileMode();
const { setPreview } = useFeatureContext();
Expand All @@ -120,11 +123,16 @@ const CragItem = ({ feature }: { feature: Feature }) => {
image: getInstantImage(def),
})) ?? [];

const getOnClickWithHash = (e) => {
e.preventDefault();
Router.push(`/${getUrlOsmId(feature.osmMeta)}${window.location.hash}`);
};

return (
<StyledLink
href={`/${getUrlOsmId(feature.osmMeta)}`}
locale={intl.lang}
onClick={getOnClickWithHash(feature.osmMeta)}
onClick={getOnClickWithHash}
onMouseEnter={mobileMode ? undefined : handleHover}
onMouseLeave={() => setPreview(null)}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/FeaturePanel/FeaturePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { FeatureHeading } from './FeatureHeading';
import { useToggleState } from '../helpers';
Expand Down
12 changes: 10 additions & 2 deletions src/components/FeaturePanel/FeaturePanelInDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { LegacyRef } from 'react';
import { FeaturePanel } from './FeaturePanel';
import { Drawer } from '../utils/Drawer';
import {
Expand All @@ -8,10 +8,17 @@ import {
} from '../utils/MobilePageDrawer';
import { useScreensize } from '../../helpers/hooks';
import { useFeatureContext } from '../utils/FeatureContext';
import { Scrollbars } from 'react-custom-scrollbars';

const DRAWER_CLASSNAME = 'featurePanelInDrawer';

export const FeaturePanelInDrawer = () => {
type FeaturePanelInDrawerProps = {
scrollRef: LegacyRef<Scrollbars>;
};

export const FeaturePanelInDrawer = ({
scrollRef,
}: FeaturePanelInDrawerProps) => {
const { feature } = useFeatureContext();
const [collapsedHeight, setCollapsedHeight] = React.useState<number>(
DRAWER_PREVIEW_HEIGHT,
Expand All @@ -35,6 +42,7 @@ export const FeaturePanelInDrawer = () => {
topOffset={DRAWER_TOP_OFFSET}
className={DRAWER_CLASSNAME}
collapsedHeight={collapsedHeight}
scrollRef={scrollRef}
>
<FeaturePanel headingRef={headingRef} />
</Drawer>
Expand Down
17 changes: 11 additions & 6 deletions src/components/FeaturePanel/FeaturePanelOnSide.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React from 'react';
import React, { LegacyRef } from 'react';

import { PanelScrollbars, PanelWrapper } from '../utils/PanelHelpers';
import { FeaturePanel } from './FeaturePanel';
import { Scrollbars } from 'react-custom-scrollbars';

export const FeaturePanelOnSide = () => (
<>
type FeaturePanelOnSideProps = {
scrollRef: LegacyRef<Scrollbars>;
};

export const FeaturePanelOnSide = ({ scrollRef }: FeaturePanelOnSideProps) => {
return (
<PanelWrapper>
<PanelScrollbars>
<PanelScrollbars scrollRef={scrollRef}>
<FeaturePanel />
</PanelScrollbars>
</PanelWrapper>
</>
);
);
};
16 changes: 9 additions & 7 deletions src/components/utils/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable react/jsx-props-no-spreading */
import styled from '@emotion/styled';
import { Global, css } from '@emotion/react';
import React, { useState } from 'react';
import React, { LegacyRef, useState } from 'react';
import { SwipeableDrawer } from '@mui/material';
import { Puller } from '../FeaturePanel/helpers/Puller';
import { Scrollbars } from 'react-custom-scrollbars';

type SettingsProps = {
$collapsedHeight: number;
Expand Down Expand Up @@ -35,11 +36,6 @@ const Container = styled.div<SettingsProps>`
overflow: hidden;
`;

const ListContainer = styled.div`
height: 100%;
overflow: auto;
`;

type Props = {
onTransitionEnd?: (
e: React.TransitionEvent<HTMLDivElement>,
Expand All @@ -50,6 +46,7 @@ type Props = {
collapsedHeight: number;
className: string;
defaultOpen?: boolean;
scrollRef?: LegacyRef<Scrollbars>;
};

export const Drawer = ({
Expand All @@ -59,7 +56,10 @@ export const Drawer = ({
className,
onTransitionEnd,
defaultOpen = false,
scrollRef,
}: Props) => {
const newRef = React.useRef<Scrollbars>(null);
const ref = scrollRef || newRef;
const [open, setOpen] = useState(defaultOpen);

const handleOnOpen = () => setOpen(true);
Expand All @@ -85,7 +85,9 @@ export const Drawer = ({
>
<Container $collapsedHeight={collapsedHeight} $topOffset={topOffset}>
<Puller setOpen={setOpen} open={open} />
<ListContainer>{children}</ListContainer>
<Scrollbars universal autoHide style={{ height: '100%' }} ref={ref}>
{children}
</Scrollbars>
</Container>
</SwipeableDrawer>
</>
Expand Down
17 changes: 14 additions & 3 deletions src/components/utils/PanelHelpers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { LegacyRef, useRef } from 'react';
import styled from '@emotion/styled';
import { Scrollbars } from 'react-custom-scrollbars';
import { useTheme } from '@mui/material';
Expand Down Expand Up @@ -36,17 +36,28 @@ export const PanelWrapper = styled.div`
}
`;

export const PanelScrollbars = ({ children }) => {
type PanelScrollbarsProps = {
children: React.ReactNode;
scrollRef?: LegacyRef<Scrollbars>;
};

export const PanelScrollbars = ({
children,
scrollRef,
}: PanelScrollbarsProps) => {
const newRef = useRef<Scrollbars>(null);
const ref = scrollRef || newRef;
const theme = useTheme();

// @TODO refresh on panel height first update

const {
scrollElementRef,
onScroll,
ShadowContainer,
ShadowTop,
ShadowBottom,
} = useScrollShadow();
} = useScrollShadow(undefined, ref);

return (
<ShadowContainer>
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,13 @@
resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.3.tgz#85f1d1d17569b28b8db45e16e996407a56b0ab04"
integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==

"@types/react-custom-scrollbars@^4.0.13":
version "4.0.13"
resolved "https://registry.yarnpkg.com/@types/react-custom-scrollbars/-/react-custom-scrollbars-4.0.13.tgz#38f11b0922503a433374b005c7e32b5279d739d8"
integrity sha512-t+15reWgAE1jXlrhaZoxjuH/SQf+EG0rzAzSCzTIkSiP5CDT7KhoExNPwIa6uUxtPkjc3gdW/ry7GetLEwCfGA==
dependencies:
"@types/react" "*"

"@types/react-dom@^18.3.0":
version "18.3.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0"
Expand Down

0 comments on commit 117ef06

Please sign in to comment.