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

Merge pull request #7187 from weseek/feat/sticky-event-features

feat: Enable features used sticky-event
Yuki Takei 3 лет назад
Родитель
Сommit
11e4ea74f9
20 измененных файлов с 247 добавлено и 254 удалено
  1. 33 1
      packages/app/src/components/Fab.module.scss
  2. 50 34
      packages/app/src/components/Fab.tsx
  3. 1 1
      packages/app/src/components/Layout/BasicLayout.tsx
  4. 1 1
      packages/app/src/components/Layout/ShareLinkLayout.tsx
  5. 3 3
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  6. 0 9
      packages/app/src/components/Navbar/GrowiSubNavigation.module.scss
  7. 0 141
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx
  8. 9 0
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss
  9. 103 0
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx
  10. 8 0
      packages/app/src/components/SearchPage/SearchResultContent.module.scss
  11. 3 1
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  12. 20 20
      packages/app/src/components/StickyStretchableScroller.tsx
  13. 2 1
      packages/app/src/pages/[[...path]].page.tsx
  14. 2 2
      packages/app/src/stores/ui.tsx
  15. 0 32
      packages/app/src/styles/_layout.scss
  16. 3 1
      packages/app/src/styles/theme/_apply-colors.scss
  17. 2 2
      packages/app/test/cypress/integration/20-basic-features/20-basic-features--access-to-page.spec.ts
  18. 2 2
      packages/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.spec.ts
  19. 2 2
      packages/app/test/cypress/integration/60-home/60-home--home.spec.ts
  20. 3 1
      packages/preset-themes/src/styles/theme/_apply-colors.scss

+ 33 - 1
packages/app/src/components/Fab.module.scss

@@ -1,9 +1,41 @@
+@use '~/styles/bootstrap/init' as bs;
+
 .grw-fab :global {
 .grw-fab :global {
+  position: fixed;
+  right: 1.5rem;
+  bottom: 3rem;
+  z-index: bs.$zindex-fixed;
+
+  transition: all 200ms linear;
+
+  .btn-create-page {
+    width: 60px;
+    height: 60px;
+    font-size: 24px;
+
+    box-shadow: 2px 3px 6px #0000005d;
+    svg {
+      width: 28px;
+      height: 28px;
+    }
+  }
+
+  .btn-scroll-to-top {
+    width: 40px;
+    height: 40px;
+
+    opacity: 0.4;
+    svg {
+      width: 18px;
+      height: 18px;
+    }
+  }
+
   // workaround
   // workaround
   // https://stackoverflow.com/a/57667536
   // https://stackoverflow.com/a/57667536
   .fadeInUp {
   .fadeInUp {
     & :local {
     & :local {
-      animation: fab-fadeinup 1s ease 0s;
+      animation: fab-fadeinup 0.5s ease 0s;
     }
     }
   }
   }
   .fadeOut {
   .fadeOut {

+ 50 - 34
packages/app/src/components/Fab.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useState, useCallback, useRef,
+  useState, useCallback, useRef, useEffect,
 } from 'react';
 } from 'react';
 
 
 import { animateScroll } from 'react-scroll';
 import { animateScroll } from 'react-scroll';
@@ -7,9 +7,9 @@ import { useRipple } from 'react-use-ripple';
 import StickyEvents from 'sticky-events';
 import StickyEvents from 'sticky-events';
 
 
 import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
-import { useCurrentUser } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useCurrentPagePath } from '~/stores/page';
+import { useIsAbleToChangeEditorMode } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { CreatePageIcon } from './Icons/CreatePageIcon';
 import { CreatePageIcon } from './Icons/CreatePageIcon';
@@ -21,45 +21,61 @@ const logger = loggerFactory('growi:cli:Fab');
 
 
 export const Fab = (): JSX.Element => {
 export const Fab = (): JSX.Element => {
 
 
-  const { data: currentUser } = useCurrentUser();
+  const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
   const { data: currentPath = '' } = useCurrentPagePath();
   const { data: currentPath = '' } = useCurrentPagePath();
   const { open: openCreateModal } = usePageCreateModal();
   const { open: openCreateModal } = usePageCreateModal();
 
 
-  const [animateClasses, setAnimateClasses] = useState('invisible');
-  const [buttonClasses, setButtonClasses] = useState('');
+  const [animateClasses, setAnimateClasses] = useState<string>('invisible');
+  const [buttonClasses, setButtonClasses] = useState<string>('');
+  const [isSticky, setIsSticky] = useState<boolean>(false);
 
 
   // ripple
   // ripple
   const createBtnRef = useRef(null);
   const createBtnRef = useRef(null);
   useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
   useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
 
 
-  /*
-  * TODO: Comment out to prevent err >>> TypeError: Cannot read properties of null (reading 'bottom')
-  *       We need add style={{ position: 'relative }} to child elements if disable StickyEvents. see: use grep = "<Fab".
-  */
-  // 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);
-  // }, []);
-
-  // // setup effect by sticky event
-  // 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]);
+  /**
+   * 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('');
+      }
+      else {
+        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
+  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]);
 
 
   const PageCreateButton = useCallback(() => {
   const PageCreateButton = useCallback(() => {
     return (
     return (
@@ -102,7 +118,7 @@ export const Fab = (): JSX.Element => {
 
 
   return (
   return (
     <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab-container">
     <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab-container">
-      {currentUser != null && <PageCreateButton />}
+      {isAbleToChangeEditorMode && <PageCreateButton />}
       <ScrollToTopButton />
       <ScrollToTopButton />
     </div>
     </div>
   );
   );

+ 1 - 1
packages/app/src/components/Layout/BasicLayout.tsx

@@ -43,7 +43,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
             <Sidebar />
             <Sidebar />
           </div>
           </div>
 
 
-          <div className="flex-fill mw-0" style={{ position: 'relative' }}>
+          <div className="flex-fill mw-0">
             <AlertSiteUrlUndefined />
             <AlertSiteUrlUndefined />
             {children}
             {children}
           </div>
           </div>

+ 1 - 1
packages/app/src/components/Layout/ShareLinkLayout.tsx

@@ -28,7 +28,7 @@ export const ShareLinkLayout = ({ children }: Props): JSX.Element => {
       <GrowiNavbar isGlobalSearchHidden={true} />
       <GrowiNavbar isGlobalSearchHidden={true} />
 
 
       <div className="page-wrapper d-flex d-print-block">
       <div className="page-wrapper d-flex d-print-block">
-        <div className="flex-fill mw-0" style={{ position: 'relative' }}>
+        <div className="flex-fill mw-0">
           {children}
           {children}
         </div>
         </div>
       </div>
       </div>

+ 3 - 3
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -27,7 +27,7 @@ import {
 import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import {
 import {
   EditorMode, useDrawerMode, useEditorMode, useIsAbleToShowPageManagement, useIsAbleToShowTagLabel,
   EditorMode, useDrawerMode, useEditorMode, useIsAbleToShowPageManagement, useIsAbleToShowTagLabel,
-  useIsAbleToShowPageEditorModeManager, useIsAbleToShowPageAuthors,
+  useIsAbleToChangeEditorMode, useIsAbleToShowPageAuthors,
 } from '~/stores/ui';
 } from '~/stores/ui';
 
 
 import CreateTemplateModal from '../CreateTemplateModal';
 import CreateTemplateModal from '../CreateTemplateModal';
@@ -216,7 +216,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
 
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
   const { data: isAbleToShowTagLabel } = useIsAbleToShowTagLabel();
   const { data: isAbleToShowTagLabel } = useIsAbleToShowTagLabel();
-  const { data: isAbleToShowPageEditorModeManager } = useIsAbleToShowPageEditorModeManager();
+  const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
   const { data: isAbleToShowPageAuthors } = useIsAbleToShowPageAuthors();
   const { data: isAbleToShowPageAuthors } = useIsAbleToShowPageAuthors();
 
 
   const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
   const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
@@ -380,7 +380,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
                 ) }
                 ) }
               </div>
               </div>
             ) }
             ) }
-            {isAbleToShowPageEditorModeManager && (
+            {isAbleToChangeEditorMode && (
               <PageEditorModeManager
               <PageEditorModeManager
                 onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
                 onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
                 isBtnDisabled={isGuestUser}
                 isBtnDisabled={isGuestUser}

+ 0 - 9
packages/app/src/components/Navbar/GrowiSubNavigation.module.scss

@@ -172,12 +172,3 @@
     }
     }
   }
   }
 }
 }
-
-/*
- * shadow
- */
-.grw-subnav-append-shadow-container {
-  .grw-subnav {
-    box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
-  }
-}

+ 0 - 141
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx

@@ -1,141 +0,0 @@
-import React, {
-  useMemo, useState, useRef, useEffect, useCallback,
-} from 'react';
-
-import PropTypes from 'prop-types';
-import StickyEvents from 'sticky-events';
-import { debounce } from 'throttle-debounce';
-
-import { useSidebarCollapsed } from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import { useSWRxCurrentPage } from '~/stores/page';
-
-import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
-
-import styles from './GrowiSubNavigationSwitcher.module.scss';
-
-const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
-
-
-/**
- * Subnavigation
- *
- * needs:
- *   #grw-subnav-fixed-container element
- *   #grw-subnav-sticky-trigger element
- *
- * @param {object} props
- */
-const GrowiSubNavigationSwitcher = (props) => {
-
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { data: isSidebarCollapsed } = useSidebarCollapsed();
-
-  const [isVisible, setVisible] = useState(false);
-  const [width, setWidth] = useState(null);
-
-  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' }), []);
-
-  const initWidth = useCallback(() => {
-    const instance = fixedContainerRef.current;
-
-    if (instance == null || instance.parentNode == null) {
-      return;
-    }
-
-    // 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);
-  }, []);
-
-  // // setup effect by sticky event
-  // useEffect(() => {
-  //   // sticky
-  //   // See: https://github.com/ryanwalters/sticky-events
-  //   const { stickySelector } = stickyEvents;
-  //   const elem = document.querySelector(stickySelector);
-  //   elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-  //   // return clean up handler
-  //   return () => {
-  //     elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-  //   };
-  // }, [stickyChangeHandler, stickyEvents]);
-
-  // update width when sidebar collapsing changed
-  useEffect(() => {
-    if (isSidebarCollapsed != null) {
-      setTimeout(initWidth, 300);
-    }
-  }, [isSidebarCollapsed, initWidth]);
-
-  // // initialize
-  // useEffect(() => {
-  //   initWidth();
-
-  //   // check sticky state several times
-  //   setTimeout(initVisible, 100);
-  //   setTimeout(initVisible, 300);
-  //   setTimeout(initVisible, 2000);
-
-  // }, [initWidth, initVisible]);
-
-  // ${styles['grw-subnav-switcher']}
-
-  return (
-    <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : '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`}
-        ref={fixedContainerRef}
-        style={{ width }}
-      >
-        <GrowiContextualSubNavigation currentPage isCompactMode isLinkSharingDisabled />
-      </div>
-    </div>
-  );
-};
-
-GrowiSubNavigationSwitcher.propTypes = {
-  isLinkSharingDisabled: PropTypes.bool,
-};
-
-export default GrowiSubNavigationSwitcher;

+ 9 - 0
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss

@@ -19,6 +19,15 @@ $easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
     .grw-subnav-fixed-container {
     .grw-subnav-fixed-container {
       transition: transform 150ms $easeInOutCubic;
       transition: transform 150ms $easeInOutCubic;
     }
     }
+
+    /*
+    * shadow
+    */
+    .grw-subnav-append-shadow-container {
+      .grw-subnav {
+        box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
+      }
+    }
   }
   }
 
 
   &:global {
   &:global {

+ 103 - 0
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -0,0 +1,103 @@
+import React, {
+  useState, useRef, useEffect, useCallback,
+} from 'react';
+
+import StickyEvents from 'sticky-events';
+import { debounce } from 'throttle-debounce';
+
+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');
+
+/**
+ * GrowiSubNavigation
+ *
+ * needs:
+ *   #grw-subnav-fixed-container element
+ *   #grw-subnav-sticky-trigger element
+ */
+export const GrowiSubNavigationSwitcher = (): JSX.Element => {
+
+  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.
+  // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
+  const fixedContainerRef = useRef<HTMLDivElement>(null);
+
+  const initWidth = useCallback(() => {
+    if (fixedContainerRef.current && fixedContainerRef.current.parentElement) {
+      // get parent elements width
+      const { clientWidth } = fixedContainerRef.current.parentElement;
+      setWidth(clientWidth);
+    }
+  }, []);
+
+  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);
+    window.addEventListener('resize', resizeHandler);
+
+    // return clean up handler
+    return () => {
+      window.removeEventListener('resize', resizeHandler);
+    };
+  }, [initWidth]);
+
+  // update width when sidebar collapsing changed
+  useEffect(() => {
+    if (isSidebarCollapsed != null) {
+      setTimeout(initWidth, 300);
+    }
+  }, [isSidebarCollapsed, initWidth]);
+
+  // initialize width
+  useEffect(() => {
+    initWidth();
+  }, [initWidth]);
+
+  if (currentPage == null) {
+    return <></>;
+  }
+
+  return (
+    <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : '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`}
+        ref={fixedContainerRef}
+        style={{ width }}
+      >
+        <GrowiContextualSubNavigation currentPage={currentPage} isCompactMode isLinkSharingDisabled />
+      </div>
+    </div>
+  );
+};

+ 8 - 0
packages/app/src/components/SearchPage/SearchResultContent.module.scss

@@ -0,0 +1,8 @@
+/*
+* shadow
+*/
+.grw-subnav-append-shadow-container :global {
+  .grw-subnav {
+    box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
+  }
+}

+ 3 - 1
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -29,6 +29,8 @@ import { ROOT_ELEM_ID as RevisionLoaderRoomElemId, RevisionLoaderProps } from '.
 import { ROOT_ELEM_ID as PageCommentRootElemId, PageCommentProps } from '../PageComment';
 import { ROOT_ELEM_ID as PageCommentRootElemId, PageCommentProps } from '../PageComment';
 import { PageContentFooterProps } from '../PageContentFooter';
 import { PageContentFooterProps } from '../PageContentFooter';
 
 
+import styles from './SearchResultContent.module.scss';
+
 
 
 const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
 const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
 const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
 const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
@@ -242,7 +244,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
 
   return (
   return (
     <div key={page._id} data-testid="search-result-content" className="search-result-content grw-page-path-text-muted-container d-flex flex-column">
     <div key={page._id} data-testid="search-result-content" className="search-result-content grw-page-path-text-muted-container d-flex flex-column">
-      <div className="grw-subnav-append-shadow-container">
+      <div className={`${styles['grw-subnav-append-shadow-container']} grw-subnav-append-shadow-container`}>
         <GrowiSubNavigation
         <GrowiSubNavigation
           pagePath={page.path}
           pagePath={page.path}
           pageId={page._id}
           pageId={page._id}

+ 20 - 20
packages/app/src/components/StickyStretchableScroller.tsx

@@ -69,26 +69,26 @@ export const StickyStretchableScroller: FC<StickyStretchableScrollerProps> = (pr
 
 
   const resetScrollbarDebounced = useMemo(() => debounce(100, resetScrollbar), [resetScrollbar]);
   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]);
+  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]);
 
 
   // setup effect by resizing event
   // setup effect by resizing event
   useEffect(() => {
   useEffect(() => {

+ 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 ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
 const PageSideContents = dynamic<PageSideContentsProps>(() => import('../components/PageSideContents').then(mod => mod.PageSideContents), { 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')
 const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
 const DrawioModal = dynamic(() => import('../components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });
 const DrawioModal = dynamic(() => import('../components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });

+ 2 - 2
packages/app/src/stores/ui.tsx

@@ -465,8 +465,8 @@ export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
   );
   );
 };
 };
 
 
-export const useIsAbleToShowPageEditorModeManager = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToShowPageEditorModeManager';
+export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
+  const key = 'isAbleToChangeEditorMode';
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
 
 

+ 0 - 32
packages/app/src/styles/_layout.scss

@@ -71,38 +71,6 @@ body.not-found-page .grw-container-convertible {
   top: calc(100px + 4px + 20px);
   top: calc(100px + 4px + 20px);
 }
 }
 
 
-.grw-fab {
-  position: fixed;
-  right: 1.5rem;
-  bottom: 3rem;
-  z-index: bs.$zindex-fixed;
-
-  transition: all 200ms linear;
-
-  .btn-create-page {
-    width: 60px;
-    height: 60px;
-    font-size: 24px;
-
-    box-shadow: 2px 3px 6px #0000005d;
-    svg {
-      width: 28px;
-      height: 28px;
-    }
-  }
-
-  .btn-scroll-to-top {
-    width: 40px;
-    height: 40px;
-
-    opacity: 0.4;
-    svg {
-      width: 18px;
-      height: 18px;
-    }
-  }
-}
-
 // printable style
 // printable style
 @media print {
 @media print {
   body {
   body {

+ 3 - 1
packages/app/src/styles/theme/_apply-colors.scss

@@ -664,7 +664,9 @@ mark.rbt-highlight-text {
 
 
 .grw-fab {
 .grw-fab {
   .btn-create-page {
   .btn-create-page {
-    fill: color-yiq($primary);
+    svg {
+      fill: color-yiq($primary);
+    }
   }
   }
 
 
   .btn-scroll-to-top {
   .btn-scroll-to-top {

+ 2 - 2
packages/app/test/cypress/integration/20-basic-features/20-basic-features--access-to-page.spec.ts

@@ -29,8 +29,8 @@ context('Access to page', () => {
     // https://redmine.weseek.co.jp/issues/111384
     // https://redmine.weseek.co.jp/issues/111384
     // cy.get('.toc-link').should('be.visible');
     // cy.get('.toc-link').should('be.visible');
 
 
-    // hide fab // disable fab for sticky-events warning
-    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab
+    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
 
     // remove animation for screenshot
     // remove animation for screenshot
     // remove 'blink' class because ::after element cannot be operated
     // remove 'blink' class because ::after element cannot be operated

+ 2 - 2
packages/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.spec.ts

@@ -14,8 +14,8 @@ context('Access to page by guest', () => {
     cy.visit('/Sandbox#Headers');
     cy.visit('/Sandbox#Headers');
     cy.waitUntilSkeletonDisappear();
     cy.waitUntilSkeletonDisappear();
 
 
-    // hide fab // disable fab for sticky-events warning
-    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab
+    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
 
     // remove animation for screenshot
     // remove animation for screenshot
     // remove 'blink' class because ::after element cannot be operated
     // remove 'blink' class because ::after element cannot be operated

+ 2 - 2
packages/app/test/cypress/integration/60-home/60-home--home.spec.ts

@@ -46,8 +46,8 @@ context('Access User settings', () => {
     });
     });
     cy.visit('/me');
     cy.visit('/me');
     cy.collapseSidebar(true, true);
     cy.collapseSidebar(true, true);
-    // hide fab // disable fab for sticky-events warning
-    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab
+    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
   });
   });
 
 
   it('Access User information', () => {
   it('Access User information', () => {

+ 3 - 1
packages/preset-themes/src/styles/theme/_apply-colors.scss

@@ -655,7 +655,9 @@ mark.rbt-highlight-text {
 
 
 .grw-fab {
 .grw-fab {
   .btn-create-page {
   .btn-create-page {
-    fill: color-yiq($primary);
+    svg {
+      fill: color-yiq($primary);
+    }
   }
   }
 
 
   .btn-scroll-to-top {
   .btn-scroll-to-top {