diff --git a/packages/ui/src/hooks/useScrollDirection.js b/packages/ui/src/hooks/useScrollDirection.js new file mode 100644 index 00000000000..d6b655d08e0 --- /dev/null +++ b/packages/ui/src/hooks/useScrollDirection.js @@ -0,0 +1,65 @@ +import { useState, useEffect } from 'react' + +/** + * Custom hook to detect scroll direction and position + * Returns an object with: + * - scrollDirection: 'up' | 'down' | null + * - isAtTop: boolean indicating if at the top of the page + * - hideHeader: boolean indicating if header should be hidden + */ +const useScrollDirection = () => { + const [scrollDirection, setScrollDirection] = useState(null) + const [lastScrollY, setLastScrollY] = useState(0) + const [isAtTop, setIsAtTop] = useState(true) + const [hideHeader, setHideHeader] = useState(false) + + useEffect(() => { + let ticking = false + + const updateScrollDirection = () => { + const scrollY = window.scrollY + + // Check if at top of page + const atTop = scrollY < 10 + + if (Math.abs(scrollY - lastScrollY) < 5) { + ticking = false + return + } + + const direction = scrollY > lastScrollY ? 'down' : 'up' + + setScrollDirection(direction) + setIsAtTop(atTop) + + // Hide header when scrolling down and not at top + // Show header when scrolling up or at top + if (atTop) { + setHideHeader(false) + } else if (direction === 'down' && scrollY > 80) { + // Only hide after scrolling past the header height + setHideHeader(true) + } else if (direction === 'up') { + setHideHeader(false) + } + + setLastScrollY(scrollY > 0 ? scrollY : 0) + ticking = false + } + + const onScroll = () => { + if (!ticking) { + window.requestAnimationFrame(updateScrollDirection) + ticking = true + } + } + + window.addEventListener('scroll', onScroll) + + return () => window.removeEventListener('scroll', onScroll) + }, [lastScrollY]) + + return { scrollDirection, isAtTop, hideHeader } +} + +export default useScrollDirection diff --git a/packages/ui/src/layout/MainLayout/index.jsx b/packages/ui/src/layout/MainLayout/index.jsx index cab73c15a83..c8cde61918c 100644 --- a/packages/ui/src/layout/MainLayout/index.jsx +++ b/packages/ui/src/layout/MainLayout/index.jsx @@ -11,6 +11,7 @@ import Header from './Header' import Sidebar from './Sidebar' import { drawerWidth, headerHeight } from '@/store/constant' import { SET_MENU } from '@/store/actions' +import useScrollDirection from '@/hooks/useScrollDirection' // styles const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ @@ -59,6 +60,7 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ const MainLayout = () => { const theme = useTheme() const matchDownMd = useMediaQuery(theme.breakpoints.down('lg')) + const { hideHeader } = useScrollDirection() // Handle left drawer const leftDrawerOpened = useSelector((state) => state.customization.opened) @@ -83,7 +85,9 @@ const MainLayout = () => { elevation={0} sx={{ bgcolor: theme.palette.background.default, - transition: leftDrawerOpened ? theme.transitions.create('width') : 'none' + transform: hideHeader ? 'translateY(-100%)' : 'translateY(0)', + transition: 'transform 0.3s ease-in-out', + boxShadow: hideHeader ? 'none' : '0 2px 4px rgba(0,0,0,0.1)' }} >