Commit 0f710c19 authored by tom's avatar tom

refactor scroll direction context

parent 7bc58a3f
......@@ -2,22 +2,27 @@ import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle';
import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null);
import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20;
type Directions = 'up' | 'down';
export default function useScrollDirection() {
interface Props {
children: React.ReactNode;
}
export function ScrollDirectionProvider({ children }: Props) {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0);
const [ scrollDirection, setDirection ] = React.useState<Directions>();
const [ scrollDirection, setDirection ] = React.useState<Directions | null>(null);
const handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight);
const scrollDiff = currentScrollPosition - prevScrollPosition.current;
if (window.pageYOffset === 0) {
setDirection(undefined);
setDirection(null);
} else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setDirection(scrollDiff < 0 ? 'up' : 'down');
}
......@@ -33,9 +38,21 @@ export default function useScrollDirection() {
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return scrollDirection;
return (
<ScrollDirectionContext.Provider value={ scrollDirection }>
{ children }
</ScrollDirectionContext.Provider>
);
}
export function useScrollDirection() {
const context = React.useContext(ScrollDirectionContext);
if (context === undefined) {
throw new Error('useScrollDirection must be used within a ScrollDirectionProvider');
}
return context;
}
......@@ -7,19 +7,16 @@ import React, { useState } from 'react';
import appConfig from 'configs/app/config';
import { AppContextProvider } from 'lib/appContext';
import { Chakra } from 'lib/Chakra';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import useConfigSentry from 'lib/hooks/useConfigSentry';
import type { ErrorType } from 'lib/hooks/useFetch';
import useScrollDirection from 'lib/hooks/useScrollDirection';
import { SocketProvider } from 'lib/socket/context';
import theme from 'theme';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import AppError from 'ui/shared/AppError/AppError';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
function MyApp({ Component, pageProps }: AppProps) {
useConfigSentry();
const directionContext = useScrollDirection();
const [ queryClient ] = useState(() => new QueryClient({
defaultOptions: {
queries: {
......@@ -62,11 +59,11 @@ function MyApp({ Component, pageProps }: AppProps) {
<ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ handleError }>
<AppContextProvider pageProps={ pageProps }>
<QueryClientProvider client={ queryClient }>
<ScrollDirectionContext.Provider value={ directionContext }>
<ScrollDirectionProvider>
<SocketProvider url={ `${ appConfig.api.socket }${ appConfig.api.basePath }/socket/v2` }>
<Component { ...pageProps }/>
</SocketProvider>
</ScrollDirectionContext.Provider>
</ScrollDirectionProvider>
<ReactQueryDevtools/>
</QueryClientProvider>
</AppContextProvider>
......
import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | undefined>(undefined);
export default ScrollDirectionContext;
......@@ -2,7 +2,7 @@ import { Flex, useColorModeValue, chakra } from '@chakra-ui/react';
import throttle from 'lodash/throttle';
import React from 'react';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import { useScrollDirection } from 'lib/contexts/scrollDirection';
type Props = {
children: React.ReactNode;
......@@ -15,6 +15,7 @@ const TOP_DOWN = 0;
const ActionBar = ({ children, className }: Props) => {
const [ isSticky, setIsSticky ] = React.useState(false);
const ref = React.useRef<HTMLDivElement>(null);
const scrollDirection = useScrollDirection();
const handleScroll = React.useCallback(() => {
if (
......@@ -41,28 +42,24 @@ const ActionBar = ({ children, className }: Props) => {
const bgColor = useColorModeValue('white', 'black');
return (
<ScrollDirectionContext.Consumer>
{ (scrollDirection) => (
<Flex
className={ className }
backgroundColor={ bgColor }
py={ 6 }
mx={{ base: -4, lg: 0 }}
px={{ base: 4, lg: 0 }}
justifyContent="space-between"
width={{ base: '100vw', lg: 'unset' }}
position="sticky"
top={{ base: scrollDirection === 'down' ? `${ TOP_DOWN }px` : `${ TOP_UP }px`, lg: 0 }}
transitionProperty="top,box-shadow,background-color,color"
transitionDuration="slow"
zIndex={{ base: 'sticky2', lg: 'docked' }}
boxShadow={{ base: isSticky ? 'md' : 'none', lg: 'none' }}
ref={ ref }
>
{ children }
</Flex>
) }
</ScrollDirectionContext.Consumer>
<Flex
className={ className }
backgroundColor={ bgColor }
py={ 6 }
mx={{ base: -4, lg: 0 }}
px={{ base: 4, lg: 0 }}
justifyContent="space-between"
width={{ base: '100vw', lg: 'unset' }}
position="sticky"
top={{ base: scrollDirection === 'down' ? `${ TOP_DOWN }px` : `${ TOP_UP }px`, lg: 0 }}
transitionProperty="top,box-shadow,background-color,color"
transitionDuration="slow"
zIndex={{ base: 'sticky2', lg: 'docked' }}
boxShadow={{ base: isSticky ? 'md' : 'none', lg: 'none' }}
ref={ ref }
>
{ children }
</Flex>
);
};
......
import { HStack, Box, Flex, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import { useScrollDirection } from 'lib/contexts/scrollDirection';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile';
......@@ -17,56 +17,53 @@ type Props = {
const Header = ({ hideOnScrollDown, isHomePage }: Props) => {
const bgColor = useColorModeValue('white', 'black');
const scrollDirection = useScrollDirection();
return (
<ScrollDirectionContext.Consumer>
{ (scrollDirection) => (
<>
<Box bgColor={ bgColor } display={{ base: 'block', lg: 'none' }}>
<Flex
as="header"
position="fixed"
top={ 0 }
left={ 0 }
paddingX={ 4 }
paddingY={ 2 }
bgColor={ bgColor }
width="100%"
alignItems="center"
justifyContent="space-between"
zIndex="sticky2"
transitionProperty="box-shadow"
transitionDuration="slow"
boxShadow={ !hideOnScrollDown && scrollDirection === 'down' ? 'md' : 'none' }
>
<Burger/>
<NetworkLogo/>
<ProfileMenuMobile/>
</Flex>
{ !isHomePage && <SearchBar withShadow={ !hideOnScrollDown }/> }
<>
<Box bgColor={ bgColor } display={{ base: 'block', lg: 'none' }}>
<Flex
as="header"
position="fixed"
top={ 0 }
left={ 0 }
paddingX={ 4 }
paddingY={ 2 }
bgColor={ bgColor }
width="100%"
alignItems="center"
justifyContent="space-between"
zIndex="sticky2"
transitionProperty="box-shadow"
transitionDuration="slow"
boxShadow={ !hideOnScrollDown && scrollDirection === 'down' ? 'md' : 'none' }
>
<Burger/>
<NetworkLogo/>
<ProfileMenuMobile/>
</Flex>
{ !isHomePage && <SearchBar withShadow={ !hideOnScrollDown }/> }
</Box>
{ !isHomePage && (
<HStack
as="header"
width="100%"
alignItems="center"
justifyContent="center"
gap={ 12 }
display={{ base: 'none', lg: 'flex' }}
paddingX={ 12 }
paddingTop={ 9 }
paddingBottom="52px"
>
<Box width="100%">
<SearchBar/>
</Box>
{ !isHomePage && (
<HStack
as="header"
width="100%"
alignItems="center"
justifyContent="center"
gap={ 12 }
display={{ base: 'none', lg: 'flex' }}
paddingX={ 12 }
paddingTop={ 9 }
paddingBottom="52px"
>
<Box width="100%">
<SearchBar/>
</Box>
<ColorModeToggler/>
<ProfileMenuDesktop/>
</HStack>
) }
</>
<ColorModeToggler/>
<ProfileMenuDesktop/>
</HStack>
) }
</ScrollDirectionContext.Consumer>
</>
);
};
......
......@@ -4,7 +4,7 @@ import React from 'react';
import type { ChangeEvent, FormEvent } from 'react';
import searchIcon from 'icons/search.svg';
import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import { useScrollDirection } from 'lib/contexts/scrollDirection';
const TOP = 55;
......@@ -17,6 +17,7 @@ interface Props {
const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => {
const [ isSticky, setIsSticky ] = React.useState(false);
const scrollDirection = useScrollDirection();
const handleScroll = React.useCallback(() => {
if (window.pageYOffset !== 0) {
......@@ -43,41 +44,37 @@ const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => {
const bgColor = useColorModeValue('white', 'black');
return (
<ScrollDirectionContext.Consumer>
{ (scrollDirection) => (
<chakra.form
noValidate
onSubmit={ onSubmit }
paddingX={ 4 }
paddingTop={ 1 }
paddingBottom={ 2 }
position="fixed"
top={ `${ TOP }px` }
left="0"
zIndex="sticky1"
bgColor={ bgColor }
transform={ scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)' }
transitionProperty="transform,box-shadow"
transitionDuration="slow"
display={{ base: 'block', lg: 'none' }}
w="100%"
boxShadow={ withShadow && scrollDirection !== 'down' && isSticky ? 'md' : 'none' }
>
<InputGroup size="sm">
<InputLeftElement >
<Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by addresses / ... "
ml="1px"
onChange={ onChange }
borderColor={ inputBorderColor }
/>
</InputGroup>
</chakra.form>
) }
</ScrollDirectionContext.Consumer>
<chakra.form
noValidate
onSubmit={ onSubmit }
paddingX={ 4 }
paddingTop={ 1 }
paddingBottom={ 2 }
position="fixed"
top={ `${ TOP }px` }
left="0"
zIndex="sticky1"
bgColor={ bgColor }
transform={ scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)' }
transitionProperty="transform,box-shadow"
transitionDuration="slow"
display={{ base: 'block', lg: 'none' }}
w="100%"
boxShadow={ withShadow && scrollDirection !== 'down' && isSticky ? 'md' : 'none' }
>
<InputGroup size="sm">
<InputLeftElement >
<Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by addresses / ... "
ml="1px"
onChange={ onChange }
borderColor={ inputBorderColor }
/>
</InputGroup>
</chakra.form>
);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment