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

Merge pull request #7965 from weseek/support/reorganize-global-navigation

support: Reorganize global navigation
Yuki Takei 2 лет назад
Родитель
Сommit
74a586f364
35 измененных файлов с 271 добавлено и 632 удалено
  1. 0 63
      apps/app/src/components/Fab.module.scss
  2. 0 119
      apps/app/src/components/Fab.tsx
  3. 1 1
      apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx
  4. 0 2
      apps/app/src/components/Layout/AdminLayout.tsx
  5. 2 11
      apps/app/src/components/Layout/BasicLayout.tsx
  6. 5 5
      apps/app/src/components/Layout/SearchResultLayout.module.scss
  7. 0 7
      apps/app/src/components/Layout/ShareLinkLayout.tsx
  8. 5 3
      apps/app/src/components/Navbar/DrawerToggler.tsx
  9. 1 20
      apps/app/src/components/Navbar/GrowiNavbar.module.scss
  10. 3 47
      apps/app/src/components/Navbar/GrowiNavbar.tsx
  11. 0 108
      apps/app/src/components/Navbar/PersonalDropdown.jsx
  12. 5 5
      apps/app/src/components/Sidebar/AppearanceModeDropdown.tsx
  13. 1 2
      apps/app/src/components/Sidebar/PageTree/ItemsTree.module.scss
  14. 105 0
      apps/app/src/components/Sidebar/PersonalDropdown.tsx
  15. 24 61
      apps/app/src/components/Sidebar/Sidebar.module.scss
  16. 12 14
      apps/app/src/components/Sidebar/Sidebar.tsx
  17. 18 0
      apps/app/src/components/Sidebar/SidebarBrandLogo.tsx
  18. 26 0
      apps/app/src/components/Sidebar/SidebarNav.module.scss
  19. 30 1
      apps/app/src/components/Sidebar/SidebarNav.tsx
  20. 1 0
      apps/app/src/components/Sidebar/index.ts
  21. 2 3
      apps/app/src/components/UsersHomepageFooter.module.scss
  22. 0 7
      apps/app/src/pages/[[...path]].page.tsx
  23. 6 12
      apps/app/src/styles/_editor.scss
  24. 2 1
      apps/app/src/styles/_mixins.scss
  25. 0 12
      apps/app/src/styles/_variables.scss
  26. 0 12
      apps/app/src/styles/theme/apply-colors.scss
  27. 2 5
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts
  28. 3 49
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--sticky-features.cy.ts
  29. 0 3
      apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.cy.ts
  30. 2 30
      apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--sticky-for-guest.cy.ts
  31. 11 11
      apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts
  32. 0 2
      apps/app/test/cypress/e2e/60-home/60-home--home.cy.ts
  33. 2 2
      apps/app/test/cypress/support/commands.ts
  34. 0 12
      packages/preset-themes/src/styles/_variables.scss
  35. 2 2
      packages/ui/src/utils/browser-utils.ts

+ 0 - 63
apps/app/src/components/Fab.module.scss

@@ -1,63 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-.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
-  // https://stackoverflow.com/a/57667536
-  .fadeInUp {
-    & :local {
-      animation: fab-fadeinup 0.5s ease 0s;
-    }
-  }
-  .fadeOut {
-    & :local {
-      animation: fab-fadeout 0.5s ease 0s forwards;
-    }
-  }
-}
-
-@keyframes fab-fadeinup {
-  0% {
-    opacity: 0;
-    transform: translateY(100px);
-  }
-  100% {
-    opacity: 1;
-    transform: translateY(0px);
-  }
-}
-
-@keyframes fab-fadeout {
-  100% {
-    opacity: 0
-  }
-}

+ 0 - 119
apps/app/src/components/Fab.tsx

@@ -1,119 +0,0 @@
-import React, {
-  useState, useCallback, useRef, useEffect,
-} from 'react';
-
-import { animateScroll } from 'react-scroll';
-import { useRipple } from 'react-use-ripple';
-
-import { useSticky } from '~/client/services/side-effects/use-sticky';
-import { usePageCreateModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
-import { useIsAbleToChangeEditorMode } from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import { CreatePageIcon } from './Icons/CreatePageIcon';
-import { ReturnTopIcon } from './Icons/ReturnTopIcon';
-
-import styles from './Fab.module.scss';
-
-const logger = loggerFactory('growi:cli:Fab');
-
-export const Fab = (): JSX.Element => {
-
-  const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
-  const { data: currentPath = '' } = useCurrentPagePath();
-  const { open: openCreateModal } = usePageCreateModal();
-
-  const [animateClasses, setAnimateClasses] = useState<string>('invisible');
-  const [buttonClasses, setButtonClasses] = useState<string>('');
-  const [isStickyApplied, setIsStickyApplied] = useState(false);
-
-  // ripple
-  const createBtnRef = useRef(null);
-  useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
-
-  // Get sticky status
-  const isSticky = useSticky('#grw-fav-sticky-trigger');
-
-  // check if isSticky is already initialized then save it in isStickyApplied state
-  useEffect(() => {
-    if (isSticky) {
-      setIsStickyApplied(true);
-    }
-  }, [isSticky]);
-
-  // 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(() => {
-    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 (
-      <div
-        className={`rounded-circle position-absolute ${animateClasses}`}
-        style={{ bottom: '2.3rem', right: '4rem' }}
-        data-testid="grw-fab-page-create-button"
-      >
-        <button
-          type="button"
-          className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 ${buttonClasses}`}
-          ref={createBtnRef}
-          onClick={currentPath != null
-            ? () => openCreateModal(currentPath)
-            : undefined}
-        >
-          <CreatePageIcon />
-        </button>
-      </div>
-    );
-  }, [animateClasses, buttonClasses, currentPath, openCreateModal]);
-
-  const ScrollToTopButton = useCallback(() => {
-    const clickHandler = () => {
-      animateScroll.scrollToTop({ duration: 200 });
-    };
-
-    return (
-      <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: 0, right: 0 }} data-testid="grw-fab-return-to-top">
-        <button
-          type="button"
-          className={`btn btn-light btn-scroll-to-top rounded-circle p-0 ${buttonClasses}`}
-          onClick={clickHandler}
-        >
-          <ReturnTopIcon />
-        </button>
-      </div>
-    );
-  }, [animateClasses, buttonClasses]);
-
-  if (currentPath == null) {
-    return <></>;
-  }
-
-  return (
-    <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab-container">
-      {isAbleToChangeEditorMode && <PageCreateButton />}
-      <ScrollToTopButton />
-    </div>
-  );
-
-};

+ 1 - 1
apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -82,7 +82,7 @@ export const InAppNotificationDropdown = (): JSX.Element => {
   }
 
   return (
-    <Dropdown className="notification-wrapper grw-notification-dropdown" isOpen={isOpen} toggle={toggleDropdownHandler}>
+    <Dropdown className="notification-wrapper grw-notification-dropdown" isOpen={isOpen} toggle={toggleDropdownHandler} direction="right">
       <DropdownToggle className="px-3 nav-link border-0 bg-transparent" innerRef={buttonRef}>
         <i className="icon-bell" /> {badge}
       </DropdownToggle>

+ 0 - 2
apps/app/src/components/Layout/AdminLayout.tsx

@@ -3,7 +3,6 @@ import React, { ReactNode } from 'react';
 import dynamic from 'next/dynamic';
 
 import { AdminNavigation } from '../Admin/Common/AdminNavigation';
-import { GrowiNavbar } from '../Navbar/GrowiNavbar';
 
 import { RawLayout } from './RawLayout';
 
@@ -28,7 +27,6 @@ const AdminLayout = ({
   return (
     <RawLayout>
       <div className={`admin-page ${styles['admin-page']}`}>
-        <GrowiNavbar isGlobalSearchHidden={true} />
 
         <header className="py-0 container-fluid">
           <h1 className="title px-3">{componentTitle}</h1>

+ 2 - 11
apps/app/src/components/Layout/BasicLayout.tsx

@@ -4,15 +4,13 @@ import dynamic from 'next/dynamic';
 import { DndProvider } from 'react-dnd';
 import { HTML5Backend } from 'react-dnd-html5-backend';
 
-import { GrowiNavbar } from '../Navbar/GrowiNavbar';
-import Sidebar from '../Sidebar';
+import { Sidebar } from '../Sidebar';
 
 import { RawLayout } from './RawLayout';
 
 const AlertSiteUrlUndefined = dynamic(() => import('../AlertSiteUrlUndefined').then(mod => mod.AlertSiteUrlUndefined), { ssr: false });
 const DeleteAttachmentModal = dynamic(() => import('../PageAttachment/DeleteAttachmentModal').then(mod => mod.DeleteAttachmentModal), { ssr: false });
 const HotkeysManager = dynamic(() => import('../Hotkeys/HotkeysManager'), { ssr: false });
-// const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
 const GrowiNavbarBottom = dynamic(() => import('../Navbar/GrowiNavbarBottom').then(mod => mod.GrowiNavbarBottom), { ssr: false });
 const ShortcutsModal = dynamic(() => import('../ShortcutsModal'), { ssr: false });
 const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
@@ -25,8 +23,6 @@ const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false
 const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
 const PageAccessoriesModal = dynamic(() => import('../PageAccessoriesModal').then(mod => mod.PageAccessoriesModal), { ssr: false });
 const DeleteBookmarkFolderModal = dynamic(() => import('../DeleteBookmarkFolderModal').then(mod => mod.DeleteBookmarkFolderModal), { ssr: false });
-// Fab
-const Fab = dynamic(() => import('../Fab').then(mod => mod.Fab), { ssr: false });
 
 
 type Props = {
@@ -38,12 +34,9 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
   return (
     <RawLayout className={className ?? ''}>
       <DndProvider backend={HTML5Backend}>
-        <GrowiNavbar />
 
         <div className="page-wrapper d-flex d-print-block">
-          <div className="grw-sidebar-wrapper">
-            <Sidebar />
-          </div>
+          <Sidebar />
 
           <div className="flex-fill mw-0">
             <AlertSiteUrlUndefined />
@@ -66,8 +59,6 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
       <PagePresentationModal />
       <HotkeysManager />
 
-      <Fab />
-
       <ShortcutsModal />
       <SystemVersion showShortcutsButton />
     </RawLayout>

+ 5 - 5
apps/app/src/components/Layout/SearchResultLayout.module.scss

@@ -13,12 +13,12 @@
   }
   .search-result-list {
     .search-result-list-scroll {
-      // subtract the height of GrowiNavbar + (SearchControl component + other factors)
-      height: calc(100vh - ((var.$grw-navbar-height + var.$grw-navbar-border-width) + 110px));
+      // subtract the height of (SearchControl component + other factors)
+      height: calc(100vh - 110px);
       overflow-y: scroll;
 
       @include bs.media-breakpoint-down(sm) {
-        height: calc(100vh - ((var.$grw-navbar-height + var.$grw-navbar-border-width + var.$grw-navbar-bottom-height) + 123px));
+        height: calc(100vh - (var.$grw-navbar-bottom-height + 123px));
       }
     }
 
@@ -54,7 +54,7 @@
     }
 
     .search-result-content {
-      height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width));
+      height: 100vh;
 
       > h2 {
         margin-right: 10px;
@@ -85,7 +85,7 @@
 // style to apply when displaying search page
 .on-search :global {
   // set sidebar height shown in search page
-  $search-page-sidebar-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width));
+  $search-page-sidebar-height: 100vh;
 
   .grw-sidebar {
     height: $search-page-sidebar-height;

+ 0 - 7
apps/app/src/components/Layout/ShareLinkLayout.tsx

@@ -3,7 +3,6 @@ import React, { ReactNode } from 'react';
 import dynamic from 'next/dynamic';
 
 import { useEditorModeClassName } from '../../client/services/layout';
-import { GrowiNavbar } from '../Navbar/GrowiNavbar';
 
 import { RawLayout } from './RawLayout';
 
@@ -12,9 +11,6 @@ const GrowiNavbarBottom = dynamic(() => import('../Navbar/GrowiNavbarBottom').th
 const ShortcutsModal = dynamic(() => import('../ShortcutsModal'), { ssr: false });
 const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
 
-// Fab
-const Fab = dynamic(() => import('../Fab').then(mod => mod.Fab), { ssr: false });
-
 
 type Props = {
   children?: ReactNode
@@ -25,7 +21,6 @@ export const ShareLinkLayout = ({ children }: Props): JSX.Element => {
 
   return (
     <RawLayout className={className}>
-      <GrowiNavbar isGlobalSearchHidden={true} />
 
       <div className="page-wrapper d-flex d-print-block">
         <div className="flex-fill mw-0">
@@ -35,8 +30,6 @@ export const ShareLinkLayout = ({ children }: Props): JSX.Element => {
 
       <GrowiNavbarBottom />
 
-      <Fab />
-
       <ShortcutsModal />
       <PageCreateModal />
       <SystemVersion showShortcutsButton />

+ 5 - 3
apps/app/src/components/Navbar/DrawerToggler.tsx

@@ -1,4 +1,4 @@
-import React, { FC } from 'react';
+import React from 'react';
 
 import { useDrawerOpened } from '~/stores/ui';
 
@@ -6,11 +6,13 @@ type Props = {
   iconClass?: string,
 }
 
-const DrawerToggler: FC<Props> = (props: Props) => {
+const DrawerToggler = (props: Props): JSX.Element => {
 
   const { data: isOpened, mutate } = useDrawerOpened();
 
-  const iconClass = props.iconClass || 'icon-menu';
+  const iconClass = props.iconClass ?? isOpened
+    ? 'icon-arrow-left'
+    : 'icon-arrow-right';
 
   return (
     <button

+ 1 - 20
apps/app/src/components/Navbar/GrowiNavbar.module.scss

@@ -1,17 +1,9 @@
 @use '~/styles/variables' as var;
-@use '@growi/core/scss/bootstrap/init' as bs;
+@use '~/styles/bootstrap/init' as bs;
 @use '~/styles/mixins';
 
 .grw-navbar :global {
 
-  .grw-logo {
-    svg {
-      width: var.$grw-logo-width;
-      height: var.$grw-navbar-height;
-      padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
-    }
-  }
-
   .confidential {
     font-weight: bold;
   }
@@ -70,17 +62,6 @@
     background: rgba(0, 0, 0, 0.2);
   }
 
-  .grw-apperance-mode-dropdown,
-  .grw-personal-dropdown {
-    .dropdown-menu {
-      min-width: 15rem;
-
-      .grw-icon-container svg {
-        width: 18px;
-        height: 18px;
-      }
-    }
-  }
   .grw-email-sm {
     font-size: 0.75em;
   }

+ 3 - 47
apps/app/src/components/Navbar/GrowiNavbar.tsx

@@ -4,28 +4,21 @@ import React, {
 
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
-import Link from 'next/link';
 import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 
 import {
-  useIsSearchPage, useIsGuestUser, useIsReadOnlyUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useIsDefaultLogo,
+  useIsSearchPage, useIsGuestUser, useIsReadOnlyUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
 } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 
-import GrowiLogo from '../Icons/GrowiLogo';
 
 import { GlobalSearchProps } from './GlobalSearch';
 
 import styles from './GrowiNavbar.module.scss';
 
-const PersonalDropdown = dynamic(() => import('./PersonalDropdown'), { ssr: false });
-const InAppNotificationDropdown = dynamic(() => import('../InAppNotification/InAppNotificationDropdown')
-  .then(mod => mod.InAppNotificationDropdown), { ssr: false });
-const AppearanceModeDropdown = dynamic(() => import('./AppearanceModeDropdown').then(mod => mod.AppearanceModeDropdown), { ssr: false });
-
 const NavbarRight = memo((): JSX.Element => {
   const { t } = useTranslation();
 
@@ -44,10 +37,6 @@ const NavbarRight = memo((): JSX.Element => {
   const authenticatedNavItem = useMemo(() => {
     return (
       <>
-        <li className="nav-item">
-          <InAppNotificationDropdown />
-        </li>
-
         {!isReadOnlyUser
           && <li className="nav-item d-none d-md-block">
             <button
@@ -62,28 +51,17 @@ const NavbarRight = memo((): JSX.Element => {
             </button>
           </li>
         }
-
-        <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
-        </li>
-
-        <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
-          <PersonalDropdown />
-        </li>
       </>
     );
-  }, [isReadOnlyUser, t, isAuthenticated, openCreateModal, currentPagePath]);
+  }, [isReadOnlyUser, t, openCreateModal, currentPagePath]);
 
   const notAuthenticatedNavItem = useMemo(() => {
     return (
       <>
-        <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
-        </li>
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>
       </>
     );
-  }, [isAuthenticated]);
+  }, []);
 
   return (
     <>
@@ -121,21 +99,6 @@ const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX
 });
 Confidential.displayName = 'Confidential';
 
-interface NavbarLogoProps {
-  isDefaultLogo?: boolean
-}
-
-const GrowiNavbarLogo: FC<NavbarLogoProps> = memo((props: NavbarLogoProps) => {
-  const { isDefaultLogo } = props;
-
-  return isDefaultLogo
-    ? <GrowiLogo />
-    // eslint-disable-next-line @next/next/no-img-element
-    : (<img src='/attachment/brand-logo' alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />);
-});
-
-GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';
-
 type Props = {
   isGlobalSearchHidden?: boolean
 }
@@ -151,16 +114,9 @@ export const GrowiNavbar = (props: Props): JSX.Element => {
   const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
   const { data: isSearchPage } = useIsSearchPage();
-  const { data: isDefaultLogo } = useIsDefaultLogo();
 
   return (
     <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
-      {/* Brand Logo  */}
-      <div className="navbar-brand mr-0">
-        <Link href="/" className="grw-logo d-block">
-          <GrowiNavbarLogo isDefaultLogo={isDefaultLogo} />
-        </Link>
-      </div>
 
       <div className="grw-app-title d-none d-md-block">
         {appTitle}

+ 0 - 108
apps/app/src/components/Navbar/PersonalDropdown.jsx

@@ -1,108 +0,0 @@
-import { useRef, useState } from 'react';
-
-import { pagePathUtils } from '@growi/core/dist/utils';
-import { UserPicture } from '@growi/ui/dist/components';
-import { useTranslation } from 'next-i18next';
-import dynamic from 'next/dynamic';
-import Link from 'next/link';
-import { useRipple } from 'react-use-ripple';
-
-import { apiv3Post } from '~/client/util/apiv3-client';
-import { toastError } from '~/client/util/toastr';
-import { useCurrentUser } from '~/stores/context';
-
-const ProactiveQuestionnaireModal = dynamic(() => import('~/features/questionnaire/client/components/ProactiveQuestionnaireModal'), { ssr: false });
-
-const PersonalDropdown = () => {
-  const { t } = useTranslation('commons');
-  const { data: currentUser } = useCurrentUser();
-
-  const [isQuestionnaireModalOpen, setQuestionnaireModalOpen] = useState(false);
-
-  // ripple
-  const buttonRef = useRef(null);
-  useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
-
-  if (currentUser == null) {
-    return <div className="text-muted text-center mb-5">
-      <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
-    </div>;
-  }
-
-  const logoutHandler = async() => {
-    try {
-      await apiv3Post('/logout');
-      window.location.reload();
-    }
-    catch (err) {
-      toastError(err);
-    }
-  };
-
-  return (
-    <>
-      {/* Button */}
-      {/* remove .dropdown-toggle for hide caret */}
-      {/* See https://stackoverflow.com/a/44577512/13183572 */}
-      <button className="bg-transparent border-0 nav-link" type="button" ref={buttonRef} data-toggle="dropdown" data-testid="personal-dropdown-button">
-        <UserPicture user={currentUser} noLink noTooltip /><span className="ml-1 d-none d-lg-inline-block">&nbsp;{currentUser.name}</span>
-      </button>
-
-      {/* Menu */}
-      <div className="dropdown-menu dropdown-menu-right" data-testid="personal-dropdown-menu">
-
-        <div className="px-4 pt-3 pb-2 text-center">
-          <UserPicture user={currentUser} size="lg" noLink noTooltip />
-
-          <h5 className="mt-2">
-            {currentUser.name}
-          </h5>
-
-          <div className="my-2">
-            <i className="icon-user icon-fw"></i>{currentUser.username}<br />
-            <i className="icon-envelope icon-fw"></i><span className="grw-email-sm">{currentUser.email}</span>
-          </div>
-
-          <div className="btn-group btn-block mt-2" role="group">
-            <Link
-              href={pagePathUtils.userHomepagePath(currentUser)}
-              className="btn btn-sm btn-outline-secondary col"
-              data-testid="grw-personal-dropdown-menu-user-home"
-            >
-              <i className="icon-fw icon-home"></i>{t('personal_dropdown.home')}
-            </Link>
-            <Link
-              href="/me"
-              className="btn btn-sm btn-outline-secondary col"
-              data-testid="grw-personal-dropdown-menu-user-settings"
-            >
-              <i className="icon-fw icon-wrench"></i>{t('personal_dropdown.settings')}
-            </Link>
-          </div>
-        </div>
-
-        <div className="dropdown-divider"></div>
-
-        <button
-          data-testid="grw-proactive-questionnaire-modal-toggle-btn"
-          type="button"
-          className="dropdown-item"
-          onClick={() => setQuestionnaireModalOpen(true)}>
-          <i className="icon-fw icon-pencil"></i>{t('personal_dropdown.feedback')}
-        </button>
-
-        <div className="dropdown-divider"></div>
-
-        <button type="button" className="dropdown-item" onClick={logoutHandler}>
-          <i className="icon-fw icon-power"></i>{t('Sign out')}
-        </button>
-      </div>
-
-      <ProactiveQuestionnaireModal isOpen={isQuestionnaireModalOpen} onClose={() => setQuestionnaireModalOpen(false)} />
-
-    </>
-  );
-
-};
-
-export default PersonalDropdown;

+ 5 - 5
apps/app/src/components/Navbar/AppearanceModeDropdown.tsx → apps/app/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -101,16 +101,16 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
   }, [isPreferDrawerMode, isPreferDrawerModeOnEdit, preferDrawerModeSwitchModifiedHandler, t]);
 
   return (
-    <>
+    <div className="dropend">
       {/* setting button */}
       {/* remove .dropdown-toggle for hide caret */}
       {/* See https://stackoverflow.com/a/44577512/13183572 */}
-      <button className="bg-transparent border-0 nav-link" type="button" data-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
-        <i className="icon-settings"></i>
+      <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
+        <i className="material-icons">settings</i>
       </button>
 
       {/* dropdown */}
-      <div className="dropdown-menu dropdown-menu-right">
+      <div className="dropdown-menu">
 
         {/* sidebar mode */}
         {renderSidebarModeSwitch(false)}
@@ -170,7 +170,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
 
       </div>
 
-    </>
+    </div>
   );
 
 };

+ 1 - 2
apps/app/src/components/Sidebar/PageTree/ItemsTree.module.scss

@@ -1,4 +1,3 @@
-@use '~/styles/variables' as var;
 @use '~/styles/mixins' as *;
 $grw-sidebar-content-header-height: 58px;
 $grw-sidebar-content-footer-height: 50px;
@@ -18,7 +17,7 @@ $grw-pagetree-item-container-height: 40px;
   }
 
   :global {
-    min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
+    min-height: calc(100vh - ($grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
 
     .btn-page-item-control {
       .icon-plus::before {

+ 105 - 0
apps/app/src/components/Sidebar/PersonalDropdown.tsx

@@ -0,0 +1,105 @@
+import { useState } from 'react';
+
+import { pagePathUtils } from '@growi/core/dist/utils';
+import { UserPicture } from '@growi/ui/dist/components';
+import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
+import Link from 'next/link';
+
+import { apiv3Post } from '~/client/util/apiv3-client';
+import { toastError } from '~/client/util/toastr';
+import { useCurrentUser } from '~/stores/context';
+
+const ProactiveQuestionnaireModal = dynamic(() => import('~/features/questionnaire/client/components/ProactiveQuestionnaireModal'), { ssr: false });
+
+export const PersonalDropdown = (): JSX.Element => {
+  const { t } = useTranslation('commons');
+  const { data: currentUser } = useCurrentUser();
+
+  const [isQuestionnaireModalOpen, setQuestionnaireModalOpen] = useState(false);
+
+  if (currentUser == null) {
+    return <div className="text-muted text-center mb-5">
+      <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
+    </div>;
+  }
+
+  const logoutHandler = async() => {
+    try {
+      await apiv3Post('/logout');
+      window.location.reload();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
+
+  return (
+    <>
+      <div className="dropend">
+        {/* Button */}
+        {/* remove .dropdown-toggle for hide caret */}
+        {/* See https://stackoverflow.com/a/44577512/13183572 */}
+        <button type="button"
+          className="btn btn-primary"
+          data-bs-toggle="dropdown" data-testid="personal-dropdown-button" aria-expanded="false"
+        >
+          <UserPicture user={currentUser} noLink noTooltip /><span className="ml-1 d-none d-lg-inline-block">&nbsp;{currentUser.name}</span>
+        </button>
+
+        {/* Menu */}
+        <div className="dropdown-menu" data-testid="personal-dropdown-menu">
+
+          <div className="px-4 pt-3 pb-2 text-center">
+            <UserPicture user={currentUser} size="lg" noLink noTooltip />
+
+            <h5 className="mt-2">
+              {currentUser.name}
+            </h5>
+
+            <div className="my-2">
+              <i className="icon-user icon-fw"></i>{currentUser.username}<br />
+              <i className="icon-envelope icon-fw"></i><span className="grw-email-sm">{currentUser.email}</span>
+            </div>
+
+            <div className="btn-group btn-block mt-2" role="group">
+              <Link
+                href={pagePathUtils.userHomepagePath(currentUser)}
+                className="btn btn-sm btn-outline-secondary col"
+                data-testid="grw-personal-dropdown-menu-user-home"
+              >
+                <i className="icon-fw icon-home"></i>{t('personal_dropdown.home')}
+              </Link>
+              <Link
+                href="/me"
+                className="btn btn-sm btn-outline-secondary col"
+                data-testid="grw-personal-dropdown-menu-user-settings"
+              >
+                <i className="icon-fw icon-wrench"></i>{t('personal_dropdown.settings')}
+              </Link>
+            </div>
+          </div>
+
+          <div className="dropdown-divider"></div>
+
+          <button
+            data-testid="grw-proactive-questionnaire-modal-toggle-btn"
+            type="button"
+            className="dropdown-item"
+            onClick={() => setQuestionnaireModalOpen(true)}>
+            <i className="icon-fw icon-pencil"></i>{t('personal_dropdown.feedback')}
+          </button>
+
+          <div className="dropdown-divider"></div>
+
+          <button type="button" className="dropdown-item" onClick={logoutHandler}>
+            <i className="icon-fw icon-power"></i>{t('Sign out')}
+          </button>
+        </div>
+      </div>
+
+      <ProactiveQuestionnaireModal isOpen={isQuestionnaireModalOpen} onClose={() => setQuestionnaireModalOpen(false)} />
+    </>
+  );
+
+};

+ 24 - 61
apps/app/src/components/Sidebar.module.scss → apps/app/src/components/Sidebar/Sidebar.module.scss

@@ -1,21 +1,19 @@
-@use '~/styles/variables' as var;
 @use '~/styles/mixins';
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 .grw-sidebar :global {
   // sticky
   position: sticky;
-  top: var.$grw-navbar-border-width;
+  top: 0;
 
   // set the max value that should be taken when sticky
-  height: calc(100vh - var.$grw-navbar-border-width);
+  height: 100vh;
+
 
-  // override @atlaskit/navigation-next styles
-  $navbar-total-height: var.$grw-navbar-height + var.$grw-navbar-border-width;
   .data-layout-container {
     display: flex;
     flex-direction: row;
-    height: calc(100vh - 0px);
+    height: 100vh;
     margin-top: 0px;
     // css-teprsg
     > div:nth-of-type(2) {
@@ -38,31 +36,16 @@
           transition: width 200ms cubic-bezier(0.2, 0, 0, 1) 0s;
         }
         will-change: width;
+
         .grw-contextual-navigation-child {
-          position: absolute;
-          top: 0px;
-          left: 0px;
-          box-sizing: border-box;
-          width: 100%;
-          min-width: 240px;
           height: 100%;
           overflow-x: hidden;
-          transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
-          transition-duration: 0.22s;
-          transition-property: boxShadow, transform;
-          animation-duration: 0.22s;
-          animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
-          animation-fill-mode: forwards;
+        }
 
-          :global .grw-contextual-navigation-sub {
-            box-sizing: border-box;
-            display: flex;
-            flex-direction: column;
-            width: 100%;
-            height: 100%;
-            overflow: hidden;
-          }
+        .grw-drawer-toggler {
+          display: none; // invisible in default
         }
+
       }
 
       .simplebar-mask {
@@ -155,10 +138,6 @@
     }
   }
 
-  .grw-drawer-toggler {
-    display: none; // invisible in default
-  }
-
   .grw-sidebar-content-header {
     .grw-btn-reload {
       font-size: 18px;
@@ -169,18 +148,6 @@
 
 // Dock Mode
 @mixin dock() {
-  z-index: bs.$zindex-sticky;
-
-  // override @atlaskit/navigation-next styles
-  $navbar-total-height: var.$grw-navbar-height + var.$grw-navbar-border-width;
-  .data-layout-container {
-    max-height: calc(100vh - #{var.$grw-navbar-border-width});
-  }
-  .navigation {
-    position: unset;
-
-    top: $navbar-total-height;
-  }
 }
 
 // Drawer Mode
@@ -210,8 +177,10 @@
       transform: translateX(0);
     }
 
-    .grw-drawer-toggler {
-      display: block;
+    .grw-contextual-navigation-child {
+      .grw-drawer-toggler {
+        display: block;
+      }
     }
   }
 
@@ -219,24 +188,18 @@
     display: none !important;
   }
 
-  .grw-drawer-toggler {
-    position: fixed;
-    right: -15px;
-
-    @include bs.media-breakpoint-down(sm) {
-      bottom: 15px;
-      width: 42px;
-      height: 42px;
-      font-size: 18px;
-    }
-    @include bs.media-breakpoint-up(md) {
-      top: 72px;
-      width: 50px;
-      height: 50px;
-      font-size: 24px;
+  .grw-contextual-navigation-child {
+    .grw-drawer-toggler {
+      @include bs.media-breakpoint-down(sm) {
+        position: fixed;
+        right: -15px;
+        bottom: 15px;
+        width: 42px;
+        height: 42px;
+        font-size: 18px;
+        transform: translateX(100%);
+      }
     }
-
-    transform: translateX(100%);
   }
 }
 

+ 12 - 14
apps/app/src/components/Sidebar.tsx → apps/app/src/components/Sidebar/Sidebar.tsx

@@ -14,17 +14,18 @@ import {
   useSidebarScrollerRef,
 } from '~/stores/ui';
 
-import DrawerToggler from './Navbar/DrawerToggler';
-import { NavigationResizeHexagon } from './Sidebar/NavigationResizeHexagon';
-import { SidebarNav } from './Sidebar/SidebarNav';
-import { SidebarSkeleton } from './Sidebar/Skeleton/SidebarSkeleton';
-import { StickyStretchableScrollerProps } from './StickyStretchableScroller';
+import DrawerToggler from '../Navbar/DrawerToggler';
+import { StickyStretchableScrollerProps } from '../StickyStretchableScroller';
+
+import { NavigationResizeHexagon } from './NavigationResizeHexagon';
+import { SidebarNav } from './SidebarNav';
+import { SidebarSkeleton } from './Skeleton/SidebarSkeleton';
 
 import styles from './Sidebar.module.scss';
 
-const StickyStretchableScroller = dynamic<StickyStretchableScrollerProps>(() => import('./StickyStretchableScroller')
+const StickyStretchableScroller = dynamic<StickyStretchableScrollerProps>(() => import('../StickyStretchableScroller')
   .then(mod => mod.StickyStretchableScroller), { ssr: false });
-const SidebarContents = dynamic(() => import('./Sidebar/SidebarContents')
+const SidebarContents = dynamic(() => import('./SidebarContents')
   .then(mod => mod.SidebarContents), { ssr: false, loading: () => <SidebarSkeleton /> });
 
 const sidebarMinWidth = 240;
@@ -90,7 +91,7 @@ const SidebarContentsWrapper = memo(() => {
 SidebarContentsWrapper.displayName = 'SidebarContentsWrapper';
 
 
-const Sidebar = memo((): JSX.Element => {
+export const Sidebar = memo((): JSX.Element => {
 
   const { data: isDrawerMode } = useDrawerMode();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
@@ -315,10 +316,9 @@ const Sidebar = memo((): JSX.Element => {
                 onMouseLeave={hoverOutResizableContainerHandler}
                 style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
               >
-                <div className="grw-contextual-navigation-child">
-                  <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
-                    <SidebarContentsWrapper></SidebarContentsWrapper>
-                  </div>
+                <div className={`grw-contextual-navigation-child ${showContents ? '' : 'd-none'}`} data-testid="grw-contextual-navigation-child">
+                  <SidebarContents />
+                  <DrawerToggler iconClass="icon-arrow-left" />
                 </div>
               </div>
             </div>
@@ -358,5 +358,3 @@ const Sidebar = memo((): JSX.Element => {
 
 });
 Sidebar.displayName = 'Sidebar';
-
-export default Sidebar;

+ 18 - 0
apps/app/src/components/Sidebar/SidebarBrandLogo.tsx

@@ -0,0 +1,18 @@
+import { memo } from 'react';
+
+import GrowiLogo from '../Icons/GrowiLogo';
+
+type SidebarBrandLogoProps = {
+  isDefaultLogo?: boolean
+}
+
+export const SidebarBrandLogo = memo((props: SidebarBrandLogoProps) => {
+  const { isDefaultLogo } = props;
+
+  return isDefaultLogo
+    ? <GrowiLogo />
+    // eslint-disable-next-line @next/next/no-img-element
+    : (<img src='/attachment/brand-logo' alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />);
+});
+
+SidebarBrandLogo.displayName = 'SidebarBrandLogo';

+ 26 - 0
apps/app/src/components/Sidebar/SidebarNav.module.scss

@@ -1,3 +1,5 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
 @use '~/styles/variables' as var;
 
 .grw-sidebar-nav :global {
@@ -20,8 +22,32 @@
     }
   }
 
+  // set position and z-index to prevent dropdowns covered by other element
+  position: relative;
+  z-index: bs.$zindex-fixed;
+
   height: 100vh;
 
+  .grw-logo {
+    svg {
+      width: var.$grw-logo-width;
+      height: var.$grw-logo-width;
+      padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
+    }
+  }
+
+  .grw-apperance-mode-dropdown,
+  .grw-personal-dropdown {
+    .dropdown-menu {
+      min-width: 15rem;
+
+      .grw-icon-container svg {
+        width: 18px;
+        height: 18px;
+      }
+    }
+  }
+
   .btn {
     width: var.$grw-sidebar-nav-width;
     line-height: 1em;

+ 30 - 1
apps/app/src/components/Sidebar/SidebarNav.tsx

@@ -2,16 +2,29 @@ import React, {
   FC, memo, useCallback,
 } from 'react';
 
+import dynamic from 'next/dynamic';
 import Link from 'next/link';
 
 import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { SidebarContentsType } from '~/interfaces/ui';
-import { useIsAdmin, useGrowiCloudUri } from '~/stores/context';
+import {
+  useIsAdmin, useGrowiCloudUri, useIsDefaultLogo, useIsGuestUser,
+} from '~/stores/context';
 import { useCurrentSidebarContents } from '~/stores/ui';
 
+import DrawerToggler from '../Navbar/DrawerToggler';
+
+import { SidebarBrandLogo } from './SidebarBrandLogo';
+
 import styles from './SidebarNav.module.scss';
 
 
+const PersonalDropdown = dynamic(() => import('./PersonalDropdown').then(mod => mod.PersonalDropdown), { ssr: false });
+const InAppNotificationDropdown = dynamic(() => import('../InAppNotification/InAppNotificationDropdown')
+  .then(mod => mod.InAppNotificationDropdown), { ssr: false });
+const AppearanceModeDropdown = dynamic(() => import('./AppearanceModeDropdown').then(mod => mod.AppearanceModeDropdown), { ssr: false });
+
+
 type PrimaryItemProps = {
   contents: SidebarContentsType,
   label: string,
@@ -83,12 +96,24 @@ type Props = {
 
 export const SidebarNav: FC<Props> = (props: Props) => {
   const { data: isAdmin } = useIsAdmin();
+  const { data: isGuestUser } = useIsGuestUser();
   const { data: growiCloudUri } = useGrowiCloudUri();
+  const { data: isDefaultLogo } = useIsDefaultLogo();
 
   const { onItemSelected } = props;
 
+  const isAuthenticated = isGuestUser === false;
+
   return (
     <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`}>
+      {/* Brand Logo  */}
+      <div className="navbar-brand">
+        <Link href="/" className="grw-logo d-block">
+          <SidebarBrandLogo isDefaultLogo={isDefaultLogo} />
+        </Link>
+        <DrawerToggler />
+      </div>
+
       <div className="grw-sidebar-nav-primary-container" data-vrt-blackout-sidebar-nav>
         {/* eslint-disable max-len */}
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />
@@ -102,6 +127,10 @@ export const SidebarNav: FC<Props> = (props: Props) => {
         <PrimaryItem contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmark" onItemSelected={onItemSelected} />
       </div>
       <div className="grw-sidebar-nav-secondary-container">
+        <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
+        <PersonalDropdown />
+        <InAppNotificationDropdown />
+
         {isAdmin && <SecondaryItem label="Admin" iconName="settings" href="/admin" />}
         {/* <SecondaryItem label="Draft" iconName="file_copy" href="/me/drafts" /> */}
         <SecondaryItem label="Help" iconName="help" href={ growiCloudUri != null ? 'https://growi.cloud/help/' : 'https://docs.growi.org' } isBlank />

+ 1 - 0
apps/app/src/components/Sidebar/index.ts

@@ -0,0 +1 @@
+export * from './Sidebar';

+ 2 - 3
apps/app/src/components/UsersHomepageFooter.module.scss

@@ -1,5 +1,4 @@
 @use '@growi/ui/src/styles/molecules/page_list';
-@use '~/styles/variables' as var;
 $grw-sidebar-content-header-height: 58px;
 $grw-sidebar-content-footer-height: 50px;
 
@@ -97,11 +96,11 @@ $grw-sidebar-content-footer-height: 50px;
 }
 
 .grw-bookarks-contents-compressed {
-  max-height: calc(70vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
+  max-height: calc(70vh - ($grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
   overflow-y: scroll;
 }
 
 .grw-bookarks-contents-expanded {
-  max-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
+  max-height: calc(100vh - ($grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
   overflow-y: scroll;
 }

+ 0 - 7
apps/app/src/pages/[[...path]].page.tsx

@@ -53,7 +53,6 @@ import loggerFactory from '~/utils/logger';
 
 import { BasicLayout } from '../components/Layout/BasicLayout';
 import GrowiContextualSubNavigationSubstance from '../components/Navbar/GrowiContextualSubNavigation';
-import type { GrowiSubNavigationSwitcherProps } from '../components/Navbar/GrowiSubNavigationSwitcher';
 import { DisplaySwitcher } from '../components/Page/DisplaySwitcher';
 
 import type { NextPageWithLayout } from './_app.page';
@@ -72,8 +71,6 @@ declare global {
 const GrowiPluginsActivator = dynamic(() => import('~/features/growi-plugin/client/components').then(mod => mod.GrowiPluginsActivator), { ssr: false });
 const DescendantsPageListModal = dynamic(() => import('../components/DescendantsPageListModal').then(mod => mod.DescendantsPageListModal), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
-const GrowiSubNavigationSwitcher = dynamic<GrowiSubNavigationSwitcherProps>(() => import('../components/Navbar/GrowiSubNavigationSwitcher')
-  .then(mod => mod.GrowiSubNavigationSwitcher), { ssr: false });
 const DrawioModal = dynamic(() => import('../components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });
 const HandsontableModal = dynamic(() => import('../components/PageEditor/HandsontableModal').then(mod => mod.HandsontableModal), { ssr: false });
 const TemplateModal = dynamic(() => import('../components/TemplateModal').then(mod => mod.TemplateModal), { ssr: false });
@@ -339,10 +336,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
           </div>
         </header>
 
-        <div className="d-edit-none">
-          <GrowiSubNavigationSwitcher isLinkSharingDisabled={props.disableLinkSharing} />
-        </div>
-
         <div id="grw-subnav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 

+ 6 - 12
apps/app/src/styles/_editor.scss

@@ -1,6 +1,6 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
+@use './variables' as var;
 
-@import './variables' ;
 @import './mixins' ;
 @import './organisms/wiki-custom-sidebar';
 
@@ -15,30 +15,24 @@
 
   // restrict height of subnav
   .grw-subnav {
-    height: $grw-subnav-height-on-edit;
+    height: var.$grw-subnav-height-on-edit;
     min-height: unset;
     padding-top: 0;
     padding-right: 15px;
     padding-left: 15px;
 
     @include bs.media-breakpoint-up(lg) {
-      height: $grw-subnav-height-lg-on-edit;
+      height: var.$grw-subnav-height-lg-on-edit;
     }
   }
 
-  .page-wrapper {
-    position: relative;
-    top: $grw-navbar-border-width;
-    height: calc(100vh - #{$grw-navbar-border-width});
-  }
-
   // calculate margin
-  $editor-margin-top: $grw-navbar-border-width + $grw-subnav-height-on-edit;
+  $editor-margin-top: var.$grw-subnav-height-on-edit;
   @include expand-editor($editor-margin-top);
 
   @include bs.media-breakpoint-up(lg) {
     // calculate margin
-    $editor-margin-top: $grw-navbar-border-width + $grw-subnav-height-lg-on-edit;
+    $editor-margin-top: var.$grw-subnav-height-lg-on-edit;
     @include expand-editor($editor-margin-top);
   }
 
@@ -75,7 +69,7 @@
    * Expand Editor
    *****************/
   .grw-editor-navbar-bottom {
-    height: $grw-editor-navbar-bottom-height;
+    height: var.$grw-editor-navbar-bottom-height;
 
     .grw-grant-selector {
       @include bs.media-breakpoint-down(sm) {

+ 2 - 1
apps/app/src/styles/_mixins.scss

@@ -1,4 +1,5 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
+@use './variables' as var;
 
 @mixin variable-font-size($basesize) {
   font-size: $basesize * 0.6;
@@ -18,7 +19,7 @@
 }
 
 @mixin expand-editor($editor-margin-top) {
-  $header-plus-footer: $editor-margin-top + $grw-editor-navbar-bottom-height;
+  $header-plus-footer: $editor-margin-top + var.$grw-editor-navbar-bottom-height;
 
   $editor-margin: $header-plus-footer //
     + 25px //   add .btn-open-dropzone height

+ 0 - 12
apps/app/src/styles/_variables.scss

@@ -10,12 +10,6 @@ $grw-marker-cyan: #6ff;
 $grw-marker-green: #6f6;
 
 //== Layout
-$grw-navbar-height: 52px;
-$grw-navbar-border-width: 3.3333px;
-// slightly larger than $zindex-sticky
-// https://getbootstrap.jp/docs/4.6/layout/overview/#z-index
-$grw-navbar-z-index: 1025;
-
 $grw-subnav-min-height: 95px;
 $grw-subnav-min-height-md: 115px;
 $grw-subnav-height-on-edit: 95px;
@@ -31,10 +25,4 @@ $grw-sidebar-nav-width: 64px; // !!DO NOT CHANGE!! 'margin-left' for '.css-teprs
 $grw-logo-width: $grw-sidebar-nav-width;
 $grw-logomark-width: 36px;
 
-// fix tab width to 95 pixels
-// see also '_editor.scss'
-$grw-nav-main-left-tab-width: 95px;
-$grw-nav-main-left-tab-width-mobile: 50px;
-$grw-nav-main-tab-height: 42px;
-
 $grw-scroll-margin-top-in-view: 130px;

+ 0 - 12
apps/app/src/styles/theme/apply-colors.scss

@@ -665,18 +665,6 @@ mark.rbt-highlight-text {
   background-color: var(--bgcolor-global);
 }
 
-.grw-fab {
-  .btn-create-page {
-    svg {
-      fill: hsl.contrast(var(--primary));
-    }
-  }
-
-  .btn-scroll-to-top {
-    fill: $gray-900;
-  }
-}
-
 /*
   Slack Integration
 */

+ 2 - 5
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts

@@ -42,9 +42,6 @@ context('Access to page', () => {
     // https://redmine.weseek.co.jp/issues/111384
     // cy.get('.toc-link').should('be.visible');
 
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
-
     // assert the element is in viewport
     cy.get('#headers').should('be.inViewport');
 
@@ -212,7 +209,7 @@ context('Access to Template Editing Mode', () => {
 
     // Open sidebar
     cy.collapseSidebar(false);
-    cy.getByTestid('grw-contextual-navigation-sub').should('be.visible');
+    cy.getByTestid('grw-contextual-navigation-child').should('be.visible');
     cy.waitUntilSkeletonDisappear();
 
     // If PageTree is not active when the sidebar is opened, make it active
@@ -224,7 +221,7 @@ context('Access to Template Editing Mode', () => {
       });
 
     // Create page (/{parentPath}}/{newPagePath}) from PageTree
-    cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+    cy.getByTestid('grw-contextual-navigation-child').within(() => {
       cy.get('.grw-pagetree-item-children').first().as('pagetreeItem').within(() => {
         cy.get('#page-create-button-in-page-tree').first().click({force: true})
       });

+ 3 - 49
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--sticky-features.cy.ts

@@ -1,5 +1,5 @@
 context('Access to any page', () => {
-  const ssPrefix = 'subnav-and-fab-';
+  const ssPrefix = 'subnav-';
 
   beforeEach(() => {
     // login
@@ -13,7 +13,7 @@ context('Access to any page', () => {
     cy.collapseSidebar(true, true);
   });
 
-  it('Subnavigation and fab displays changes on scroll down and up', () => {
+  it('Subnavigation displays changes on scroll down and up', () => {
     cy.waitUntil(() => {
       // do
       // Scroll the window 250px down is enough to trigger sticky effect
@@ -21,8 +21,6 @@ context('Access to any page', () => {
       // wait until
       return cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
     });
-    // wait until fab visible
-    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('visible')));
 
     cy.waitUntilSkeletonDisappear();
     cy.screenshot(`${ssPrefix}visible-on-scroll-down`);
@@ -34,13 +32,11 @@ context('Access to any page', () => {
       // wait until
       return cy.waitUntil(() => cy.getByTestid('grw-subnav-switcher').then($elem => $elem.hasClass('grw-subnav-switcher-hidden')));
     });
-    // wait until fab invisible
-    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('invisible')));
 
     cy.screenshot(`${ssPrefix}invisible-on-scroll-top`);
   });
 
-  it('Subnavigation and fab are not displayed when move to other pages', () => {
+  it('Subnavigation is not displayed when move to other pages', () => {
     cy.waitUntil(() => {
       // do
       // Scroll the window 250px down is enough to trigger sticky effect
@@ -48,7 +44,6 @@ context('Access to any page', () => {
       // wait until
       return () => cy.getByTestid('grw-subnav-switcher').then($elem => !$elem.hasClass('grw-subnav-switcher-hidden'));
     });
-    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('visible')));
 
     // Move to /Sandbox page
     cy.visit('/Sandbox');
@@ -56,51 +51,10 @@ context('Access to any page', () => {
     cy.waitUntilSkeletonDisappear();
     cy.collapseSidebar(true);
 
-    cy.waitUntil(() => cy.getByTestid('grw-fab-page-create-button').then($elem => $elem.hasClass('invisible')));
     cy.waitUntil(() => cy.getByTestid('grw-subnav-switcher').then($elem => $elem.hasClass('grw-subnav-switcher-hidden')));
     cy.screenshot(`${ssPrefix}not-visible-on-move-to-other-pages`);
   });
 
-  it('Able to open create page modal from fab', () => {
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window back to top
-      cy.scrollTo(0, 250);
-      // wait until
-      return cy.getByTestid('grw-fab-page-create-button')
-      .should('have.class', 'visible')
-      .within(() => {
-        cy.get('.btn-create-page').click();
-        return true;
-      });
-    });
-
-    cy.getByTestid('page-create-modal').should('be.visible').within(() => {
-      cy.screenshot(`${ssPrefix}new-page-modal-opened-from-fab`);
-      cy.get('button.close').click();
-    });
-  });
-
-  it('Able to scroll page to top from fab', () => {
-    // Initial scroll down
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window 250px down is enough to trigger sticky effect
-      cy.scrollTo(0, 250);
-      // wait until
-      return cy.getByTestid('grw-fab-return-to-top')
-        .should('have.class', 'visible')
-        .then(() => {
-          cy.waitUntil(() => {
-            cy.get('.btn-scroll-to-top').click();
-            return cy.getByTestid('grw-fab-return-to-top').should('have.class', 'invisible');
-          });
-        });
-    });
-    cy.waitUntilSkeletonDisappear();
-    cy.screenshot(`${ssPrefix}scroll-page-to-top`);
-  });
-
   it('Able to click buttons on subnavigation switcher when sticky', () => {
     cy.waitUntil(() => {
       // do

+ 0 - 3
apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.cy.ts

@@ -14,9 +14,6 @@ context('Access to page by guest', () => {
     cy.visit('/Sandbox#headers');
     cy.collapseSidebar(true);
 
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
-
     // assert the element is in viewport
     cy.get('#headers').should('be.inViewport');
 

+ 2 - 30
apps/app/test/cypress/e2e/21-basic-features-for-guest/21-basic-features-for-guest--sticky-for-guest.cy.ts

@@ -1,5 +1,6 @@
-context('Access sticky sub navigation switcher and Fab for guest', () => {
+context('Access sticky sub navigation switcher for guest', () => {
   const ssPrefix = 'access-sticky-by-guest-';
+
   it('Sub navigation sticky changes when scrolling down and up', () => {
     cy.visit('/Sandbox');
     cy.waitUntilSkeletonDisappear();
@@ -26,33 +27,4 @@ context('Access sticky sub navigation switcher and Fab for guest', () => {
     cy.screenshot(`${ssPrefix}subnav-switcher-is-not-sticky-on-scroll-top`);
   });
 
-  it('Fab display changes when scrolling down and up', () => {
-    cy.visit('/Sandbox');
-    cy.waitUntilSkeletonDisappear();
-    cy.collapseSidebar(true, true);
-
-    // Visible
-    cy.waitUntil(() => {
-      // do
-      // Scroll the window 250px down is enough to trigger sticky effect
-       cy.scrollTo(0, 250);
-
-      // wait until
-      return cy.getByTestid('grw-fab-return-to-top').then($elem => $elem.hasClass('visible'));
-
-    });
-    cy.screenshot(`${ssPrefix}fab-is-visible-on-scroll-down`);
-
-    // Invisible
-    cy.waitUntil(() => {
-      // do
-      // Scroll page to top
-       cy.scrollTo(0, 0);
-
-       // wait until
-      return cy.getByTestid('grw-fab-return-to-top').then($elem => $elem.hasClass('invisible'));
-    });
-    cy.screenshot(`${ssPrefix}fab-is-invisible-on-scroll-top`);
-
-  });
 });

+ 11 - 11
apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts

@@ -27,7 +27,7 @@ describe('Access to sidebar', () => {
 
       describe('Test show/collapse button', () => {
         it('Successfully show sidebar', () => {
-          cy.getByTestid('grw-contextual-navigation-sub').should('be.visible');
+          cy.getByTestid('grw-contextual-navigation-child').should('be.visible');
 
           cy.waitUntilSkeletonDisappear();
           cy.screenshot(`${ssPrefix}1-sidebar-shown`, {
@@ -39,7 +39,7 @@ describe('Access to sidebar', () => {
         it('Successfully collapse sidebar', () => {
           cy.getByTestid('grw-navigation-resize-button').click({force: true});
 
-          cy.getByTestid('grw-contextual-navigation-sub').should('not.be.visible');
+          cy.getByTestid('grw-contextual-navigation-child').should('not.be.visible');
 
           cy.waitUntilSkeletonDisappear();
           cy.screenshot(`${ssPrefix}2-sidebar-collapsed`, {
@@ -61,7 +61,7 @@ describe('Access to sidebar', () => {
         });
 
         it('Successfully access to page tree', () => {
-          cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+          cy.getByTestid('grw-contextual-navigation-child').within(() => {
             cy.getByTestid('grw-pagetree-item-container').should('be.visible');
 
             cy.waitUntilSkeletonDisappear();
@@ -70,7 +70,7 @@ describe('Access to sidebar', () => {
         });
 
         it('Successfully hide page tree items', () => {
-          cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+          cy.getByTestid('grw-contextual-navigation-child').within(() => {
             cy.get('.grw-pagetree-open').should('be.visible');
 
             // hide page tree tiems
@@ -83,7 +83,7 @@ describe('Access to sidebar', () => {
         it('Successfully click Add to Bookmarks button', () => {
           cy.waitUntil(() => {
             // do
-            cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+            cy.getByTestid('grw-contextual-navigation-child').within(() => {
               cy.get('.grw-pagetree-item-children').first().as('pagetreeItem').within(() => {
                 cy.getByTestid('open-page-item-control-btn').find('button').first().invoke('css','display','block').click()
               });
@@ -101,7 +101,7 @@ describe('Access to sidebar', () => {
           // show dropdown again
           cy.waitUntil(() => {
             // do
-            cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+            cy.getByTestid('grw-contextual-navigation-child').within(() => {
               cy.get('.grw-pagetree-item-children').first().as('pagetreeItem').within(() => {
                 cy.getByTestid('open-page-item-control-btn').find('button').first().invoke('css','display','block').click()
               });
@@ -116,7 +116,7 @@ describe('Access to sidebar', () => {
         it('Successfully show duplicate page modal', () => {
           cy.waitUntil(() => {
             // do
-            cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+            cy.getByTestid('grw-contextual-navigation-child').within(() => {
               cy.get('.grw-pagetree-item-children').first().as('pagetreeItem').within(() => {
                 cy.getByTestid('open-page-item-control-btn').find('button').first().invoke('css','display','block').click()
               });
@@ -141,7 +141,7 @@ describe('Access to sidebar', () => {
         it('Successfully rename page', () => {
           cy.waitUntil(() => {
             // do
-            cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+            cy.getByTestid('grw-contextual-navigation-child').within(() => {
               cy.get('.grw-pagetree-item-children').first().as('pagetreeItem').within(() => {
                 cy.getByTestid('open-page-item-control-btn').find('button').first().invoke('css','display','block').click()
               });
@@ -164,7 +164,7 @@ describe('Access to sidebar', () => {
         it('Successfully show delete page modal', () => {
           cy.waitUntil(() => {
             // do
-            cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+            cy.getByTestid('grw-contextual-navigation-child').within(() => {
               cy.get('.grw-pagetree-item-children').first().as('pagetreeItem').within(() => {
                 cy.getByTestid('open-page-item-control-btn').find('button').first().invoke('css','display','block').click()
               });
@@ -196,7 +196,7 @@ describe('Access to sidebar', () => {
         });
 
         it('Successfully access to custom sidebar', () => {
-          cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+          cy.getByTestid('grw-contextual-navigation-child').within(() => {
             cy.get('.grw-sidebar-content-header > h3').find('a');
 
             cy.waitUntilSkeletonDisappear();
@@ -269,7 +269,7 @@ describe('Access to sidebar', () => {
         });
 
         it('Successfully access to tags', () => {
-          cy.getByTestid('grw-contextual-navigation-sub').within(() => {
+          cy.getByTestid('grw-contextual-navigation-child').within(() => {
             cy.getByTestid('grw-tags-list').should('be.visible');
 
             cy.screenshot(`${ssPrefix}tags-1-access-to-tags`, { blackout: blackoutOverride });

+ 0 - 2
apps/app/test/cypress/e2e/60-home/60-home--home.cy.ts

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

+ 2 - 2
apps/app/test/cypress/support/commands.ts

@@ -81,7 +81,7 @@ Cypress.Commands.add('collapseSidebar', (isCollapsed: boolean, waitUntilSaving =
 
   cy.getByTestid('grw-sidebar').should('be.visible').within(() => {
 
-    const isSidebarContextualNavigationHidden = isHiddenByTestId('grw-contextual-navigation-sub');
+    const isSidebarContextualNavigationHidden = isHiddenByTestId('grw-contextual-navigation-child');
     if (isSidebarContextualNavigationHidden === isCollapsed) {
       return;
     }
@@ -96,7 +96,7 @@ Cypress.Commands.add('collapseSidebar', (isCollapsed: boolean, waitUntilSaving =
       }
 
       // wait until
-      return cy.getByTestid('grw-contextual-navigation-sub').then($contents => isHidden($contents) === isCollapsed);
+      return cy.getByTestid('grw-contextual-navigation-child').then($contents => isHidden($contents) === isCollapsed);
     });
   });
 

+ 0 - 12
packages/preset-themes/src/styles/_variables.scss

@@ -10,12 +10,6 @@ $grw-marker-cyan: #6ff;
 $grw-marker-green: #6f6;
 
 //== Layout
-$grw-navbar-height: 52px;
-$grw-navbar-border-width: 3.3333px;
-// slightly larger than $zindex-sticky
-// https://getbootstrap.jp/docs/4.6/layout/overview/#z-index
-$grw-navbar-z-index: 1025;
-
 $grw-subnav-min-height: 95px;
 $grw-subnav-min-height-md: 115px;
 $grw-subnav-height-on-edit: 95px;
@@ -30,9 +24,3 @@ $grw-sidebar-nav-width: 64px; // !!DO NOT CHANGE!! 'margin-left' for '.css-teprs
 
 $grw-logo-width: $grw-sidebar-nav-width;
 $grw-logomark-width: 36px;
-
-// fix tab width to 95 pixels
-// see also '_editor.scss'
-$grw-nav-main-left-tab-width: 95px;
-$grw-nav-main-left-tab-width-mobile: 50px;
-$grw-nav-main-tab-height: 42px;

+ 2 - 2
packages/ui/src/utils/browser-utils.ts

@@ -7,8 +7,8 @@ export const addBreakpointListener = (
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     listener: (this: MediaQueryList, ev: MediaQueryListEvent) => any,
 ): MediaQueryList => {
-  // get the value of '--breakpoint-*'
-  const breakpointPixel = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue(`--breakpoint-${breakpoint}`), 10);
+  // get the value of '--bs-breakpoint-*'
+  const breakpointPixel = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue(`--bs-breakpoint-${breakpoint}`), 10);
 
   const mediaQueryList = window.matchMedia(`(min-width: ${breakpointPixel}px)`);