Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into support/use-turborepo

Yuki Takei 3 лет назад
Родитель
Сommit
195dc8e1f7

+ 0 - 1
apps/app/package.json

@@ -243,7 +243,6 @@
     "simplebar-react": "^2.3.6",
     "socket.io-client": "^4.2.0",
     "source-map-loader": "^4.0.1",
-    "sticky-events": "^3.4.11",
     "swagger2openapi": "^5.3.1",
     "tsc-alias": "^1.2.9"
   }

+ 31 - 41
apps/app/src/components/Fab.tsx

@@ -4,8 +4,8 @@ import React, {
 
 import { animateScroll } from 'react-scroll';
 import { useRipple } from 'react-use-ripple';
-import StickyEvents from 'sticky-events';
 
+import { useSticky } from '~/client/side-effects/use-sticky';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useIsAbleToChangeEditorMode } from '~/stores/ui';
@@ -26,55 +26,45 @@ export const Fab = (): JSX.Element => {
 
   const [animateClasses, setAnimateClasses] = useState<string>('invisible');
   const [buttonClasses, setButtonClasses] = useState<string>('');
-  const [isSticky, setIsSticky] = useState<boolean>(false);
+  const [isStickyApplied, setIsStickyApplied] = useState(false);
 
   // ripple
   const createBtnRef = useRef(null);
   useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
 
-  /**
-   * After the fade animation is finished, fix the button display status.
-   * Prevents the fade animation occurred each time by button components rendered.
-   * Check Fab.module.scss for fade animation time.
-   */
+  // Get sticky status
+  const isSticky = useSticky('#grw-fav-sticky-trigger');
+
+  // check if isSticky is already initialized then save it in isStickyApplied state
   useEffect(() => {
-    const timer = setTimeout(() => {
-      if (isSticky) {
-        setAnimateClasses('visible');
-        setButtonClasses('');
-      }
-      else {
-        setAnimateClasses('invisible');
-      }
-    }, 500);
-    return () => clearTimeout(timer);
+    if (isSticky) {
+      setIsStickyApplied(true);
+    }
   }, [isSticky]);
 
-  const stickyChangeHandler = useCallback((event) => {
-    logger.debug('StickyEvents.CHANGE detected');
-
-    const newAnimateClasses = event.detail.isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
-    const newButtonClasses = event.detail.isSticky ? '' : 'disabled grw-pointer-events-none';
-
-    setAnimateClasses(newAnimateClasses);
-    setButtonClasses(newButtonClasses);
-    setIsSticky(event.detail.isSticky);
-  }, []);
-
-  // setup effect by sticky event
+  // Apply new classes if only isSticky is initialized, otherwise no classes have changed
+  // Prevents the Fab button from showing on first load due to the isSticky effect
   useEffect(() => {
-    // sticky
-    // See: https://github.com/ryanwalters/sticky-events
-    const stickyEvents = new StickyEvents({ stickySelector: '#grw-fav-sticky-trigger' });
-    const { stickySelector } = stickyEvents;
-    const elem = document.querySelector(stickySelector);
-    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-    // return clean up handler
-    return () => {
-      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-    };
-  }, [stickyChangeHandler]);
+    if (isStickyApplied) {
+      const timer = setTimeout(() => {
+        if (isSticky) {
+          setAnimateClasses('visible');
+          setButtonClasses('');
+        }
+        else {
+          setAnimateClasses('invisible');
+        }
+      }, 500);
+
+      const newAnimateClasses = isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
+      const newButtonClasses = isSticky ? '' : 'disabled grw-pointer-events-none';
+
+      setAnimateClasses(newAnimateClasses);
+      setButtonClasses(newButtonClasses);
+
+      return () => clearTimeout(timer);
+    }
+  }, [isSticky, isStickyApplied]);
 
   const PageCreateButton = useCallback(() => {
     return (

+ 5 - 22
apps/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -2,9 +2,9 @@ import React, {
   useState, useRef, useEffect, useCallback,
 } from 'react';
 
-import StickyEvents from 'sticky-events';
 import { debounce } from 'throttle-debounce';
 
+import { useSticky } from '~/client/side-effects/use-sticky';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSidebarCollapsed } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
@@ -32,7 +32,6 @@ export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProp
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: isSidebarCollapsed } = useSidebarCollapsed();
 
-  const [isVisible, setIsVisible] = useState<boolean>(false);
   const [width, setWidth] = useState<number>(0);
 
   // use more specific type HTMLDivElement for avoid assertion error.
@@ -40,6 +39,9 @@ export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProp
   const fixedContainerRef = useRef<HTMLDivElement>(null);
   const clientWidth = fixedContainerRef.current?.parentElement?.clientWidth;
 
+  // Get sticky status
+  const isSticky = useSticky('#grw-subnav-sticky-trigger');
+
   // Do not use clientWidth as useCallback deps, resizing events will not work in production builds.
   const initWidth = useCallback(() => {
     if (fixedContainerRef.current != null && fixedContainerRef.current.parentElement != null) {
@@ -49,25 +51,6 @@ export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProp
     }
   }, []);
 
-  const stickyChangeHandler = useCallback((event) => {
-    logger.debug('StickyEvents.CHANGE detected');
-    setIsVisible(event.detail.isSticky);
-  }, []);
-
-  // setup effect by sticky-events
-  useEffect(() => {
-    // sticky-events
-    // See: https://github.com/ryanwalters/sticky-events
-    const { stickySelector } = new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' });
-    const elem = document.querySelector(stickySelector);
-    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-    // return clean up handler
-    return () => {
-      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-    };
-  }, [stickyChangeHandler]);
-
   // setup effect by resizing event
   useEffect(() => {
     const resizeHandler = debounce(100, initWidth);
@@ -100,7 +83,7 @@ export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProp
   }
 
   return (
-    <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
+    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`}>
       <div
         id="grw-subnav-fixed-container"
         className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}

+ 7 - 21
apps/app/src/components/StickyStretchableScroller.tsx

@@ -1,11 +1,11 @@
 import React, {
-  useEffect, useCallback, FC, useRef, useState, useMemo, RefObject,
+  useEffect, useCallback, useRef, useState, useMemo, RefObject,
 } from 'react';
 
 import SimpleBar from 'simplebar-react';
-import StickyEvents from 'sticky-events';
 import { debounce } from 'throttle-debounce';
 
+import { useSticky } from '~/client/side-effects/use-sticky';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:cli:StickyStretchableScroller');
@@ -49,6 +49,9 @@ export const StickyStretchableScroller = (props: StickyStretchableScrollerProps)
   const simplebarRef = useRef<SimpleBar>(null);
   const [simplebarMaxHeight, setSimplebarMaxHeight] = useState<number|undefined>();
 
+  // Get sticky status
+  const isSticky = useSticky(stickyElemSelector);
+
   /**
    * Reset scrollbar
    */
@@ -70,26 +73,9 @@ export const StickyStretchableScroller = (props: StickyStretchableScrollerProps)
 
   const resetScrollbarDebounced = useMemo(() => debounce(100, resetScrollbar), [resetScrollbar]);
 
-  const stickyChangeHandler = useCallback(() => {
-    logger.debug('StickyEvents.CHANGE detected');
-    resetScrollbarDebounced();
-  }, [resetScrollbarDebounced]);
-
-  // setup effect by sticky event
   useEffect(() => {
-    // sticky
-    // See: https://github.com/ryanwalters/sticky-events
-    const stickyEvents = new StickyEvents({ stickySelector: stickyElemSelector });
-    stickyEvents.enableEvents();
-    const { stickySelector } = stickyEvents;
-    const elem = document.querySelector(stickySelector);
-    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-    // return clean up handler
-    return () => {
-      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-    };
-  }, [stickyElemSelector, stickyChangeHandler]);
+    resetScrollbarDebounced();
+  }, [isSticky, resetScrollbarDebounced]);
 
   // setup effect by resizing event
   useEffect(() => {

+ 37 - 0
packages/app/src/client/side-effects/use-sticky.ts

@@ -0,0 +1,37 @@
+import { useState, useEffect } from 'react';
+
+// Custom hook that accepts a selector string as an argument
+// and returns a boolean indicating whether the selected element is currently sticky.
+export const useSticky = (selector: string): boolean => {
+  const [isSticky, setIsSticky] = useState<boolean>(false);
+
+  useEffect(() => {
+    // Get element to observe
+    const stickyElement = document.querySelector(selector);
+    // Updates the sticky status based on the current position of the observed element.
+    const observe = () => {
+      // If the observed element is empty or not an instance of Element, return early.
+      if (stickyElement == null || !(stickyElement instanceof Element)) return;
+
+      // Calculate the element's offset from the top of the viewport and the value of its "top" CSS property.
+      const elemOffset = stickyElement.getBoundingClientRect().top;
+      const stickyOffset = parseInt(window.getComputedStyle(stickyElement).top);
+
+      // Update the sticky status based on whether the element's offset is less than or equal to the sticky offset.
+      setIsSticky(elemOffset <= stickyOffset);
+    };
+    // Call the observe function immediately and add it as a listener for scroll and resize events.
+    observe();
+    document.addEventListener('scroll', observe);
+    window.addEventListener('resize', observe);
+
+    // Remove the scroll and resize event listeners when the component unmounts or the selector value changes.
+    return () => {
+      document.removeEventListener('scroll', observe);
+      window.removeEventListener('resize', observe);
+    };
+  }, [selector]);
+
+  // Return the current sticky status.
+  return isSticky;
+};

+ 0 - 5
yarn.lock

@@ -17965,11 +17965,6 @@ stealthy-require@^1.1.1:
   resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
   integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
 
-sticky-events@^3.4.11:
-  version "3.4.11"
-  resolved "https://registry.yarnpkg.com/sticky-events/-/sticky-events-3.4.11.tgz#c44b7866648c5b2818a00fe93f709aa86e9a09d3"
-  integrity sha512-g1ex5lR7EGJv8EXJh4gdBu0m8FMgAVeqFAow3dRR9MwxAIfBNVC2GtlXI1z+oMLE+/Ot2At+gp1aO/tbUGoOnQ==
-
 stoppable@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b"