jam411 пре 3 година
родитељ
комит
9cb7319d15

+ 20 - 16
packages/app/src/components/Fab.tsx

@@ -27,27 +27,20 @@ export const Fab = (): JSX.Element => {
 
   const [animateClasses, setAnimateClasses] = useState('invisible');
   const [buttonClasses, setButtonClasses] = useState('');
+  const [isSticky, setIsSticky] = useState<boolean>(false);
 
   // ripple
   const createBtnRef = useRef(null);
   useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
 
-  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);
-
-    /**
-     * 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.
-     */
-    setTimeout(() => {
-      if (event.detail.isSticky) {
+  /**
+   * 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.
+   */
+  useEffect(() => {
+    const timer = setTimeout(() => {
+      if (isSticky) {
         setAnimateClasses('visible');
         setButtonClasses('');
       }
@@ -55,7 +48,18 @@ export const Fab = (): JSX.Element => {
         setAnimateClasses('invisible');
       }
     }, 500);
+    return () => clearTimeout(timer);
+  }, [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

+ 33 - 69
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx → packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -1,8 +1,7 @@
 import React, {
-  useMemo, useState, useRef, useEffect, useCallback,
+  useState, useRef, useEffect, useCallback,
 } from 'react';
 
-import PropTypes from 'prop-types';
 import StickyEvents from 'sticky-events';
 import { debounce } from 'throttle-debounce';
 
@@ -10,87 +9,49 @@ import { useSWRxCurrentPage } from '~/stores/page';
 import { useSidebarCollapsed } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
-
 import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
 
 import styles from './GrowiSubNavigationSwitcher.module.scss';
 
 const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
 
-
 /**
- * Subnavigation
+ * GrowiSubNavigation
  *
  * needs:
  *   #grw-subnav-fixed-container element
  *   #grw-subnav-sticky-trigger element
- *
- * @param {object} props
  */
-const GrowiSubNavigationSwitcher = (props) => {
+export const GrowiSubNavigationSwitcher = (): JSX.Element => {
 
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: isSidebarCollapsed } = useSidebarCollapsed();
 
-  const [isVisible, setVisible] = useState(false);
-  const [width, setWidth] = useState(null);
+  const [isVisible, setIsVisible] = useState<boolean>(false);
+  const [width, setWidth] = useState<number>(0);
 
-  const fixedContainerRef = useRef();
-  /*
-  * Comment out to prevent err >>> TypeError: Cannot read properties of null (reading 'bottom')
-  * The above err occurs when moving to admin page after rendering normal pages.
-  * This is because id "grw-subnav-sticky-trigger" does not exist on admin pages.
-  */
-  const stickyEvents = useMemo(() => new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' }), []);
+  // use more specific type HTMLDivElement for avoid assertion error.
+  // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
+  const fixedContainerRef = useRef<HTMLDivElement>(null);
 
   const initWidth = useCallback(() => {
-    const instance = fixedContainerRef.current;
-
-    if (instance == null || instance.parentNode == null) {
-      return;
+    if (fixedContainerRef.current && fixedContainerRef.current.parentElement) {
+      // get parent elements width
+      const { clientWidth } = fixedContainerRef.current.parentElement;
+      setWidth(clientWidth);
     }
-
-    // get parent width
-    const { clientWidth } = instance.parentNode;
-    // update style
-    setWidth(clientWidth);
   }, []);
 
-  const initVisible = useCallback(() => {
-    const elements = stickyEvents.stickyElements;
-
-    for (const elem of elements) {
-      const bool = stickyEvents.isSticking(elem);
-      if (bool) {
-        setVisible(bool);
-        break;
-      }
-    }
-
-  }, [stickyEvents]);
-
-  // setup effect by resizing event
-  useEffect(() => {
-    const resizeHandler = debounce(100, initWidth);
-
-    window.addEventListener('resize', resizeHandler);
-
-    // return clean up handler
-    return () => {
-      window.removeEventListener('resize', resizeHandler);
-    };
-  }, [initWidth]);
-
   const stickyChangeHandler = useCallback((event) => {
     logger.debug('StickyEvents.CHANGE detected');
-    setVisible(event.detail.isSticky);
+    setIsVisible(event.detail.isSticky);
   }, []);
 
-  // setup effect by sticky event
+  // setup effect by sticky-events
   useEffect(() => {
-    // sticky
+    // sticky-events
     // See: https://github.com/ryanwalters/sticky-events
-    const { stickySelector } = stickyEvents;
+    const { stickySelector } = new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' });
     const elem = document.querySelector(stickySelector);
     elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
 
@@ -98,7 +59,18 @@ const GrowiSubNavigationSwitcher = (props) => {
     return () => {
       elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
     };
-  }, [stickyChangeHandler, stickyEvents]);
+  }, [stickyChangeHandler]);
+
+  // setup effect by resizing event
+  useEffect(() => {
+    const resizeHandler = debounce(100, initWidth);
+    window.addEventListener('resize', resizeHandler);
+
+    // return clean up handler
+    return () => {
+      window.removeEventListener('resize', resizeHandler);
+    };
+  }, [initWidth]);
 
   // update width when sidebar collapsing changed
   useEffect(() => {
@@ -107,16 +79,14 @@ const GrowiSubNavigationSwitcher = (props) => {
     }
   }, [isSidebarCollapsed, initWidth]);
 
-  // initialize
+  // initialize width
   useEffect(() => {
     initWidth();
+  }, [initWidth]);
 
-    // check sticky state several times
-    setTimeout(initVisible, 100);
-    setTimeout(initVisible, 300);
-    setTimeout(initVisible, 2000);
-
-  }, [initWidth, initVisible]);
+  if (currentPage == null) {
+    return <></>;
+  }
 
   return (
     <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
@@ -131,9 +101,3 @@ const GrowiSubNavigationSwitcher = (props) => {
     </div>
   );
 };
-
-GrowiSubNavigationSwitcher.propTypes = {
-  isLinkSharingDisabled: PropTypes.bool,
-};
-
-export default GrowiSubNavigationSwitcher;

+ 2 - 1
packages/app/src/pages/[[...path]].page.tsx

@@ -90,7 +90,8 @@ const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').
 const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
 const PageSideContents = dynamic<PageSideContentsProps>(() => import('../components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
-const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
+const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher')
+  .then(mod => mod.GrowiSubNavigationSwitcher), { ssr: false });
 const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
 const DrawioModal = dynamic(() => import('../components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });