From c3d8791455db85c6b37ba79e872bdfbfe3287e39 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 21 Jul 2023 05:20:40 -0700 Subject: [PATCH] Refactor scrollarea component for overlayscrollbars --- src/renderer/components/page-header/index.tsx | 32 +++-- src/renderer/components/scroll-area/index.tsx | 136 ++++++++---------- .../components/album-detail-content.tsx | 4 - .../features/home/routes/home-route.tsx | 2 +- src/renderer/index.tsx | 1 + src/renderer/styles/ag-grid.scss | 2 +- src/renderer/styles/global.scss | 1 + src/renderer/styles/overlayscrollbars.scss | 15 ++ src/renderer/themes/default.scss | 1 + 9 files changed, 95 insertions(+), 99 deletions(-) create mode 100644 src/renderer/styles/overlayscrollbars.scss diff --git a/src/renderer/components/page-header/index.tsx b/src/renderer/components/page-header/index.tsx index a31d7c4a..1f4a83e3 100644 --- a/src/renderer/components/page-header/index.tsx +++ b/src/renderer/components/page-header/index.tsx @@ -1,6 +1,6 @@ -import { useRef } from 'react'; import { Flex, FlexProps } from '@mantine/core'; import { AnimatePresence, motion, Variants } from 'framer-motion'; +import { useRef } from 'react'; import styled from 'styled-components'; import { useShouldPadTitlebar, useTheme } from '/@/renderer/hooks'; import { useWindowSettings } from '/@/renderer/store/settings.store'; @@ -64,6 +64,7 @@ const BackgroundImageOverlay = styled.div<{ theme: 'light' | 'dark' }>` export interface PageHeaderProps extends Omit { + animated?: boolean; backgroundColor?: string; children?: React.ReactNode; height?: string; @@ -79,12 +80,19 @@ const TitleWrapper = styled(motion.div)` `; const variants: Variants = { - animate: { opacity: 1 }, + animate: { + opacity: 1, + transition: { + duration: 0.3, + ease: 'easeIn', + }, + }, exit: { opacity: 0 }, initial: { opacity: 0 }, }; export const PageHeader = ({ + animated, position, height, backgroundColor, @@ -109,17 +117,15 @@ export const PageHeader = ({ $isHidden={isHidden} $padRight={padRight} > - - {!isHidden && ( - - {children} - - )} + + + {children} + {backgroundColor && ( diff --git a/src/renderer/components/scroll-area/index.tsx b/src/renderer/components/scroll-area/index.tsx index e107edcb..7d557f48 100644 --- a/src/renderer/components/scroll-area/index.tsx +++ b/src/renderer/components/scroll-area/index.tsx @@ -1,8 +1,9 @@ -import { forwardRef, Ref, useEffect, useRef, useState } from 'react'; import type { ScrollAreaProps as MantineScrollAreaProps } from '@mantine/core'; import { ScrollArea as MantineScrollArea } from '@mantine/core'; -import { useMergedRef, useTimeout } from '@mantine/hooks'; -import { motion, useScroll } from 'framer-motion'; +import { useMergedRef } from '@mantine/hooks'; +import { useInView } from 'framer-motion'; +import { useOverlayScrollbars } from 'overlayscrollbars-react'; +import { forwardRef, Ref, useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { PageHeader, PageHeaderProps } from '/@/renderer/components/page-header'; import { useWindowSettings } from '/@/renderer/store/settings.store'; @@ -30,25 +31,6 @@ const StyledScrollArea = styled(MantineScrollArea)` const StyledNativeScrollArea = styled.div<{ scrollBarOffset?: string; windowBarStyle?: Platform }>` height: 100%; - overflow-y: auto; - - &::-webkit-scrollbar-track { - margin-top: ${(props) => - props.windowBarStyle === Platform.WINDOWS || - props.windowBarStyle === Platform.MACOS || - props.windowBarStyle === Platform.LINUX - ? '0px' - : props.scrollBarOffset || '65px'}; - } - - &::-webkit-scrollbar-thumb { - margin-top: ${(props) => - props.windowBarStyle === Platform.WINDOWS || - props.windowBarStyle === Platform.MACOS || - props.windowBarStyle === Platform.LINUX - ? '0px' - : props.scrollBarOffset || '65px'}; - } `; export const ScrollArea = forwardRef(({ children, ...props }: ScrollAreaProps, ref: Ref) => { @@ -87,90 +69,84 @@ export const NativeScrollArea = forwardRef( ref: Ref, ) => { const { windowBarStyle } = useWindowSettings(); - const [hideScrollbar, setHideScrollbar] = useState(false); - const [hideHeader, setHideHeader] = useState(true); - const { start, clear } = useTimeout( - () => setHideScrollbar(true), - scrollHideDelay !== undefined ? scrollHideDelay * 1000 : 0, - ); - const containerRef = useRef(null); - const mergedRef = useMergedRef(ref, containerRef); + const [isPastOffset, setIsPastOffset] = useState(false); - const { scrollYProgress } = useScroll({ - container: containerRef, - offset: pageHeaderProps?.offset || ['center start', 'end start'], - target: pageHeaderProps?.target, + // useInView initializes as false, so we need to track this to properly render the header + const isInViewInitializedRef = useRef(null); + + const isInView = useInView({ + current: pageHeaderProps?.target?.current, }); - // Automatically hide the scrollbar after the timeout duration useEffect(() => { - start(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (!isInViewInitializedRef.current && isInView) { + isInViewInitializedRef.current = true; + } + }, [isInView]); + + const [initialize] = useOverlayScrollbars({ + defer: true, + + events: { + scroll: (_instance, e) => { + if (!pageHeaderProps?.offset) { + return; + } + + const offset = pageHeaderProps?.offset; + const scrollTop = (e?.target as HTMLDivElement)?.scrollTop; + + if (scrollTop > offset && isPastOffset === false) { + setIsPastOffset(true); + } else if (scrollTop <= offset && isPastOffset === true) { + setIsPastOffset(false); + } + }, + }, + options: { + overflow: { x: 'hidden', y: 'scroll' }, + scrollbars: { + autoHide: 'move', + autoHideDelay: 500, + pointers: ['mouse', 'pen', 'touch'], + theme: 'feishin', + visibility: 'visible', + }, + }, + }); useEffect(() => { - const setHeaderVisibility = (v: number) => { - if (v === 1) { - return setHideHeader(false); - } + if (containerRef.current) { + initialize(containerRef.current as HTMLDivElement); + } + }, [initialize]); - if (hideHeader === false) { - return setHideHeader(true); - } + // console.log('isPastOffsetRef.current', isPastOffsetRef.current); - return undefined; - }; + const mergedRef = useMergedRef(ref, containerRef); - const unsubscribe = scrollYProgress.on('change', setHeaderVisibility); - - return () => { - unsubscribe(); - }; - }, [hideHeader, scrollYProgress]); + const shouldShowHeader = + !noHeader && (isPastOffset || (isInViewInitializedRef.current && !isInView)); return ( <> - {!noHeader && ( + {shouldShowHeader && ( )} { - setHideScrollbar(false); - clear(); - }} - onMouseLeave={() => { - start(); - }} {...props} > {children} - {debugScrollPosition && ( - - )} ); }, diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index b6313793..6db00607 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -50,10 +50,6 @@ const DetailContainer = styled.div` flex-direction: column; padding: 1rem 2rem 5rem; overflow: hidden; - - .ag-theme-alpine-dark { - --ag-header-background-color: rgba(0, 0, 0, 0%) !important; - } `; interface AlbumDetailContentProps { diff --git a/src/renderer/features/home/routes/home-route.tsx b/src/renderer/features/home/routes/home-route.tsx index 00b1a3d6..2378c2da 100644 --- a/src/renderer/features/home/routes/home-route.tsx +++ b/src/renderer/features/home/routes/home-route.tsx @@ -139,7 +139,7 @@ const HomeRoute = () => { Home ), - offset: ['0px', '200px'], + offset: 200, }} >