1- import { NavLink , useLocation } from "react-router-dom" ;
1+ import { NavLink } from "react-router-dom" ;
22import styled , { RuleSet } from "styled-components" ;
33import classNames from "classnames" ;
4- import { PropsWithChildren , useCallback , useEffect , useState } from "react" ;
4+ import { PropsWithChildren , useEffect , useState } from "react" ;
5+ import { throttle } from "lodash-es" ;
56import IconSvg from "../IconSvg" ;
67import BaseMenu from "./BaseMenu" ;
78import BaseFooter from "./BaseFooter" ;
@@ -10,6 +11,8 @@ import logo from "@/assets/img/logo.png";
1011import { useIsAuthed } from "@/store/hooks/auth" ;
1112import { useAppDispatch , useAppSelector } from "@/store/hooks" ;
1213import { setIsMenuVisible } from "@/store/slices/ui" ;
14+ import { setScrollTop } from "@/utils/dom" ;
15+ import { flexCenter } from "@/styles/styled-mixins/base" ;
1316
1417const Header = styled . header `
1518 padding: 18px 40px;
@@ -66,23 +69,81 @@ const Main = styled.main<{ $mainCss?: RuleSet }>`
6669
6770const LayoutWrapper = styled . section `
6871 min-height: 100%;
72+
73+ > aside {
74+ position: fixed;
75+ bottom: 160px;
76+ right: 24px;
77+
78+ ${ IconSvg } {
79+ ${ flexCenter }
80+ color: #fff;
81+ font-size: 24px;
82+ width: 50px;
83+ height: 50px;
84+ border-radius: 50%;
85+ background-color: rgba(102, 57, 57, 0.4);
86+ cursor: pointer;
87+ + ${ IconSvg } {
88+ margin-top: 10px;
89+ margin-left: 0;
90+ }
91+ }
92+ }
6993` ;
7094
71- const BaseLayout : React . FC < PropsWithChildren < { mainCss ?: RuleSet } > > = ( { children, mainCss } ) => {
95+ const BaseLayout : React . FC < PropsWithChildren < { mainCss ?: RuleSet ; asideIcons ?: React . ReactNode } > > = ( {
96+ children,
97+ asideIcons,
98+ mainCss,
99+ } ) => {
72100 const isAuthed = useIsAuthed ( ) ;
73101 const isMenuVisible = useAppSelector ( ( state ) => state . ui . isMenuVisible ) ;
74102 const dispatch = useAppDispatch ( ) ;
75103 const [ isAnimationEnabled , setIsAnimationEnabled ] = useState ( false ) ;
104+ const [ isShowGoTopIcon , setIsShowGoTopIcon ] = useState ( false ) ;
76105
77- const location = useLocation ( ) ;
78-
79- const hideMenu = useCallback ( ( ) => {
106+ const hideMenu = ( ) => {
80107 dispatch ( setIsMenuVisible ( false ) ) ;
81- } , [ dispatch ] ) ;
108+ } ;
82109
83110 useEffect ( ( ) => {
84- hideMenu ( ) ;
85- } , [ location , hideMenu ] ) ;
111+ let hideTimer : number | null = null ;
112+ const clearHideTimer = ( ) => {
113+ if ( hideTimer ) {
114+ clearTimeout ( hideTimer ) ;
115+ hideTimer = null ;
116+ }
117+ } ;
118+
119+ const setHideTimer = ( ) => {
120+ clearHideTimer ( ) ;
121+ hideTimer = window . setTimeout ( ( ) => {
122+ setIsShowGoTopIcon ( false ) ;
123+ } , 5000 ) ;
124+ } ;
125+
126+ const onScroll = ( ) => {
127+ const currScrollTop = document . body . scrollTop || document . documentElement . scrollTop ;
128+ if ( currScrollTop > 0 ) {
129+ setIsShowGoTopIcon ( true ) ;
130+ setHideTimer ( ) ;
131+ } else {
132+ setIsShowGoTopIcon ( false ) ;
133+ }
134+ } ;
135+
136+ const onScrollThrottle = throttle ( onScroll , 300 , { leading : true } ) ;
137+
138+ document . addEventListener ( "scroll" , onScrollThrottle ) ;
139+ return ( ) => {
140+ hideMenu ( ) ;
141+ document . removeEventListener ( "scroll" , onScrollThrottle ) ;
142+ clearHideTimer ( ) ;
143+ document . body . style . overflow = "" ;
144+ } ;
145+ // eslint-disable-next-line react-hooks/exhaustive-deps
146+ } , [ ] ) ;
86147
87148 const onToggleMenu = ( ) => {
88149 if ( isAnimationEnabled === false ) {
@@ -110,6 +171,12 @@ const BaseLayout: React.FC<PropsWithChildren<{ mainCss?: RuleSet }>> = ({ childr
110171 hideMenu ( ) ;
111172 } ;
112173
174+ const goToTop = ( ) => {
175+ setScrollTop ( {
176+ useAnimation : true ,
177+ } ) ;
178+ } ;
179+
113180 const sectionClass = classNames ( {
114181 slideInLeft : isMenuVisible ,
115182 slideOutLeft : ! isMenuVisible ,
@@ -142,6 +209,11 @@ const BaseLayout: React.FC<PropsWithChildren<{ mainCss?: RuleSet }>> = ({ childr
142209 < BaseMenu open = { isMenuVisible } />
143210
144211 < Mask open = { isMenuVisible } onClick = { onClickMask } />
212+
213+ < aside >
214+ { asideIcons }
215+ < IconSvg icon = "arrow-up" style = { { display : isShowGoTopIcon ? "flex" : "none" } } onClick = { goToTop } />
216+ </ aside >
145217 </ LayoutWrapper >
146218 ) ;
147219} ;
0 commit comments