Commit 0f710c19 authored by tom's avatar tom

refactor scroll direction context

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