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

Merge branch 'dev/7.0.x' into feat/126520-emoji-features-1

Yuki Takei 2 лет назад
Родитель
Сommit
96a54fb075
100 измененных файлов с 1470 добавлено и 846 удалено
  1. 1 0
      .devcontainer/Dockerfile
  2. 18 1
      CHANGELOG.md
  3. 0 67
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss
  4. 2 16
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx
  5. 1 1
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  6. 0 0
      apps/app/_obsolete/src/components/Sidebar/NavigationResizeHexagon.tsx
  7. 6 6
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  8. 18 23
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  9. 1 1
      apps/app/docker/README.md
  10. 1 1
      apps/app/package.json
  11. 2 2
      apps/app/public/static/locales/en_US/admin.json
  12. 2 2
      apps/app/public/static/locales/ja_JP/admin.json
  13. 2 2
      apps/app/public/static/locales/zh_CN/admin.json
  14. 3 0
      apps/app/resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2
  15. 1 1
      apps/app/src/client/services/page-operation.ts
  16. 1 11
      apps/app/src/client/services/user-ui-settings.ts
  17. 5 6
      apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
  18. 10 12
      apps/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx
  19. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.module.scss
  20. 5 8
      apps/app/src/components/AuthorInfo/AuthorInfo.module.scss
  21. 5 1
      apps/app/src/components/AuthorInfo/AuthorInfo.tsx
  22. 1 0
      apps/app/src/components/AuthorInfo/index.ts
  23. 0 17
      apps/app/src/components/BookmarkButtons.module.scss
  24. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  25. 2 5
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx
  26. 0 0
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.module.scss
  27. 1 0
      apps/app/src/components/Common/CopyDropdown/index.ts
  28. 18 0
      apps/app/src/components/Common/DrawerToggler/DrawerToggler.module.scss
  29. 36 0
      apps/app/src/components/Common/DrawerToggler/DrawerToggler.tsx
  30. 1 0
      apps/app/src/components/Common/DrawerToggler/index.ts
  31. 5 21
      apps/app/src/components/Common/Dropdown/PageItemControl.tsx
  32. 6 0
      apps/app/src/components/Common/PagePathHierarchicalLink/CollapsedParentsDropdown.module.scss
  33. 35 0
      apps/app/src/components/Common/PagePathHierarchicalLink/CollapsedParentsDropdown.tsx
  34. 4 0
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss
  35. 8 11
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  36. 1 0
      apps/app/src/components/Common/PagePathHierarchicalLink/index.ts
  37. 21 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss
  38. 121 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  39. 1 0
      apps/app/src/components/Common/PagePathNav/index.ts
  40. 0 0
      apps/app/src/components/Common/UserPictureList.jsx
  41. 4 12
      apps/app/src/components/CompleteUserRegistrationForm.tsx
  42. 23 0
      apps/app/src/components/FontFamily/GlobalFonts.tsx
  43. 1 0
      apps/app/src/components/FontFamily/types.d.ts
  44. 20 0
      apps/app/src/components/FontFamily/use-lato.tsx
  45. 18 0
      apps/app/src/components/FontFamily/use-material-symbols-outlined.tsx
  46. 23 0
      apps/app/src/components/FontFamily/use-source-han-code-jp.tsx
  47. 1 6
      apps/app/src/components/IdenticalPathPage.module.scss
  48. 0 70
      apps/app/src/components/InAppNotification/InAppNotificationElm.tsx
  49. 45 0
      apps/app/src/components/InAppNotification/PageNotification/ModelNotification.tsx
  50. 23 28
      apps/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx
  51. 18 22
      apps/app/src/components/InAppNotification/PageNotification/UserModelNotification.tsx
  52. 99 0
      apps/app/src/components/InAppNotification/PageNotification/useActionAndMsg.ts
  53. 4 12
      apps/app/src/components/InstallerForm.tsx
  54. 12 20
      apps/app/src/components/InvitedForm.tsx
  55. 0 0
      apps/app/src/components/ItemsTree/Item.module.scss
  56. 1 1
      apps/app/src/components/ItemsTree/ItemNode.ts
  57. 0 2
      apps/app/src/components/ItemsTree/ItemsTree.module.scss
  58. 7 9
      apps/app/src/components/ItemsTree/ItemsTree.tsx
  59. 2 2
      apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx
  60. 2 0
      apps/app/src/components/ItemsTree/index.ts
  61. 0 10
      apps/app/src/components/Layout/Admin.module.scss
  62. 1 1
      apps/app/src/components/Layout/AdminLayout.tsx
  63. 5 2
      apps/app/src/components/Layout/BasicLayout.tsx
  64. 157 10
      apps/app/src/components/Layout/NoLoginLayout.module.scss
  65. 3 3
      apps/app/src/components/Layout/NoLoginLayout.tsx
  66. 35 1
      apps/app/src/components/Layout/PageViewLayout.module.scss
  67. 6 4
      apps/app/src/components/Layout/PageViewLayout.tsx
  68. 4 26
      apps/app/src/components/Layout/SearchResultLayout.module.scss
  69. 0 17
      apps/app/src/components/LikeButtons.module.scss
  70. 22 36
      apps/app/src/components/LoginForm.tsx
  71. 0 31
      apps/app/src/components/Navbar/DrawerToggler.tsx
  72. 13 0
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.module.scss
  73. 84 138
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  74. 15 0
      apps/app/src/components/Navbar/GrowiNavbarBottom.module.scss
  75. 27 14
      apps/app/src/components/Navbar/GrowiNavbarBottom.tsx
  76. 12 9
      apps/app/src/components/Navbar/PageEditorModeManager.module.scss
  77. 36 30
      apps/app/src/components/Navbar/PageEditorModeManager.tsx
  78. 58 0
      apps/app/src/components/Navbar/hooks.tsx
  79. 0 3
      apps/app/src/components/Page/DisplaySwitcher.tsx
  80. 6 0
      apps/app/src/components/Page/PageView.tsx
  81. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  82. 5 0
      apps/app/src/components/PageAuthorInfo/PageAuthorInfo.module.scss
  83. 46 0
      apps/app/src/components/PageAuthorInfo/PageAuthorInfo.tsx
  84. 2 2
      apps/app/src/components/PageContentFooter.tsx
  85. 26 0
      apps/app/src/components/PageControls/BookmarkButtons.module.scss
  86. 33 35
      apps/app/src/components/PageControls/BookmarkButtons.tsx
  87. 25 0
      apps/app/src/components/PageControls/LikeButtons.module.scss
  88. 19 24
      apps/app/src/components/PageControls/LikeButtons.tsx
  89. 20 0
      apps/app/src/components/PageControls/PageControls.module.scss
  90. 19 18
      apps/app/src/components/PageControls/PageControls.tsx
  91. 25 0
      apps/app/src/components/PageControls/SeenUserInfo.module.scss
  92. 6 7
      apps/app/src/components/PageControls/SeenUserInfo.tsx
  93. 18 0
      apps/app/src/components/PageControls/SubscribeButton.module.scss
  94. 3 1
      apps/app/src/components/PageControls/SubscribeButton.tsx
  95. 17 0
      apps/app/src/components/PageControls/_button-styles.scss
  96. 1 0
      apps/app/src/components/PageControls/index.ts
  97. 12 0
      apps/app/src/components/PageControls/user-list-popover.module.scss
  98. 1 0
      apps/app/src/components/PageEditor/Editor.tsx
  99. 42 0
      apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss
  100. 15 22
      apps/app/src/components/PageEditor/EditorNavbarBottom.tsx

+ 1 - 0
.devcontainer/Dockerfile

@@ -50,6 +50,7 @@ RUN apt-get update \
     && rm -rf /var/lib/apt/lists/*
 ENV DEBIAN_FRONTEND=dialog
 
+RUN git-lfs pull
 RUN yarn global add turbo
 RUN yarn global add node-gyp
 

+ 18 - 1
CHANGELOG.md

@@ -1,9 +1,26 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.2.1...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.2.2...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.2.2](https://github.com/weseek/growi/compare/v6.2.1...v6.2.2) - 2023-10-30
+
+### 🚀 Improvement
+
+- imprv: Printing styles (#8195) @yuki-takei
+
+### 🐛 Bug Fixes
+
+- fix: Show liker counts in lsx (#8194) @yuki-takei
+
+### 🧰 Maintenance
+
+- ci(deps-dev): bump postcss from 8.4.26 to 8.4.31 (#8142) @dependabot
+- ci(deps): bump cypress-io/github-action from 5 to 6 (#8051) @dependabot
+- ci(deps): bump amannn/action-semantic-pull-request from 5.0.2 to 5.3.0 (#8127) @dependabot
+- ci(deps): bump aws-actions/configure-aws-credentials from 2 to 4 (#8128) @dependabot
+
 ## [v6.2.1](https://github.com/weseek/growi/compare/v6.2.0...v6.2.1) - 2023-10-03
 
 ### BREAKING CHANGES

+ 0 - 67
apps/app/src/components/Navbar/GrowiSubNavigation.module.scss → apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss

@@ -22,24 +22,11 @@
       min-height: var.$grw-subnav-min-height-md;
     }
 
-    .grw-drawer-toggler {
-      width: 50px;
-      height: 50px;
-      font-size: 24px;
-    }
-
     h1 {
       @include mixins.variable-font-size(32px);
       line-height: 1.4em;
     }
 
-    .grw-page-path-nav {
-      .separator {
-        margin-right: 0.2em;
-        margin-left: 0.2em;
-      }
-    }
-
     .btn-copy {
       &:not(:hover):not(:active) {
         background-color: transparent !important;
@@ -47,19 +34,6 @@
       opacity: 0.5;
     }
 
-    .btn-edit-tags {
-      opacity: 0.5;
-
-      &.no-tags {
-        opacity: 0.7;
-      }
-    }
-
-    .btn-skeleton {
-      @extend %subnav-buttons-height;
-      width: 100%;
-    }
-
     .btn-subscribe {
       @extend %subnav-buttons-height;
       font-size: 20px;
@@ -125,46 +99,5 @@
         opacity: unset;
       }
     }
-
-    /*
-     * Compact Mode
-     */
-    &.grw-subnav-compact {
-      min-height: 70px;
-
-      @include bs.media-breakpoint-up(md) {
-        min-height: 90px;
-      }
-
-      .btn-skeleton {
-        @extend %compact-subnav-buttons-height;
-        width: 100%;
-      }
-
-      .btn-like,
-      .btn-bookmark,
-      .btn-subscribe {
-        width: 32px;
-        @extend %compact-subnav-buttons-height;
-        padding: 4px;
-        font-size: 16px;
-      }
-      .btn-seen-user {
-        width: 48px;
-        @extend %compact-subnav-buttons-height;
-        padding: 4px;
-        font-size: 16px;
-
-        svg {
-          width: 16px;
-          height: 16px;
-        }
-      }
-      .btn-page-item-control {
-        width: 32px;
-        @extend %compact-subnav-buttons-height;
-        font-size: 12px;
-      }
-    }
   }
 }

+ 2 - 16
apps/app/src/components/Navbar/GrowiSubNavigation.tsx → apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx

@@ -6,8 +6,6 @@ import {
 
 import PagePathNav from '../PagePathNav';
 
-import DrawerToggler from './DrawerToggler';
-
 
 import styles from './GrowiSubNavigation.module.scss';
 
@@ -16,10 +14,7 @@ export type GrowiSubNavigationProps = {
   pagePath?: string,
   pageId?: string,
   isNotFound?: boolean,
-  showDrawerToggler?: boolean,
   isTagLabelsDisabled?: boolean,
-  isDrawerMode?: boolean,
-  isCompactMode?: boolean,
   tags?: string[],
   rightComponent?: React.FunctionComponent,
   additionalClasses?: string[],
@@ -31,32 +26,23 @@ export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element
 
   const {
     pageId, pagePath,
-    showDrawerToggler,
-    isDrawerMode, isCompactMode,
     rightComponent: RightComponent,
     additionalClasses = [],
   } = props;
 
   const isViewMode = editorMode === EditorMode.View;
   const isEditorMode = !isViewMode;
-  const compactModeClasses = isCompactMode ? 'grw-subnav-compact d-print-none' : '';
 
   return (
     <div className={`
       grw-subnav ${styles['grw-subnav']} d-flex align-items-center justify-content-between
-      ${additionalClasses.join(' ')}
-      ${compactModeClasses}`}
+      ${additionalClasses.join(' ')}`}
     >
       {/* Left side */}
       <div className="d-flex grw-subnav-start-side">
-        { (showDrawerToggler && isDrawerMode) && (
-          <div className={`d-none d-md-flex align-items-center ${isEditorMode ? 'me-2 pe-2' : 'border-end me-4 pe-4'}`}>
-            <DrawerToggler />
-          </div>
-        ) }
         <div className="grw-path-nav-container">
           { pagePath != null && (
-            <PagePathNav pageId={pageId} pagePath={pagePath} isSingleLineMode={isEditorMode} isCompactMode={isCompactMode} />
+            <PagePathNav pageId={pageId} pagePath={pagePath} isSingleLineMode={isEditorMode} />
           ) }
         </div>
       </div>

+ 1 - 1
apps/app/src/components/Sidebar/AppearanceModeDropdown.tsx → apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -106,7 +106,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
       {/* remove .dropdown-toggle for hide caret */}
       {/* See https://stackoverflow.com/a/44577512/13183572 */}
       <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
-        <i className="material-icons">settings</i>
+        <span className="material-symbols-outlined">settings</span>
       </button>
 
       {/* dropdown */}

+ 0 - 0
apps/app/src/components/Sidebar/NavigationResizeHexagon.tsx → apps/app/_obsolete/src/components/Sidebar/NavigationResizeHexagon.tsx


+ 6 - 6
apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss

@@ -180,9 +180,9 @@
   /*
   * GROWI subnavigation
   */
-  .grw-subnav {
-    background-color: var(--bgcolor-subnav);
-  }
+  // .grw-subnav {
+  //   background-color: var(--bgcolor-subnav);
+  // }
 
   .grw-subnav-fixed-container .grw-subnav {
     background-color: hsl.alpha(var(--bgcolor-subnav),85%);
@@ -207,9 +207,9 @@
   /**
    * GROWI PagePathHierarchicalLink
    */
-  .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
-    color: $gray-600;
-  }
+  // .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
+  //   color: $gray-600;
+  // }
   /*
   * GROWI Sidebar
   */

+ 18 - 23
apps/app/_obsolete/src/styles/theme/apply-colors.scss

@@ -206,29 +206,24 @@ ul.pagination {
   $bgcolor-resize-button: var(--bgcolor-resize-button,white);
   $color-resize-button-hover: var(--color-resize-button-hover,var(--color-reversal));
   $bgcolor-resize-button-hover: var(--bgcolor-resize-button-hover,#{hsl.lighten(var(--bgcolor-resize-button), 5%)});
-  .grw-navigation-resize-button {
-    .hexagon-container svg {
-      .background {
-        fill: var(--bgcolor-resize-button);
-      }
-      .icon {
-        fill: var(--color-resize-button);
-      }
-    }
-    &:hover .hexagon-container svg {
-      .background {
-        fill: var(--bgcolor-resize-button-hover);
-      }
-      .icon {
-        fill: var(--color-resize-button-hover);
-      }
-    }
-  }
-  div.grw-global-navigation {
-    > div {
-      background-color: var(--bgcolor-sidebar);
-    }
-  }
+  // .grw-navigation-resize-button {
+  //   .hexagon-container svg {
+  //     .background {
+  //       fill: var(--bgcolor-resize-button);
+  //     }
+  //     .icon {
+  //       fill: var(--color-resize-button);
+  //     }
+  //   }
+  //   &:hover .hexagon-container svg {
+  //     .background {
+  //       fill: var(--bgcolor-resize-button-hover);
+  //     }
+  //     .icon {
+  //       fill: var(--color-resize-button-hover);
+  //     }
+  //   }
+  // }
   div.grw-contextual-navigation {
     > div {
       color: var(--color-sidebar-context);

+ 1 - 1
apps/app/docker/README.md

@@ -11,7 +11,7 @@ Supported tags and respective Dockerfile links
 ------------------------------------------------
 
 * [`7.0.0`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.0/apps/app/docker/Dockerfile)
-* [`6.2.1`, `6.2`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.1/apps/app/docker/Dockerfile)
+* [`6.2.2`, `6.2`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.2/apps/app/docker/Dockerfile)
 * [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)
 
 

+ 1 - 1
apps/app/package.json

@@ -236,7 +236,6 @@
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
     "load-css-file": "^1.0.0",
-    "material-icons": "^1.13.10",
     "mongodb-memory-server": "^8.12.2",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",
@@ -247,6 +246,7 @@
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",
     "react-hotkeys": "^2.0.0",
+    "react-stickynode": "^4.1.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "sass": "^1.53.0",

+ 2 - 2
apps/app/public/static/locales/en_US/admin.json

@@ -488,8 +488,8 @@
       "enable_marp_desc": "Marp can be used in presentation preview. This option may make you vulnerable to XSS.",
       "marp_official_site": "The Marp Official Site",
       "marp_official_site_link": "https://marp.app",
-      "presentation_docs" : "GROWI Docs - Create slides for a presentation",
-      "presentation_docs_link": "https://docs.growi.org/en/guide/features/presentation.html"
+      "marp_in_growi" : "GROWI Docs - Create slide using Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
     },
     "custom_title": "Custom title",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",

+ 2 - 2
apps/app/public/static/locales/ja_JP/admin.json

@@ -497,8 +497,8 @@
       "enable_marp_desc": "プレゼンテーション表示に Marp を利用できるようになります。ただし、XSS に対して脆弱になる恐れがあります。",
       "marp_official_site": "参考:Marp 公式サイト",
       "marp_official_site_link": "https://marp.app",
-      "presentation_docs" : "参考:GROWI Docs - プレゼンテーション機能を使う",
-      "presentation_docs_link": "https://docs.growi.org/ja/guide/features/presentation.html"
+      "marp_in_growi" : "参考:GROWI Docs - Marp でスライドを作成する",
+      "marp_in_growi_link": "https://docs.growi.org/ja/guide/features/marp.html"
     },
     "custom_title": "カスタム Title",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",

+ 2 - 2
apps/app/public/static/locales/zh_CN/admin.json

@@ -496,8 +496,8 @@
       "enable_marp_desc": "Marp 可在演示视图中使用。该选项可能会使您受到 XSS 的攻击。",
       "marp_official_site": "参考资料:Marp 官方网站",
       "marp_official_site_link": "https://marp.app",
-      "presentation_docs" : "参考资料:GROWI Docs - Create slides for a presentation",
-      "presentation_docs_link": "https://docs.growi.org/en/guide/features/presentation.html"
+      "marp_in_growi" : "参考资料:GROWI Docs - Create slide using Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
     },
     "custom_title": "自定义标题",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",

+ 3 - 0
apps/app/resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b607eb2ff757a116a1bf6bfac3702b38c8d5b2d20caa36654c31e8116c299ee7
+size 868836

+ 1 - 1
apps/app/src/client/services/page-operation.ts

@@ -88,7 +88,7 @@ export const resumeRenameOperation = async(pageId: string): Promise<void> => {
 };
 
 // TODO: define return type
-const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
+export const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
   // clone
   const params = Object.assign(tmpParams, {
     path: pagePath,

+ 1 - 11
apps/app/src/client/services/user-ui-settings.ts

@@ -18,7 +18,7 @@ const _putUserUISettingsInBulk = (): Promise<AxiosResponse<IUserUISettings>> =>
 const _putUserUISettingsInBulkDebounced = debounce(1500, _putUserUISettingsInBulk);
 
 type ScheduleToPutFunction = (settings: Partial<IUserUISettings>) => Promise<AxiosResponse<IUserUISettings>>;
-const scheduleToPut: ScheduleToPutFunction = (settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {
+export const scheduleToPut: ScheduleToPutFunction = (settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {
   settingsForBulk = {
     ...settingsForBulk,
     ...settings,
@@ -26,13 +26,3 @@ const scheduleToPut: ScheduleToPutFunction = (settings: Partial<IUserUISettings>
 
   return _putUserUISettingsInBulkDebounced();
 };
-
-type UserUISettingsUtil = {
-  scheduleToPut: ScheduleToPutFunction | (() => void),
-}
-export const useUserUISettings = (): UserUISettingsUtil => {
-
-  return {
-    scheduleToPut,
-  };
-};

+ 5 - 6
apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx

@@ -16,10 +16,8 @@ type Props = {
 
 const CustomizePresentationSetting = (props: Props): JSX.Element => {
   const { adminCustomizeContainer } = props;
-
-  console.log(adminCustomizeContainer);
-
   const { t } = useTranslation();
+
   const onClickSubmit = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizePresentation();
@@ -28,7 +26,8 @@ const CustomizePresentationSetting = (props: Props): JSX.Element => {
     catch (err) {
       toastError(err);
     }
-  }, [adminCustomizeContainer]);
+  }, [adminCustomizeContainer, t]);
+
   return (
     <React.Fragment>
       <h2 className="admin-setting-header">{t('admin:customize_settings.custom_presentation')}</h2>
@@ -51,10 +50,10 @@ const CustomizePresentationSetting = (props: Props): JSX.Element => {
               </a>
               <br></br>
               <a
-                href={`${t('admin:customize_settings.presentation_options.presentation_docs_link')}`}
+                href={`${t('admin:customize_settings.presentation_options.marp_in_gorwi_link')}`}
                 target="_blank"
                 rel="noopener noreferrer"
-              >{`${t('admin:customize_settings.presentation_options.presentation_docs')}`}
+              >{`${t('admin:customize_settings.presenattion_options.marp_in_growi')}`}
               </a>
             </p>
           </CustomizePresentationOption>

+ 10 - 12
apps/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx

@@ -4,14 +4,14 @@ import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { useSWRxSidebarConfig } from '~/stores/ui';
+import { useSWRxSidebarConfig } from '~/stores/admin/sidebar-config';
 import { useNextThemes } from '~/stores/use-next-themes';
 
 const CustomizeSidebarsetting = (): JSX.Element => {
   const { t } = useTranslation(['admin', 'commons']);
 
   const {
-    update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
+    update, isSidebarCollapsedMode, setIsSidebarCollapsedMode,
   } = useSWRxSidebarConfig();
 
   const { resolvedTheme } = useNextThemes();
@@ -45,8 +45,8 @@ const CustomizeSidebarsetting = (): JSX.Element => {
             <div id="layoutOptions" className="row row-cols-2">
               <div className="col">
                 <div
-                  className={`card customize-layout-card ${isSidebarDrawerMode ? 'border-active' : ''}`}
-                  onClick={() => setIsSidebarDrawerMode(true)}
+                  className={`card customize-layout-card ${isSidebarCollapsedMode ? 'border-active' : ''}`}
+                  onClick={() => setIsSidebarCollapsedMode(true)}
                   role="button"
                 >
                   <img src={drawerIconFileName} />
@@ -57,8 +57,8 @@ const CustomizeSidebarsetting = (): JSX.Element => {
               </div>
               <div className="col">
                 <div
-                  className={`card customize-layout-card ${!isSidebarDrawerMode ? 'border-active' : ''}`}
-                  onClick={() => setIsSidebarDrawerMode(false)}
+                  className={`card customize-layout-card ${!isSidebarCollapsedMode ? 'border-active' : ''}`}
+                  onClick={() => setIsSidebarCollapsedMode(false)}
                   role="button"
                 >
                   <img src={dockIconFileName} />
@@ -83,9 +83,8 @@ const CustomizeSidebarsetting = (): JSX.Element => {
                 id="is-open"
                 className="form-check-input"
                 name="mailVisibility"
-                checked={isSidebarDrawerMode === false && isSidebarClosedAtDockMode === false}
-                disabled={isSidebarDrawerMode}
-                onChange={() => setIsSidebarClosedAtDockMode(false)}
+                checked={isSidebarCollapsedMode === false}
+                onChange={() => setIsSidebarCollapsedMode(false)}
               />
               <label className="form-label form-check-label" htmlFor="is-open">
                 {t('customize_settings.default_sidebar_mode.dock_mode_default_open')}
@@ -97,9 +96,8 @@ const CustomizeSidebarsetting = (): JSX.Element => {
                 id="is-closed"
                 className="form-check-input"
                 name="mailVisibility"
-                checked={isSidebarDrawerMode === false && isSidebarClosedAtDockMode === true}
-                disabled={isSidebarDrawerMode}
-                onChange={() => setIsSidebarClosedAtDockMode(true)}
+                checked={isSidebarCollapsedMode === true}
+                onChange={() => setIsSidebarCollapsedMode(true)}
               />
               <label className="form-label form-check-label" htmlFor="is-closed">
                 {t('customize_settings.default_sidebar_mode.dock_mode_default_close')}

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.module.scss

@@ -1,2 +1,2 @@
-@use '@growi/ui/src/styles/molecules/page_list';
+@use '@growi/ui/scss/molecules/page_list';
 

+ 5 - 8
apps/app/src/components/Navbar/AuthorInfo.module.scss → apps/app/src/components/AuthorInfo/AuthorInfo.module.scss

@@ -4,10 +4,7 @@ $author-font-size: 12px;
 $date-font-size: 11px;
 
 .grw-author-info :global {
-  li {
-    font-size: $author-font-size;
-    list-style: none;
-  }
+  font-size: $author-font-size;
 
   .text-date {
     font-size: $date-font-size;
@@ -25,7 +22,7 @@ $date-font-size: 11px;
   }
 }
 
-.grw-author-info-skeleton :global {
-  width: 139px;
-  height: calc((#{$author-font-size} + #{$date-font-size}) * #{bs.$line-height-base});
-}
+// .grw-author-info-skeleton :global {
+//   width: 139px;
+//   height: calc((#{$author-font-size} + #{$date-font-size}) * #{bs.$line-height-base});
+// }

+ 5 - 1
apps/app/src/components/Navbar/AuthorInfo.tsx → apps/app/src/components/AuthorInfo/AuthorInfo.tsx

@@ -6,6 +6,10 @@ import { UserPicture } from '@growi/ui/dist/components';
 import { format } from 'date-fns';
 import Link from 'next/link';
 
+
+import styles from './AuthorInfo.module.scss';
+
+
 export type AuthorInfoProps = {
   date: Date,
   user: IUser,
@@ -59,7 +63,7 @@ export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => {
   };
 
   return (
-    <div className="d-flex align-items-center">
+    <div className={`grw-author-info ${styles['grw-author-info']} d-flex align-items-center`}>
       <div className="me-2">
         <UserPicture user={user} size="sm" />
       </div>

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

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

+ 0 - 17
apps/app/src/components/BookmarkButtons.module.scss

@@ -1,17 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-.btn-group-bookmark :global {
-  .btn-bookmark {
-    box-shadow: none !important;
-
-    @include bs.button-outline-variant(rgba(bs.$secondary, 50%), bs.$orange, rgba(lighten(bs.$orange, 20%), 0.5), rgba(lighten(bs.$orange, 20%), 0.5));
-
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: bs.$orange;
-    }
-    &:not(:disabled):not(.disabled):not(:hover) {
-      background-color: transparent;
-    }
-  }
-}

+ 1 - 1
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -233,7 +233,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
                 onClick={loadChildFolder}
               >
                 <div className="d-flex justify-content-center">
-                  <span className="material-icons-round">arrow_right</span>
+                  <span className="material-symbols-outlined">arrow_right</span>
                 </div>
               </button>
             )}

+ 2 - 5
apps/app/src/components/Page/CopyDropdown.jsx → apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx

@@ -25,7 +25,7 @@ const DropdownItemContents = ({ title, contents }) => (
 /* eslint-enable react/prop-types */
 
 
-const CopyDropdown = (props) => {
+export const CopyDropdown = (props) => {
   const [dropdownOpen, setDropdownOpen] = useState(false);
   const [tooltipOpen, setTooltipOpen] = useState(false);
   const [isParamsAppended, setParamsAppended] = useState(!props.isShareLinkMode);
@@ -110,7 +110,7 @@ const CopyDropdown = (props) => {
 
   return (
     <>
-      <Dropdown className={`${styles['grw-copy-dropdown']} grw-copy-dropdown`} isOpen={dropdownOpen} toggle={toggleDropdown}>
+      <Dropdown className={`${styles['grw-copy-dropdown']} grw-copy-dropdown d-print-none`} isOpen={dropdownOpen} toggle={toggleDropdown}>
         <DropdownToggle
           caret
           className={dropdownToggleClassName}
@@ -120,7 +120,6 @@ const CopyDropdown = (props) => {
 
         <DropdownMenu
           strategy="fixed"
-          style={{ zIndex: 1016 }} /* zIndex: 1016 // larger than z-index value of grw-subnav-fixed-container in GrowiSubNavigationSwitcher.module.scss */
         >
           <div className="d-flex align-items-center justify-content-between">
             <DropdownItem header className="px-3">
@@ -212,5 +211,3 @@ CopyDropdown.propTypes = {
   dropdownToggleClassName: PropTypes.string,
   isShareLinkMode: PropTypes.bool,
 };
-
-export default CopyDropdown;

+ 0 - 0
apps/app/src/components/Page/CopyDropdown.module.scss → apps/app/src/components/Common/CopyDropdown/CopyDropdown.module.scss


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

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

+ 18 - 0
apps/app/src/components/Common/DrawerToggler/DrawerToggler.module.scss

@@ -0,0 +1,18 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use '~/styles/variables' as var;
+
+
+.grw-drawer-toggler :global {
+  .btn {
+    --bs-btn-color: rgba(var(--bs-tertiary-color-rgb), 0.5);
+    --bs-btn-bg: transparent;
+
+    --bs-btn-hover-color: rgba(var(--bs-tertiary-color-rgb), 0.7);
+
+    width: var.$grw-sidebar-nav-width;
+    height: var.$grw-sidebar-nav-width;
+  }
+}

+ 36 - 0
apps/app/src/components/Common/DrawerToggler/DrawerToggler.tsx

@@ -0,0 +1,36 @@
+import { type ReactNode } from 'react';
+
+import { useDrawerOpened } from '~/stores/ui';
+
+
+import styles from './DrawerToggler.module.scss';
+
+const moduleClass = styles['grw-drawer-toggler'];
+
+
+type Props = {
+  className?: string,
+  children?: ReactNode,
+}
+
+export const DrawerToggler = (props: Props): JSX.Element => {
+
+  const { className, children } = props;
+
+  const { data: isOpened, mutate } = useDrawerOpened();
+
+  return (
+    <div className={`${moduleClass} ${className ?? ''}`}>
+      <button
+        className="btn d-flex align-items-center border-0"
+        type="button"
+        aria-expanded="false"
+        aria-label="Toggle navigation"
+        onClick={() => mutate(!isOpened)}
+      >
+        {children}
+      </button>
+    </div>
+  );
+
+};

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

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

+ 5 - 21
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -250,6 +250,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
 
   return (
     <DropdownMenu
+      className="d-print-none"
       data-testid="page-item-control-menu"
       end={alignEnd}
       container="body"
@@ -266,7 +267,6 @@ PageItemControlDropdownMenu.displayName = 'PageItemControl';
 
 type PageItemControlSubstanceProps = CommonProps & {
   pageId: string,
-  fetchOnInit?: boolean,
   children?: React.ReactNode,
   operationProcessData?: IPageOperationProcessData,
 }
@@ -274,12 +274,12 @@ type PageItemControlSubstanceProps = CommonProps & {
 export const PageItemControlSubstance = (props: PageItemControlSubstanceProps): JSX.Element => {
 
   const {
-    pageId, pageInfo: presetPageInfo, fetchOnInit, children, onClickBookmarkMenuItem, onClickRenameMenuItem,
+    pageId, pageInfo: presetPageInfo, children, onClickBookmarkMenuItem, onClickRenameMenuItem,
     onClickDuplicateMenuItem, onClickDeleteMenuItem, onClickPathRecoveryMenuItem,
   } = props;
 
   const [isOpen, setIsOpen] = useState(false);
-  const [shouldFetch, setShouldFetch] = useState(fetchOnInit ?? false);
+  const [shouldFetch, setShouldFetch] = useState(false);
 
   const { data: fetchedPageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(shouldFetch ? pageId : null);
 
@@ -336,10 +336,10 @@ export const PageItemControlSubstance = (props: PageItemControlSubstanceProps):
 
   return (
     <NotAvailableForGuest>
-      <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)} data-testid="open-page-item-control-btn">
+      <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)} className="grw-page-item-control" data-testid="open-page-item-control-btn">
         { children ?? (
           <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control d-flex align-items-center justify-content-center">
-            <i className="icon-options"></i>
+            <span className="material-symbols-outlined">more_vert</span>
           </DropdownToggle>
         ) }
 
@@ -377,19 +377,3 @@ export const PageItemControl = (props: PageItemControlProps): JSX.Element => {
 
   return <PageItemControlSubstance pageId={pageId} {...props} />;
 };
-
-
-type AsyncPageItemControlProps = Omit<CommonProps, 'pageInfo'> & {
-  pageId?: string,
-  children?: React.ReactNode,
-}
-
-export const AsyncPageItemControl = (props: AsyncPageItemControlProps): JSX.Element => {
-  const { pageId } = props;
-
-  if (pageId == null) {
-    return <></>;
-  }
-
-  return <PageItemControlSubstance pageId={pageId} fetchOnInit {...props} />;
-};

+ 6 - 0
apps/app/src/components/Common/PagePathHierarchicalLink/CollapsedParentsDropdown.module.scss

@@ -0,0 +1,6 @@
+
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+.collapsed-parents-dropdown-menu {
+  --bs-dropdown-zindex: #{bs.$zindex-fixed};
+}

+ 35 - 0
apps/app/src/components/Common/PagePathHierarchicalLink/CollapsedParentsDropdown.tsx

@@ -0,0 +1,35 @@
+import {
+  DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown,
+} from 'reactstrap';
+
+import LinkedPagePath from '~/models/linked-page-path';
+
+
+import styles from './CollapsedParentsDropdown.module.scss';
+
+
+type Props = {
+  linkedPagePath: LinkedPagePath,
+}
+
+export const CollapsedParentsDropdown = (props: Props): JSX.Element => {
+  const { linkedPagePath } = props;
+
+  return (
+    <UncontrolledDropdown className="d-inline-block">
+      <DropdownToggle color="transparent">...</DropdownToggle>
+      <DropdownMenu className={`dropdown-menu ${styles['collapsed-parents-dropdown-menu']}`} container="body">
+        {/* TODO: generate DropdownItems */}
+        <DropdownItem>
+          <a role="menuitem">foo</a>
+        </DropdownItem>
+        <DropdownItem>
+          <a role="menuitem">bar</a>
+        </DropdownItem>
+        <DropdownItem>
+          <a role="menuitem">baz</a>
+        </DropdownItem>
+      </DropdownMenu>
+    </UncontrolledDropdown>
+  );
+};

+ 4 - 0
apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss

@@ -0,0 +1,4 @@
+.separator {
+  margin-right: 0.2em;
+  margin-left: 0.2em;
+}

+ 8 - 11
apps/app/src/components/PagePathHierarchicalLink.tsx → apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx

@@ -3,7 +3,9 @@ import React, { memo, useCallback } from 'react';
 import Link from 'next/link';
 import urljoin from 'url-join';
 
-import LinkedPagePath from '../models/linked-page-path';
+import LinkedPagePath from '../../../models/linked-page-path';
+
+import styles from './PagePathHierarchicalLink.module.scss';
 
 
 type PagePathHierarchicalLinkProps = {
@@ -16,8 +18,7 @@ type PagePathHierarchicalLinkProps = {
   isInnerElem?: boolean,
 };
 
-// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JSX.Element => {
+export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JSX.Element => {
   const {
     linkedPagePath, linkedPagePathByHtml, basePath, isInTrash, isInnerElem,
   } = props;
@@ -26,7 +27,7 @@ const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JS
   const RootElm = useCallback(({ children }) => {
     return isInnerElem
       ? <>{children}</>
-      : <span className="grw-page-path-hierarchical-link text-break">{children}</span>;
+      : <span className="text-break">{children}</span>;
   }, [isInnerElem]);
 
   // render root element
@@ -43,7 +44,7 @@ const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JS
               <i className="icon-trash"></i>
             </Link>
           </span>
-          <span className="separator"><a href="/">/</a></span>
+          <span className={`separator ${styles.separator}`}><a href="/">/</a></span>
         </RootElm>
       )
       : (
@@ -51,7 +52,7 @@ const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JS
           <span className="path-segment">
             <Link href="/" prefetch={false}>
               <i className="icon-home"></i>
-              <span className="separator">/</span>
+              <span className={`separator ${styles.separator}`}>/</span>
             </Link>
           </span>
         </RootElm>
@@ -78,7 +79,7 @@ const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JS
         />
       ) }
       { isSeparatorRequired && (
-        <span className="separator">/</span>
+        <span className={`separator ${styles.separator}`}>/</span>
       ) }
 
       <Link href={href} prefetch={false} legacyBehavior>
@@ -93,7 +94,3 @@ const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkProps): JS
     </RootElm>
   );
 });
-PagePathHierarchicalLink.displayName = 'PagePathHierarchicalLink';
-
-
-export default PagePathHierarchicalLink;

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

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

+ 21 - 0
apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss

@@ -0,0 +1,21 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+.grw-mr-02em {
+  margin-right: 0.2em;
+}
+
+.grw-page-path-nav-sticky :global {
+  min-height: 75px;
+
+  .sticky-inner-wrapper {
+    z-index: bs.$zindex-sticky;
+  }
+
+  // set smaller font-size when sticky
+  .sticky-inner-wrapper.active {
+    h1 {
+      font-size: 1.75rem !important;
+    }
+  }
+}
+

+ 121 - 0
apps/app/src/components/Common/PagePathNav/PagePathNav.tsx

@@ -0,0 +1,121 @@
+import React, { FC } from 'react';
+
+import { DevidedPagePath } from '@growi/core/dist/models';
+import { pagePathUtils } from '@growi/core/dist/utils';
+import dynamic from 'next/dynamic';
+import Sticky from 'react-stickynode';
+
+import { useIsNotFound } from '~/stores/page';
+
+import LinkedPagePath from '../../../models/linked-page-path';
+import { PagePathHierarchicalLink } from '../PagePathHierarchicalLink';
+import { CollapsedParentsDropdown } from '../PagePathHierarchicalLink/CollapsedParentsDropdown';
+
+import styles from './PagePathNav.module.scss';
+
+
+const { isTrashPage } = pagePathUtils;
+
+type Props = {
+  pagePath: string,
+  pageId?: string | null,
+  isSingleLineMode?: boolean,
+  isCollapseParents?: boolean,
+  formerLinkClassName?: string,
+  latterLinkClassName?: string,
+}
+
+const CopyDropdown = dynamic(() => import('../CopyDropdown').then(mod => mod.CopyDropdown), { ssr: false });
+
+const Separator = (): JSX.Element => {
+  return <span className={styles['grw-mr-02em']}>/</span>;
+};
+
+export const PagePathNav: FC<Props> = (props: Props) => {
+  const {
+    pageId, pagePath, isSingleLineMode, isCollapseParents,
+    formerLinkClassName, latterLinkClassName,
+  } = props;
+  const dPagePath = new DevidedPagePath(pagePath, false, true);
+
+  const { data: isNotFound } = useIsNotFound();
+
+  const isInTrash = isTrashPage(pagePath);
+
+  let formerLink;
+  let latterLink;
+
+  // one line
+  if (dPagePath.isRoot || dPagePath.isFormerRoot || (!isCollapseParents && isSingleLineMode)) {
+    const linkedPagePath = new LinkedPagePath(pagePath);
+    latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePath} isInTrash={isInTrash} />;
+  }
+  // collapse parents
+  else if (isCollapseParents) {
+    const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
+    const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
+    latterLink = (
+      <>
+        <CollapsedParentsDropdown linkedPagePath={linkedPagePathFormer} />
+        <Separator />
+        <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} isInTrash={isInTrash} />
+      </>
+    );
+  }
+  // two line
+  else {
+    const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
+    const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
+    formerLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} isInTrash={isInTrash} />;
+    latterLink = (
+      <>
+        <Separator />
+        <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} isInTrash={isInTrash} />
+      </>
+    );
+  }
+
+  const copyDropdownId = `copydropdown-${pageId}`;
+  const copyDropdownToggleClassName = 'd-block btn-outline-secondary btn-copy border-0 text-muted p-2';
+
+  return (
+    <div>
+      <span className={formerLinkClassName}>{formerLink}</span>
+      <div className="d-flex align-items-center">
+        <h1 className={`m-0 text-truncate ${latterLinkClassName}`}>
+          {latterLink}
+        </h1>
+        { pageId != null && !isNotFound && (
+          <div className="mx-2">
+            <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName={copyDropdownToggleClassName}>
+              <i className="ti ti-clipboard"></i>
+            </CopyDropdown>
+          </div>
+        ) }
+      </div>
+    </div>
+  );
+};
+
+
+type PagePathNavStickyProps = Omit<Props, 'isCollapseParents'>;
+
+export const PagePathNavSticky = (props: PagePathNavStickyProps): JSX.Element => {
+  return (
+    // Controlling pointer-events
+    //  1. disable pointer-events with 'pe-none'
+    <Sticky className={`${styles['grw-page-path-nav-sticky']} mb-4`} innerClass="mt-1 pe-none" innerActiveClass="active">
+      {({ status }: { status: boolean }) => {
+        const isCollapseParents = status === Sticky.STATUS_FIXED;
+        return (
+          // Controlling pointer-events
+          //  2. enable pointer-events with 'pe-auto' only against the children
+          //      which width is minimized by 'd-inline-block'
+          <div className="d-inline-block pe-auto">
+            <PagePathNav {...props} isCollapseParents={isCollapseParents} latterLinkClassName={isCollapseParents ? 'fs-3' : 'fs-2'} />
+          </div>
+        );
+      }}
+    </Sticky>
+  );
+};

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

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

+ 0 - 0
apps/app/src/components/User/UserPictureList.jsx → apps/app/src/components/Common/UserPictureList.jsx


+ 4 - 12
apps/app/src/components/CompleteUserRegistrationForm.tsx

@@ -111,16 +111,12 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               <input type="hidden" name="token" value={token} />
 
               <div className="input-group">
-                <div>
-                  <span className="input-group-text"><i className="icon-envelope"></i></span>
-                </div>
+                <span className="input-group-text"><i className="icon-envelope"></i></span>
                 <input type="text" className="form-control" placeholder={t('Email')} disabled value={email} />
               </div>
 
               <div className="input-group" id="input-group-username">
-                <div>
-                  <span className="input-group-text"><i className="icon-user"></i></span>
-                </div>
+                <span className="input-group-text"><i className="icon-user"></i></span>
                 <input
                   type="text"
                   className="form-control"
@@ -138,9 +134,7 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               )}
 
               <div className="input-group">
-                <div>
-                  <span className="input-group-text"><i className="icon-tag"></i></span>
-                </div>
+                <span className="input-group-text"><i className="icon-tag"></i></span>
                 <input
                   type="text"
                   className="form-control"
@@ -154,9 +148,7 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
 
               <div className="input-group">
-                <div>
-                  <span className="input-group-text"><i className="icon-lock"></i></span>
-                </div>
+                <span className="input-group-text"><i className="icon-lock"></i></span>
                 <input
                   type="password"
                   className="form-control"

+ 23 - 0
apps/app/src/components/FontFamily/GlobalFonts.tsx

@@ -0,0 +1,23 @@
+import { memo } from 'react';
+
+import { useLatoFontFamily } from './use-lato';
+import { useMaterialSymbolsOutlined } from './use-material-symbols-outlined';
+import { useSourceHanCodeJP } from './use-source-han-code-jp';
+
+/**
+ * Define prefixed by '--grw-font-family'
+ */
+export const GlobalFonts = memo((): JSX.Element => {
+
+  const latoFontFamily = useLatoFontFamily();
+  const sourceHanCodeJPFontFamily = useSourceHanCodeJP();
+  const materialSymbolsOutlinedFontFamily = useMaterialSymbolsOutlined();
+
+  return (
+    <>
+      {latoFontFamily}
+      {sourceHanCodeJPFontFamily}
+      {materialSymbolsOutlinedFontFamily}
+    </>
+  );
+});

+ 1 - 0
apps/app/src/components/FontFamily/types.d.ts

@@ -0,0 +1 @@
+export type DefineStyle = () => JSX.IntrinsicElements.style;

+ 20 - 0
apps/app/src/components/FontFamily/use-lato.tsx

@@ -0,0 +1,20 @@
+import { Lato } from 'next/font/google';
+
+import { DefineStyle } from './types';
+
+const lato = Lato({
+  weight: ['400', '700'],
+  style: ['normal', 'italic'],
+  subsets: ['latin'],
+  display: 'swap',
+});
+
+export const useLatoFontFamily: DefineStyle = () => (
+  <style jsx global>
+    {`
+      :root {
+        --grw-font-family-lato: ${lato.style.fontFamily};
+      }
+    `}
+  </style>
+);

+ 18 - 0
apps/app/src/components/FontFamily/use-material-symbols-outlined.tsx

@@ -0,0 +1,18 @@
+import localFont from 'next/font/local';
+
+import { DefineStyle } from './types';
+
+const materialSymbolsOutlined = localFont({
+  src: '../../../resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2',
+  adjustFontFallback: false,
+});
+
+export const useMaterialSymbolsOutlined: DefineStyle = () => (
+  <style jsx global>
+    {`
+      :root {
+        --grw-font-family-material-symbols-outlined: ${materialSymbolsOutlined.style.fontFamily};
+      }
+    `}
+  </style>
+);

+ 23 - 0
apps/app/src/components/FontFamily/use-source-han-code-jp.tsx

@@ -0,0 +1,23 @@
+import localFont from 'next/font/local';
+
+import { DefineStyle } from './types';
+
+const sourceHanCodeJPSubsetMain = localFont({
+  src: '../../../resource/fonts/SourceHanCodeJP-Regular-subset-main.woff2',
+  display: 'optional',
+});
+const sourceHanCodeJPSubsetJis2 = localFont({
+  src: '../../../resource/fonts/SourceHanCodeJP-Regular-subset-jis2.woff2',
+  display: 'optional',
+});
+
+export const useSourceHanCodeJP: DefineStyle = () => (
+  <style jsx global>
+    {`
+      :root {
+        --grw-font-family-source-han-code-jp-subset-main: ${sourceHanCodeJPSubsetMain.style.fontFamily};
+        --grw-font-family-source-han-code-jp-subset-jis2: ${sourceHanCodeJPSubsetJis2.style.fontFamily};
+      }
+    `}
+  </style>
+);

+ 1 - 6
apps/app/src/components/IdenticalPathPage.module.scss

@@ -1,6 +1 @@
-@use '@growi/ui/src/styles/molecules/page_list';
-@use '~/styles/molecules/page-accessories-control';
-
-.grw-page-accessories-control :global {
-  @extend %grw-page-accessories-control;
-}
+@use '@growi/ui/scss/molecules/page_list';

+ 0 - 70
apps/app/src/components/InAppNotification/InAppNotificationElm.tsx

@@ -86,72 +86,6 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
 
   const actionUsers = getActionUsers();
 
-  const actionType: string = notification.action;
-  let actionMsg: string;
-  let actionIcon: string;
-
-  switch (actionType) {
-    case 'PAGE_LIKE':
-      actionMsg = 'liked';
-      actionIcon = 'icon-like';
-      break;
-    case 'PAGE_BOOKMARK':
-      actionMsg = 'bookmarked on';
-      actionIcon = 'icon-star';
-      break;
-    case 'PAGE_UPDATE':
-      actionMsg = 'updated on';
-      actionIcon = 'ti ti-agenda';
-      break;
-    case 'PAGE_RENAME':
-      actionMsg = 'renamed';
-      actionIcon = 'icon-action-redo';
-      break;
-    case 'PAGE_DUPLICATE':
-      actionMsg = 'duplicated';
-      actionIcon = 'icon-docs';
-      break;
-    case 'PAGE_DELETE':
-      actionMsg = 'deleted';
-      actionIcon = 'icon-trash';
-      break;
-    case 'PAGE_DELETE_COMPLETELY':
-      actionMsg = 'completely deleted';
-      actionIcon = 'icon-fire';
-      break;
-    case 'PAGE_REVERT':
-      actionMsg = 'reverted';
-      actionIcon = 'icon-action-undo';
-      break;
-    case 'PAGE_RECURSIVELY_RENAME':
-      actionMsg = 'renamed under';
-      actionIcon = 'icon-action-redo';
-      break;
-    case 'PAGE_RECURSIVELY_DELETE':
-      actionMsg = 'deleted under';
-      actionIcon = 'icon-trash';
-      break;
-    case 'PAGE_RECURSIVELY_DELETE_COMPLETELY':
-      actionMsg = 'deleted completely under';
-      actionIcon = 'icon-fire';
-      break;
-    case 'PAGE_RECURSIVELY_REVERT':
-      actionMsg = 'reverted under';
-      actionIcon = 'icon-action-undo';
-      break;
-    case 'COMMENT_CREATE':
-      actionMsg = 'commented on';
-      actionIcon = 'icon-bubble';
-      break;
-    case 'USER_REGISTRATION_APPROVAL_REQUEST':
-      actionMsg = 'requested registration approval';
-      actionIcon = 'icon-bubble';
-      break;
-    default:
-      actionMsg = '';
-      actionIcon = '';
-  }
-
   const isDropdownItem = props.type === 'dropdown-item';
 
   // determine tag
@@ -175,8 +109,6 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
           <PageModelNotification
             ref={notificationRef}
             notification={notification}
-            actionMsg={actionMsg}
-            actionIcon={actionIcon}
             actionUsers={actionUsers}
           />
         )}
@@ -184,8 +116,6 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
           <UserModelNotification
             ref={notificationRef}
             notification={notification}
-            actionMsg={actionMsg}
-            actionIcon={actionIcon}
             actionUsers={actionUsers}
           />
         )}

+ 45 - 0
apps/app/src/components/InAppNotification/PageNotification/ModelNotification.tsx

@@ -0,0 +1,45 @@
+import React, { FC, useImperativeHandle } from 'react';
+
+import type { HasObjectId } from '@growi/core';
+import { PagePathLabel } from '@growi/ui/dist/components';
+
+import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
+import type { IInAppNotification } from '~/interfaces/in-app-notification';
+
+import FormattedDistanceDate from '../../FormattedDistanceDate';
+
+type Props = {
+  notification: IInAppNotification & HasObjectId
+  actionMsg: string
+  actionIcon: string
+  actionUsers: string
+  publishOpen:() => void
+  ref: React.ForwardedRef<IInAppNotificationOpenable>
+};
+
+export const ModelNotification: FC<Props> = (props) => {
+  const {
+    notification, actionMsg, actionIcon, actionUsers, publishOpen, ref,
+  } = props;
+
+  useImperativeHandle(ref, () => ({
+    open() {
+      publishOpen();
+    },
+  }));
+
+  return (
+    <div className="p-2 overflow-hidden">
+      <div className="text-truncate">
+        <b>{actionUsers}</b> {actionMsg} <PagePathLabel path={notification.parsedSnapshot?.path ?? ''} />
+      </div>
+      <i className={`${actionIcon} me-2`} />
+      <FormattedDistanceDate
+        id={notification._id}
+        date={notification.createdAt}
+        isShowTooltip={false}
+        differenceForAvoidingFormat={Number.POSITIVE_INFINITY}
+      />
+    </div>
+  );
+};

+ 23 - 28
apps/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx

@@ -1,57 +1,52 @@
 import React, {
-  forwardRef, ForwardRefRenderFunction, useImperativeHandle,
+  forwardRef, ForwardRefRenderFunction,
 } from 'react';
 
 import type { HasObjectId } from '@growi/core';
-import { PagePathLabel } from '@growi/ui/dist/components/PagePath';
 import { useRouter } from 'next/router';
 
 import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import type { IInAppNotification } from '~/interfaces/in-app-notification';
 
-import FormattedDistanceDate from '../../FormattedDistanceDate';
+import { ModelNotification } from './ModelNotification';
+import { useActionMsgAndIconForPageModelNotification } from './useActionAndMsg';
+
 
 interface Props {
   notification: IInAppNotification & HasObjectId
-  actionMsg: string
-  actionIcon: string
   actionUsers: string
 }
 
 const PageModelNotification: ForwardRefRenderFunction<IInAppNotificationOpenable, Props> = (props: Props, ref) => {
 
   const {
-    notification, actionMsg, actionIcon, actionUsers,
+    notification, actionUsers,
   } = props;
 
+  const { actionMsg, actionIcon } = useActionMsgAndIconForPageModelNotification(notification);
+
   const router = useRouter();
 
   // publish open()
-  useImperativeHandle(ref, () => ({
-    open() {
-      if (notification.target != null) {
-        // jump to target page
-        const targetPagePath = notification.target.path;
-        if (targetPagePath != null) {
-          router.push(targetPagePath);
-        }
+  const publishOpen = () => {
+    if (notification.target != null) {
+      // jump to target page
+      const targetPagePath = notification.target.path;
+      if (targetPagePath != null) {
+        router.push(targetPagePath);
       }
-    },
-  }));
+    }
+  };
 
   return (
-    <div className="p-2 overflow-hidden">
-      <div className="text-truncate">
-        <b>{actionUsers}</b> {actionMsg} <PagePathLabel path={notification.parsedSnapshot?.path ?? ''} />
-      </div>
-      <i className={`${actionIcon} me-2`} />
-      <FormattedDistanceDate
-        id={notification._id}
-        date={notification.createdAt}
-        isShowTooltip={false}
-        differenceForAvoidingFormat={Number.POSITIVE_INFINITY}
-      />
-    </div>
+    <ModelNotification
+      notification={notification}
+      actionMsg={actionMsg}
+      actionIcon={actionIcon}
+      actionUsers={actionUsers}
+      publishOpen={publishOpen}
+      ref={ref}
+    />
   );
 };
 

+ 18 - 22
apps/app/src/components/InAppNotification/PageNotification/UserModelNotification.tsx

@@ -1,5 +1,5 @@
 import React, {
-  forwardRef, ForwardRefRenderFunction, useImperativeHandle,
+  forwardRef, ForwardRefRenderFunction,
 } from 'react';
 
 import type { HasObjectId } from '@growi/core';
@@ -8,38 +8,34 @@ import { useRouter } from 'next/router';
 import type { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import type { IInAppNotification } from '~/interfaces/in-app-notification';
 
-import FormattedDistanceDate from '../../FormattedDistanceDate';
+import { ModelNotification } from './ModelNotification';
+import { useActionMsgAndIconForUserModelNotification } from './useActionAndMsg';
+
 
 const UserModelNotification: ForwardRefRenderFunction<IInAppNotificationOpenable, {
   notification: IInAppNotification & HasObjectId
-  actionMsg: string
-  actionIcon: string
   actionUsers: string
 }> = ({
-  notification, actionMsg, actionIcon, actionUsers,
+  notification, actionUsers,
 }, ref) => {
   const router = useRouter();
 
+  const { actionMsg, actionIcon } = useActionMsgAndIconForUserModelNotification(notification);
+
   // publish open()
-  useImperativeHandle(ref, () => ({
-    open() {
-      router.push('/admin/users');
-    },
-  }));
+  const publishOpen = () => {
+    router.push('/admin/users');
+  };
 
   return (
-    <div className="p-2 overflow-hidden">
-      <div className="text-truncate">
-        <b>{actionUsers}</b> {actionMsg}
-      </div>
-      <i className={`${actionIcon} me-2`} />
-      <FormattedDistanceDate
-        id={notification._id}
-        date={notification.createdAt}
-        isShowTooltip={false}
-        differenceForAvoidingFormat={Number.POSITIVE_INFINITY}
-      />
-    </div>
+    <ModelNotification
+      notification={notification}
+      actionMsg={actionMsg}
+      actionIcon={actionIcon}
+      actionUsers={actionUsers}
+      publishOpen={publishOpen}
+      ref={ref}
+    />
   );
 };
 

+ 99 - 0
apps/app/src/components/InAppNotification/PageNotification/useActionAndMsg.ts

@@ -0,0 +1,99 @@
+import type { HasObjectId } from '@growi/core';
+
+import { SupportedAction } from '~/interfaces/activity';
+import type { IInAppNotification } from '~/interfaces/in-app-notification';
+
+export type ActionMsgAndIconType = {
+  actionMsg: string
+  actionIcon: string
+}
+
+export const useActionMsgAndIconForPageModelNotification = (notification: IInAppNotification & HasObjectId): ActionMsgAndIconType => {
+  const actionType: string = notification.action;
+  let actionMsg: string;
+  let actionIcon: string;
+
+  switch (actionType) {
+    case SupportedAction.ACTION_PAGE_LIKE:
+      actionMsg = 'liked';
+      actionIcon = 'icon-like';
+      break;
+    case SupportedAction.ACTION_PAGE_BOOKMARK:
+      actionMsg = 'bookmarked on';
+      actionIcon = 'icon-star';
+      break;
+    case SupportedAction.ACTION_PAGE_UPDATE:
+      actionMsg = 'updated on';
+      actionIcon = 'ti ti-agenda';
+      break;
+    case SupportedAction.ACTION_PAGE_RENAME:
+      actionMsg = 'renamed';
+      actionIcon = 'icon-action-redo';
+      break;
+    case SupportedAction.ACTION_PAGE_DUPLICATE:
+      actionMsg = 'duplicated';
+      actionIcon = 'icon-docs';
+      break;
+    case SupportedAction.ACTION_PAGE_DELETE:
+      actionMsg = 'deleted';
+      actionIcon = 'icon-trash';
+      break;
+    case SupportedAction.ACTION_PAGE_DELETE_COMPLETELY:
+      actionMsg = 'completely deleted';
+      actionIcon = 'icon-fire';
+      break;
+    case SupportedAction.ACTION_PAGE_REVERT:
+      actionMsg = 'reverted';
+      actionIcon = 'icon-action-undo';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_RENAME:
+      actionMsg = 'renamed under';
+      actionIcon = 'icon-action-redo';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_DELETE:
+      actionMsg = 'deleted under';
+      actionIcon = 'icon-trash';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_DELETE_COMPLETELY:
+      actionMsg = 'deleted completely under';
+      actionIcon = 'icon-fire';
+      break;
+    case SupportedAction.ACTION_PAGE_RECURSIVELY_REVERT:
+      actionMsg = 'reverted under';
+      actionIcon = 'icon-action-undo';
+      break;
+    case SupportedAction.ACTION_COMMENT_CREATE:
+      actionMsg = 'commented on';
+      actionIcon = 'icon-bubble';
+      break;
+    default:
+      actionMsg = '';
+      actionIcon = '';
+  }
+
+  return {
+    actionMsg,
+    actionIcon,
+  };
+};
+
+export const useActionMsgAndIconForUserModelNotification = (notification: IInAppNotification & HasObjectId): ActionMsgAndIconType => {
+  const actionType: string = notification.action;
+  let actionMsg: string;
+  let actionIcon: string;
+
+  switch (actionType) {
+    case SupportedAction.ACTION_USER_REGISTRATION_APPROVAL_REQUEST:
+      actionMsg = 'requested registration approval';
+      actionIcon = 'icon-bubble';
+      break;
+    default:
+      actionMsg = '';
+      actionIcon = '';
+  }
+
+  return {
+    actionMsg,
+    actionIcon,
+  };
+};

+ 4 - 12
apps/app/src/components/InstallerForm.tsx

@@ -147,9 +147,7 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className={`input-group mb-3${hasErrorClass}`}>
-            <div>
-              <span className="input-group-text"><i className="icon-user" /></span>
-            </div>
+            <span className="input-group-text"><i className="icon-user" /></span>
             <input
               data-testid="tiUsername"
               type="text"
@@ -163,9 +161,7 @@ const InstallerForm = memo((): JSX.Element => {
           <p className="form-text">{ unavailableUserId }</p>
 
           <div className="input-group mb-3">
-            <div>
-              <span className="input-group-text"><i className="icon-tag" /></span>
-            </div>
+            <span className="input-group-text"><i className="icon-tag" /></span>
             <input
               data-testid="tiName"
               type="text"
@@ -177,9 +173,7 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <div>
-              <span className="input-group-text"><i className="icon-envelope" /></span>
-            </div>
+            <span className="input-group-text"><i className="icon-envelope" /></span>
             <input
               data-testid="tiEmail"
               type="email"
@@ -191,9 +185,7 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <div>
-              <span className="input-group-text"><i className="icon-lock" /></span>
-            </div>
+            <span className="input-group-text"><i className="icon-lock" /></span>
             <input
               data-testid="tiPassword"
               type="password"

+ 12 - 20
apps/app/src/components/InvitedForm.tsx

@@ -82,11 +82,9 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
       <form role="form" onSubmit={submitHandler} id="invited-form">
         {/* Email Form */}
         <div className="input-group">
-          <div>
-            <span className="input-group-text">
-              <i className="icon-envelope"></i>
-            </span>
-          </div>
+          <span className="input-group-text">
+            <i className="icon-envelope"></i>
+          </span>
           <input
             type="text"
             className="form-control"
@@ -99,11 +97,9 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         </div>
         {/* UserID Form */}
         <div className="input-group" id="input-group-username">
-          <div>
-            <span className="input-group-text">
-              <i className="icon-user"></i>
-            </span>
-          </div>
+          <span className="input-group-text">
+            <i className="icon-user"></i>
+          </span>
           <input
             type="text"
             className="form-control"
@@ -115,11 +111,9 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         </div>
         {/* Name Form */}
         <div className="input-group">
-          <div>
-            <span className="input-group-text">
-              <i className="icon-tag"></i>
-            </span>
-          </div>
+          <span className="input-group-text">
+            <i className="icon-tag"></i>
+          </span>
           <input
             type="text"
             className="form-control"
@@ -131,11 +125,9 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         </div>
         {/* Password Form */}
         <div className="input-group">
-          <div>
-            <span className="input-group-text">
-              <i className="icon-lock"></i>
-            </span>
-          </div>
+          <span className="input-group-text">
+            <i className="icon-lock"></i>
+          </span>
           <input
             type="password"
             className="form-control"

+ 0 - 0
apps/app/src/components/Sidebar/PageTree/Item.module.scss → apps/app/src/components/ItemsTree/Item.module.scss


+ 1 - 1
apps/app/src/components/Sidebar/PageTree/ItemNode.ts → apps/app/src/components/ItemsTree/ItemNode.ts

@@ -1,4 +1,4 @@
-import { IPageForItem } from '../../../interfaces/page';
+import { IPageForItem } from '../../interfaces/page';
 
 export class ItemNode {
 

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

@@ -17,8 +17,6 @@ $grw-pagetree-item-container-height: 40px;
   }
 
   :global {
-    min-height: calc(100vh - ($grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
-
     .btn-page-item-control {
       .icon-plus::before {
         font-size: 18px;

+ 7 - 9
apps/app/src/components/Sidebar/PageTree/ItemsTree.tsx → apps/app/src/components/ItemsTree/ItemsTree.tsx

@@ -25,10 +25,9 @@ import { usePageTreeDescCountMap, useSidebarScrollerRef } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
 import loggerFactory from '~/utils/logger';
 
+import { ItemNode, SimpleItemProps } from '../TreeItem';
 
-import Item from './Item';
-import { ItemNode } from './ItemNode';
-import PageTreeContentSkeleton from './PageTreeContentSkeleton';
+import ItemsTreeContentSkeleton from './ItemsTreeContentSkeleton';
 
 import styles from './ItemsTree.module.scss';
 
@@ -93,14 +92,15 @@ type ItemsTreeProps = {
   targetPath: string
   targetPathOrId?: Nullable<string>
   targetAndAncestorsData?: TargetAndAncestors
+  CustomTreeItem: React.FunctionComponent<SimpleItemProps>
 }
 
 /*
  * ItemsTree
  */
-const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
+export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   const {
-    targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions, isReadOnlyUser,
+    targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions, isReadOnlyUser, CustomTreeItem,
   } = props;
 
   const { t } = useTranslation();
@@ -272,7 +272,7 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   if (initialItemNode != null) {
     return (
       <ul className={`grw-pagetree ${styles['grw-pagetree']} list-group py-3`} ref={rootElemRef}>
-        <Item
+        <CustomTreeItem
           key={initialItemNode.page.path}
           targetPathOrId={targetPathOrId}
           itemNode={initialItemNode}
@@ -287,7 +287,5 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
     );
   }
 
-  return <PageTreeContentSkeleton />;
+  return <ItemsTreeContentSkeleton />;
 };
-
-export default ItemsTree;

+ 2 - 2
apps/app/src/components/Sidebar/PageTree/PageTreeContentSkeleton.tsx → apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx

@@ -4,7 +4,7 @@ import { Skeleton } from '~/components/Skeleton';
 
 import styles from './ItemsTree.module.scss';
 
-const PageTreeContentSkeleton = (): JSX.Element => {
+const ItemsTreeContentSkeleton = (): JSX.Element => {
 
   return (
     <ul className={`grw-pagetree ${styles['grw-pagetree']} list-group py-3`}>
@@ -15,4 +15,4 @@ const PageTreeContentSkeleton = (): JSX.Element => {
   );
 };
 
-export default PageTreeContentSkeleton;
+export default ItemsTreeContentSkeleton;

+ 2 - 0
apps/app/src/components/ItemsTree/index.ts

@@ -0,0 +1,2 @@
+export { ItemNode } from './ItemNode';
+export * from './ItemsTree';

+ 0 - 10
apps/app/src/components/Layout/Admin.module.scss

@@ -5,16 +5,6 @@ $slack-work-space-name-card-background: #fff5ff;
 $slack-work-space-name-card-border: #efc1f6;
 
 .admin-page :global {
-  .title {
-    padding-top: 1rem;
-    padding-bottom: 1rem;
-
-    line-height: 1em;
-
-    @include mixins.variable-font-size(28px);
-    line-height: 1.1em;
-  }
-
   .admin-user-menu {
     .dropdown-menu {
       right: 0;

+ 1 - 1
apps/app/src/components/Layout/AdminLayout.tsx

@@ -29,7 +29,7 @@ const AdminLayout = ({
       <div className={`admin-page ${styles['admin-page']}`}>
 
         <header className="py-0 container-fluid">
-          <h1 className="title px-3">{componentTitle}</h1>
+          <h1 className="p-3 fs-2">{componentTitle}</h1>
         </header>
         <div id="main" className="main">
           <div className="container-fluid">

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

@@ -30,15 +30,18 @@ type Props = {
   className?: string
 }
 
+
 export const BasicLayout = ({ children, className }: Props): JSX.Element => {
   return (
     <RawLayout className={className ?? ''}>
       <DndProvider backend={HTML5Backend}>
 
         <div className="page-wrapper flex-row">
-          <Sidebar />
+          <div className="z-2">
+            <Sidebar />
+          </div>
 
-          <div className="flex-expand-vert">{/* neccessary for nested {children} make expanded */}
+          <div className="d-flex flex-grow-1 flex-column z-1">{/* neccessary for nested {children} make expanded */}
             <AlertSiteUrlUndefined />
             {children}
           </div>

+ 157 - 10
apps/app/src/components/Layout/NoLoginLayout.module.scss

@@ -1,4 +1,5 @@
-@use '@growi/core/scss/bootstrap/init' as *;
+@use '@growi/core/scss/bootstrap/init' as bs;
+@use '@growi/core/scss/growi-official-colors' as var;
 
 
 .nologin :global {
@@ -16,11 +17,11 @@
 
       .nologin-header {
         display: flex;
-        flex-direction: column;
         align-items: center;
         padding-top: 30px;
         padding-bottom: 10px;
       }
+
     }
 
   }
@@ -55,32 +56,32 @@
 
   $btn-fill-colors: (
     'login': (
-      rgba($danger, 0.4),
+      rgba(bs.$danger, 0.4),
       rgba(#7e4153, 0.7),
     ),
     'register': (
-      rgba($success, 0.4),
+      rgba(bs.$success, 0.4),
       rgba(#3f7263, 0.7),
     ),
     'google': (
       rgba(#24292e, 0.4),
-      $gray-700,
+      bs.$gray-700,
     ),
     'github': (
       rgba(lighten(black, 20%), 0.4),
-      $gray-700,
+      bs.$gray-700,
     ),
     'facebook': (
       rgba(#29487d, 0.4),
-      $gray-700,
+      bs.$gray-700,
     ),
     'oidc': (
       rgba(#24292e, 0.4),
-      $gray-700,
+      bs.$gray-700,
     ),
     'saml': (
       rgba(#55a79a, 0.4),
-      $gray-700,
+      bs.$gray-700,
     ),
   );
 
@@ -114,7 +115,7 @@
 }
 
 .link-switch {
-  color: $gray-200;
+  color: bs.$gray-200;
 
   &:hover {
     color: white;
@@ -126,3 +127,149 @@
     line-height: 1em;
   }
 }
+
+// Light mode color
+@include bs.color-mode(light) {
+  .nologin :global {
+    // background color
+    $color-gradient: #3c465c;
+    background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
+      linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
+      linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+
+    .nologin-header {
+      background-color: rgba(white, 0.5);
+
+      svg {
+        color: var(--bs-body-color);
+      }
+
+      .logo {
+        color: rgba(black, 0.5);
+        background-color: rgba(black, 0);
+      }
+
+      h1 {
+        color: rgba(black, 0.5);
+      }
+    }
+
+    .nologin-dialog {
+      background-color: rgba(white, 0.5);
+      .link-switch {
+        color: #1939b8;
+        &:hover {
+          color: lighten(#1939b8,20%);
+        }
+      }
+    }
+
+    .input-group {
+      .input-group-text {
+        color: darken(white, 30%);
+        background-color: rgba(bs.$gray-700, 0.7);
+      }
+
+      .form-control {
+        color: white;
+        background-color: rgba(bs.$gray-600, 0.7);
+        box-shadow: unset;
+
+        &::placeholder {
+          color: darken(white, 30%);
+        }
+      }
+    }
+
+    .link-growi-org {
+      color: rgba(black, 0.4);
+
+      &:hover,
+      &.focus {
+        color: black;
+
+        .growi {
+          color: darken(var.$growi-green, 20%);
+        }
+
+        .org {
+          color: darken(var.$growi-blue, 15%);
+        }
+      }
+    }
+  }
+}
+
+// Dark mode color
+@include bs.color-mode(dark) {
+  .nologin :global {
+    // background color
+    $color-gradient: #3c465c;
+    background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
+      linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
+      linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
+      linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+
+    .nologin-header {
+      background-color: rgba(black, 0.5);
+
+      svg {
+        color: var(--bs-body-color);
+      }
+
+      .logo {
+        color: rgba(white, 0.5);
+        background-color: rgba(white, 0);
+      }
+
+      h1 {
+        color: rgba(white, 0.5);
+      }
+    }
+
+    .nologin-dialog {
+      background-color: rgba(black, 0.5);
+      .link-switch {
+        color: #7b9bd5;
+        &:hover {
+          color: lighten(#7b9bd5,10%);
+        }
+      }
+    }
+
+    .input-group {
+      .input-group-text {
+        color: darken(white, 30%);
+        background-color: rgba(bs.$gray-700, 0.7);
+      }
+
+      .form-control {
+        color: white;
+        background-color: rgba(#505050, 0.7);
+        box-shadow: unset;
+
+        &::placeholder {
+          color: darken(white, 30%);
+        }
+      }
+    }
+
+    .link-growi-org {
+      color: rgba(white, 0.4);
+
+      &:hover,
+      &.focus {
+        color: rgba(white, 0.7);
+
+        .growi {
+          color: darken(var.$growi-green, 5%);
+        }
+
+        .org {
+          color: darken(var.$growi-blue, 5%);
+        }
+      }
+    }
+
+  }
+}

+ 3 - 3
apps/app/src/components/Layout/NoLoginLayout.tsx

@@ -26,13 +26,13 @@ export const NoLoginLayout = ({
 
   return (
     <RawLayout className={`nologin ${commonStyles.nologin} ${classNames}`}>
-      <div className="page-wrapper">
+      <div className="page-wrapper flex-row">
         <div className="main container-fluid">
 
           <div className="row">
 
-            <div className="col-md-12">
-              <div className="nologin-header mx-auto">
+            <div className="col-md-12 position-relative">
+              <div className="nologin-header mx-auto flex-column">
                 <GrowiLogo />
                 <h1 className="my-3">{ appTitle ?? 'GROWI' }</h1>
                 <div className="noLogin-form-errors px-3"></div>

+ 35 - 1
apps/app/src/components/Layout/PageViewLayout.module.scss

@@ -1,3 +1,37 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '~/styles/variables' as var;
+
+
 .page-view-layout :global {
-  min-height: calc(100vh - 116px - 250px); // 100vh - subnavigation height - page-comments-row minimum height
+  min-height: calc(100vh - 48px - 250px); // 100vh - subnavigation height - page-comments-row minimum height
+
+  .grw-side-contents-container {
+    margin-bottom: 1rem;
+
+    @include bs.media-breakpoint-up(lg) {
+      width: 250px;
+      min-width: 250px;
+      margin-left: 30px;
+    }
+  }
+}
+
+// md/lg layout padding
+.page-view-layout :global {
+  @include bs.media-breakpoint-between(md, xl) {
+    padding-left: var.$grw-sidebar-nav-width;
+  }
+}
+
+// sticky side contents
+.page-view-layout :global {
+  .grw-side-contents-sticky-container {
+    position: sticky;
+
+    $subnavigation-height: 50px;
+    $page-view-layout-margin-top: 32px;
+    $page-path-nav-height: 99px;
+    top: calc($subnavigation-height + $page-view-layout-margin-top + $page-path-nav-height + 4px);
+  }
 }

+ 6 - 4
apps/app/src/components/Layout/PageViewLayout.tsx

@@ -4,26 +4,28 @@ import styles from './PageViewLayout.module.scss';
 
 type Props = {
   children?: ReactNode,
+  headerContents?: ReactNode,
   sideContents?: ReactNode,
   footerContents?: ReactNode,
 }
 
 export const PageViewLayout = (props: Props): JSX.Element => {
   const {
-    children, sideContents, footerContents,
+    children, headerContents, sideContents, footerContents,
   } = props;
 
   return (
     <>
-      <div id="main" className={`main page-view-layout ${styles['page-view-layout']}`}>
+      <div id="main" className={`main ${styles['page-view-layout']}`}>
         <div id="content-main" className="content-main container-lg grw-container-convertible">
+          { headerContents != null && headerContents }
           { sideContents != null
             ? (
-              <div className="d-flex flex-column flex-column-reverse flex-lg-row">
+              <div className="d-flex gap-3">
                 <div className="flex-grow-1 flex-basis-0 mw-0">
                   {children}
                 </div>
-                <div className="grw-side-contents-container d-edit-none" data-vrt-blackout-side-contents>
+                <div className="grw-side-contents-container col-lg-3  d-edit-none d-print-none" data-vrt-blackout-side-contents>
                   <div className="grw-side-contents-sticky-container">
                     {sideContents}
                   </div>

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

@@ -35,32 +35,10 @@
   }
 
   .search-result-content {
-    .search-result-content-nav {
-      min-height: var.$grw-subnav-search-preview-min-height;
-      overflow: auto;
-
-      .grw-subnav {
-        min-height: inherit;
-      }
-    }
-
-    .search-result-content {
-
-      > h2 {
-        margin-right: 10px;
-        font-size: 22px;
-        line-height: 1em;
-      }
-
-      &:first-child > h2 {
-        margin-top: 0;
-      }
-
-      .search-result-content-body-container {
-        .wiki {
-          padding: 16px;
-          font-size: 14px;
-        }
+    .search-result-content-body-container {
+      .wiki {
+        margin-top: 2em;
+        font-size: 14px;
       }
     }
   }

+ 0 - 17
apps/app/src/components/LikeButtons.module.scss

@@ -1,17 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-.btn-group-like :global {
-  .btn-like {
-    box-shadow: none !important;
-
-    @include bs.button-outline-variant(rgba(bs.$secondary, 50%), lighten(bs.$red, 15%), rgba(lighten(bs.$red, 10%), 0.15), rgba(lighten(bs.$red, 10%), 0.5));
-
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: lighten(bs.$red, 15%);
-    }
-    &:not(:disabled):not(.disabled):not(:hover) {
-      background-color: transparent;
-    }
-  }
-}

+ 22 - 36
apps/app/src/components/LoginForm.tsx

@@ -195,11 +195,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
         <form role="form" onSubmit={handleLoginWithLocalSubmit} id="login-form">
           <div className="input-group">
-            <div>
-              <span className="input-group-text">
-                <i className="icon-user"></i>
-              </span>
-            </div>
+            <span className="input-group-text">
+              <i className="icon-user"></i>
+            </span>
             <input
               type="text"
               className="form-control rounded-0"
@@ -209,20 +207,16 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
               name="usernameForLogin"
             />
             {isLdapStrategySetup && (
-              <div>
-                <small className="input-group-text text-success">
-                  <i className="icon-fw icon-check"></i> LDAP
-                </small>
-              </div>
+              <small className="input-group-text text-success">
+                <i className="icon-fw icon-check"></i> LDAP
+              </small>
             )}
           </div>
 
           <div className="input-group">
-            <div>
-              <span className="input-group-text">
-                <i className="icon-lock"></i>
-              </span>
-            </div>
+            <span className="input-group-text">
+              <i className="icon-lock"></i>
+            </span>
             <input
               type="password"
               className="form-control rounded-0"
@@ -311,7 +305,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             type="button"
             className="btn btn-secondary btn-external-auth-tab btn-sm rounded-0 mb-3"
             data-bs-toggle={isExternalAuthCollapsible ? 'collapse' : ''}
-            data-target="#external-auth"
+            data-bs-target="#external-auth"
             aria-expanded="false"
             aria-controls="external-auth"
           >
@@ -421,11 +415,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {!isEmailAuthenticationEnabled && (
             <div>
               <div className="input-group" id="input-group-username">
-                <div>
-                  <span className="input-group-text">
-                    <i className="icon-user"></i>
-                  </span>
-                </div>
+                <span className="input-group-text">
+                  <i className="icon-user"></i>
+                </span>
                 {/* username */}
                 <input
                   type="text"
@@ -441,11 +433,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 <span id="help-block-username"></span>
               </p>
               <div className="input-group">
-                <div>
-                  <span className="input-group-text">
-                    <i className="icon-tag"></i>
-                  </span>
-                </div>
+                <span className="input-group-text">
+                  <i className="icon-tag"></i>
+                </span>
                 {/* name */}
                 <input
                   type="text"
@@ -461,11 +451,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           )}
 
           <div className="input-group">
-            <div>
-              <span className="input-group-text">
-                <i className="icon-envelope"></i>
-              </span>
-            </div>
+            <span className="input-group-text">
+              <i className="icon-envelope"></i>
+            </span>
             {/* email */}
             <input
               type="email"
@@ -497,11 +485,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {!isEmailAuthenticationEnabled && (
             <div>
               <div className="input-group">
-                <div>
-                  <span className="input-group-text">
-                    <i className="icon-lock"></i>
-                  </span>
-                </div>
+                <span className="input-group-text">
+                  <i className="icon-lock"></i>
+                </span>
                 {/* Password */}
                 <input
                   type="password"

+ 0 - 31
apps/app/src/components/Navbar/DrawerToggler.tsx

@@ -1,31 +0,0 @@
-import React from 'react';
-
-import { useDrawerOpened } from '~/stores/ui';
-
-type Props = {
-  iconClass?: string,
-}
-
-const DrawerToggler = (props: Props): JSX.Element => {
-
-  const { data: isOpened, mutate } = useDrawerOpened();
-
-  const iconClass = props.iconClass ?? isOpened
-    ? 'icon-arrow-left'
-    : 'icon-arrow-right';
-
-  return (
-    <button
-      className="grw-drawer-toggler btn btn-secondary"
-      type="button"
-      aria-expanded="false"
-      aria-label="Toggle navigation"
-      onClick={() => mutate(!isOpened)}
-    >
-      <i className={iconClass}></i>
-    </button>
-  );
-
-};
-
-export default DrawerToggler;

+ 13 - 0
apps/app/src/components/Navbar/GrowiContextualSubNavigation.module.scss

@@ -0,0 +1,13 @@
+@use '~/styles/mixins';
+
+.grw-contextual-sub-navigation :global {
+  background-color: rgba(var(--bs-body-bg-rgb), 0.7);
+  backdrop-filter: blur(35px);
+}
+
+@include mixins.editing() {
+  .grw-contextual-sub-navigation {
+    position: fixed;
+    right: 0;
+  }
+}

+ 84 - 138
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -2,7 +2,7 @@ import React, { useState, useCallback } from 'react';
 
 import { isPopulated } from '@growi/core';
 import type {
-  IUser, IPagePopulatedToShowRevision,
+  IPagePopulatedToShowRevision,
   IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity,
 } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
@@ -12,22 +12,23 @@ import { useRouter } from 'next/router';
 import { DropdownItem } from 'reactstrap';
 
 import { exportAsMarkdown, updateContentWidth } from '~/client/services/page-operation';
-import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
+import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
   useCurrentPathname,
-  useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId, useIsContainerFluid, useIsIdenticalPath,
+  useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId, useIsContainerFluid,
 } from '~/stores/context';
 import {
-  usePageAccessoriesModal, PageAccessoriesModalContents, IPageForPageDuplicateModal,
+  usePageAccessoriesModal, PageAccessoriesModalContents, type IPageForPageDuplicateModal,
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal, usePagePresentationModal,
 } from '~/stores/modal';
 import {
-  useSWRMUTxCurrentPage, useCurrentPageId, useIsNotFound, useSWRxPageInfo,
+  useSWRMUTxCurrentPage, useCurrentPageId, useSWRxPageInfo,
 } from '~/stores/page';
 import { mutatePageTree } from '~/stores/page-listing';
 import {
-  EditorMode, useDrawerMode, useEditorMode, useIsAbleToShowPageManagement,
-  useIsAbleToChangeEditorMode, useIsAbleToShowPageAuthors,
+  useEditorMode, useIsAbleToShowPageManagement,
+  useIsAbleToChangeEditorMode,
+  useSelectedGrant,
 } from '~/stores/ui';
 
 import CreateTemplateModal from '../CreateTemplateModal';
@@ -38,30 +39,18 @@ import ShareLinkIcon from '../Icons/ShareLinkIcon';
 import { NotAvailable } from '../NotAvailable';
 import { Skeleton } from '../Skeleton';
 
-import type { AuthorInfoProps } from './AuthorInfo';
-import { GrowiSubNavigation } from './GrowiSubNavigation';
-import type { SubNavButtonsProps } from './SubNavButtons';
-
-import AuthorInfoStyles from './AuthorInfo.module.scss';
+import styles from './GrowiContextualSubNavigation.module.scss';
 import PageEditorModeManagerStyles from './PageEditorModeManager.module.scss';
 
-const AuthorInfoSkeleton = () => <Skeleton additionalClass={`${AuthorInfoStyles['grw-author-info-skeleton']} py-1`} />;
-
-
 const PageEditorModeManager = dynamic(
   () => import('./PageEditorModeManager').then(mod => mod.PageEditorModeManager),
   { ssr: false, loading: () => <Skeleton additionalClass={`${PageEditorModeManagerStyles['grw-page-editor-mode-manager-skeleton']}`} /> },
 );
-// TODO: If enable skeleton, we get hydration error when create a page from PageCreateModal
-// { ssr: false, loading: () => <Skeleton additionalClass='btn-skeleton py-2' /> },
-const SubNavButtons = dynamic<SubNavButtonsProps>(
-  () => import('./SubNavButtons').then(mod => mod.SubNavButtons),
+const PageControls = dynamic(
+  () => import('../PageControls').then(mod => mod.PageControls),
   { ssr: false, loading: () => <></> },
 );
-const AuthorInfo = dynamic<AuthorInfoProps>(() => import('./AuthorInfo').then(mod => mod.AuthorInfo), {
-  ssr: false,
-  loading: AuthorInfoSkeleton,
-});
+
 
 type PageOperationMenuItemsProps = {
   pageId: string,
@@ -183,8 +172,7 @@ const CreateTemplateMenuItems = (props: CreateTemplateMenuItemsProps): JSX.Eleme
 
 type GrowiContextualSubNavigationProps = {
   currentPage?: IPagePopulatedToShowRevision | null,
-  isCompactMode?: boolean,
-  isLinkSharingDisabled: boolean,
+  isLinkSharingDisabled?: boolean,
 };
 
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
@@ -202,20 +190,17 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const revision = currentPage?.revision;
   const revisionId = (revision != null && isPopulated(revision)) ? revision._id : undefined;
 
-  const { data: isDrawerMode } = useDrawerMode();
-  const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
+  const { data: editorMode } = useEditorMode();
   const { data: pageId } = useCurrentPageId();
   const { data: currentUser } = useCurrentUser();
-  const { data: isNotFound } = useIsNotFound();
-  const { data: isIdenticalPath } = useIsIdenticalPath();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isContainerFluid } = useIsContainerFluid();
+  const { data: grantData } = useSelectedGrant();
 
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
   const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
-  const { data: isAbleToShowPageAuthors } = useIsAbleToShowPageAuthors();
 
   // TODO: implement tags for editor
   // refs: https://redmine.weseek.co.jp/issues/132125
@@ -229,6 +214,8 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId);
 
   const path = currentPage?.path ?? currentPathname;
+  const grant = currentPage?.grant ?? grantData?.grant;
+  const grantUserGroupId = currentPage?.grantedGroup?._id ?? grantData?.grantedGroup?.id;
 
   // TODO: implement tags for editor
   // refs: https://redmine.weseek.co.jp/issues/132125
@@ -250,10 +237,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
   const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);
 
-  const { isCompactMode, isLinkSharingDisabled } = props;
-
-  const isViewMode = editorMode === EditorMode.View;
-
+  const { isLinkSharingDisabled } = props;
 
   // TODO: implement tags for editor
   // refs: https://redmine.weseek.co.jp/issues/132125
@@ -309,124 +293,86 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     }
   }, [isSharedPage, mutateCurrentPage]);
 
-  const templateMenuItemClickHandler = useCallback(() => {
-    setIsPageTempleteModalShown(true);
-  }, []);
-
-
-  const RightComponent = () => {
-    const additionalMenuItemsRenderer = () => {
-      if (revisionId == null || pageId == null) {
-        return (
-          <>
-            {!isReadOnlyUser
-              && (
-                <CreateTemplateMenuItems
-                  onClickTemplateMenuItem={templateMenuItemClickHandler}
-                />
-              )
-            }
-          </>
-        );
-      }
+  const additionalMenuItemsRenderer = useCallback(() => {
+    if (revisionId == null || pageId == null) {
       return (
         <>
-          <PageOperationMenuItems
-            pageId={pageId}
-            revisionId={revisionId}
-            isLinkSharingDisabled={isLinkSharingDisabled}
-          />
-          {!isReadOnlyUser && (
-            <>
-              <DropdownItem divider />
+          {!isReadOnlyUser
+            && (
               <CreateTemplateMenuItems
-                onClickTemplateMenuItem={templateMenuItemClickHandler}
+                onClickTemplateMenuItem={() => setIsPageTempleteModalShown(true)}
               />
-            </>
-          )
+            )
           }
         </>
       );
-    };
-
+    }
     return (
       <>
-        <div className="d-flex">
-          <div className="d-flex flex-column align-items-end justify-content-center py-md-2" style={{ gap: `${isCompactMode ? '5px' : '7px'}` }}>
-            {isViewMode && (
-              <div className="h-50">
-                {pageId != null && (
-                  <SubNavButtons
-                    isCompactMode={isCompactMode}
-                    pageId={pageId}
-                    revisionId={revisionId}
-                    shareLinkId={shareLinkId}
-                    path={path ?? currentPathname} // If the page is empty, "path" is undefined
-                    expandContentWidth={currentPage?.expandContentWidth ?? isContainerFluid}
-                    disableSeenUserInfoPopover={isSharedUser}
-                    showPageControlDropdown={isAbleToShowPageManagement}
-                    additionalMenuItemRenderer={additionalMenuItemsRenderer}
-                    onClickDuplicateMenuItem={duplicateItemClickedHandler}
-                    onClickRenameMenuItem={renameItemClickedHandler}
-                    onClickDeleteMenuItem={deleteItemClickedHandler}
-                    onClickSwitchContentWidth={switchContentWidthHandler}
-                  />
-                )}
-              </div>
-            )}
-            {isAbleToChangeEditorMode && (
-              <PageEditorModeManager
-                editorMode={editorMode}
-                isBtnDisabled={!!isGuestUser || !!isReadOnlyUser}
-                onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
-              />
-            )}
-          </div>
-          {(isAbleToShowPageAuthors && !isCompactMode && !pagePathUtils.isUsersHomepage(path ?? '')) && (
-            <ul className={`${AuthorInfoStyles['grw-author-info']} text-nowrap border-start d-none d-lg-block d-edit-none py-2 ps-4 mb-0 ms-3`}>
-              <li className="pb-1">
-                {currentPage != null
-                  ? <AuthorInfo user={currentPage.creator as IUser} date={currentPage.createdAt} mode="create" locate="subnav" />
-                  : <AuthorInfoSkeleton />
-                }
-              </li>
-              <li className="mt-1 pt-1 border-top">
-                {currentPage != null
-                  ? <AuthorInfo user={currentPage.lastUpdateUser as IUser} date={currentPage.updatedAt} mode="update" locate="subnav" />
-                  : <AuthorInfoSkeleton />
-                }
-              </li>
-            </ul>
-          )}
-        </div>
-
-        {path != null && currentUser != null && !isReadOnlyUser && (
-          <CreateTemplateModal
-            path={path}
-            isOpen={isPageTemplateModalShown}
-            onClose={() => setIsPageTempleteModalShown(false)}
-          />
-        )}
+        <PageOperationMenuItems
+          pageId={pageId}
+          revisionId={revisionId}
+          isLinkSharingDisabled={isLinkSharingDisabled}
+        />
+        {!isReadOnlyUser && (
+          <>
+            <DropdownItem divider />
+            <CreateTemplateMenuItems
+              onClickTemplateMenuItem={() => setIsPageTempleteModalShown(true)}
+            />
+          </>
+        )
+        }
       </>
     );
-  };
-
-
-  const pagePath = isIdenticalPath || isNotFound
-    ? currentPathname
-    : currentPage?.path;
+  }, [isLinkSharingDisabled, isReadOnlyUser, pageId, revisionId]);
 
   return (
-    <GrowiSubNavigation
-      pagePath={pagePath}
-      pageId={currentPage?._id}
-      showDrawerToggler={isDrawerMode}
-      isDrawerMode={isDrawerMode}
-      isCompactMode={isCompactMode}
-      rightComponent={RightComponent}
-      additionalClasses={['container-fluid']}
-    />
+    <>
+      <div
+        className={`${styles['grw-contextual-sub-navigation']}
+          d-flex align-items-center justify-content-end px-2 py-1 gap-2 gap-md-4 d-print-none
+        `}
+        data-testid="grw-contextual-sub-nav"
+      >
+        {pageId != null && (
+          <PageControls
+            pageId={pageId}
+            revisionId={revisionId}
+            shareLinkId={shareLinkId}
+            path={path ?? currentPathname} // If the page is empty, "path" is undefined
+            expandContentWidth={currentPage?.expandContentWidth ?? isContainerFluid}
+            disableSeenUserInfoPopover={isSharedUser}
+            showPageControlDropdown={isAbleToShowPageManagement}
+            additionalMenuItemRenderer={additionalMenuItemsRenderer}
+            onClickDuplicateMenuItem={duplicateItemClickedHandler}
+            onClickRenameMenuItem={renameItemClickedHandler}
+            onClickDeleteMenuItem={deleteItemClickedHandler}
+            onClickSwitchContentWidth={switchContentWidthHandler}
+          />
+        )}
+
+        {isAbleToChangeEditorMode && (
+          <PageEditorModeManager
+            editorMode={editorMode}
+            isBtnDisabled={!!isGuestUser || !!isReadOnlyUser}
+            path={path}
+            grant={grant}
+            grantUserGroupId={grantUserGroupId}
+          />
+        )}
+      </div>
+
+      {path != null && currentUser != null && !isReadOnlyUser && (
+        <CreateTemplateModal
+          path={path}
+          isOpen={isPageTemplateModalShown}
+          onClose={() => setIsPageTempleteModalShown(false)}
+        />
+      )}
+    </>
   );
+
 };
 
 

+ 15 - 0
apps/app/src/components/Navbar/GrowiNavbarBottom.module.scss

@@ -14,3 +14,18 @@
     bottom: #{-1 * var.$grw-navbar-bottom-height};
   }
 }
+
+
+// centering icons
+.grw-navbar-bottom :global {
+  .nav-link {
+    display: flex;
+    align-items: center;
+  }
+}
+
+// == Colors
+.grw-navbar-bottom {
+  background-color: rgba(var(--bs-body-bg-rgb), 0.7);
+  backdrop-filter: blur(35px);
+}

+ 27 - 14
apps/app/src/components/Navbar/GrowiNavbarBottom.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import { useIsSearchPage } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
-import { useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
+import { useIsDeviceLargerThanMd, useDrawerOpened } from '~/stores/ui';
 
 import { GlobalSearch } from './GlobalSearch';
 
@@ -13,20 +13,20 @@ import styles from './GrowiNavbarBottom.module.scss';
 export const GrowiNavbarBottom = (): JSX.Element => {
 
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
   const { open: openCreateModal } = usePageCreateModal();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
 
-  const additionalClasses = ['grw-navbar-bottom', styles['grw-navbar-bottom']];
+  const additionalClasses = [styles['grw-navbar-bottom']];
   if (isDrawerOpened) {
     additionalClasses.push('grw-navbar-bottom-drawer-opened');
   }
 
   return (
-    <div className="d-md-none d-edit-none fixed-bottom">
+    <div className="d-md-none d-edit-none d-print-none fixed-bottom">
 
-      { isDeviceSmallerThanMd && !isSearchPage && (
+      { !isDeviceLargerThanMd && !isSearchPage && (
         <div id="grw-global-search-collapse" className="grw-global-search collapse bg-dark">
           <div className="p-3">
             <GlobalSearch dropup />
@@ -34,41 +34,54 @@ export const GrowiNavbarBottom = (): JSX.Element => {
         </div>
       ) }
 
-      <div className={`navbar navbar-expand navbar-dark bg-primary px-0 ${additionalClasses.join(' ')}`}>
+      <div className={`navbar navbar-expand px-4 px-sm-5 ${additionalClasses.join(' ')}`}>
 
-        <ul className="navbar-nav w-100">
-          <li className="nav-item me-auto">
+        <ul className="navbar-nav flex-grow-1 d-flex align-items-center justify-content-between">
+          <li className="nav-item">
             <a
               role="button"
               className="nav-link btn-lg"
               onClick={() => mutateDrawerOpened(true)}
             >
-              <i className="icon-menu"></i>
+              <span className="material-symbols-outlined fs-2">reorder</span>
             </a>
           </li>
+
+          <li className="nav-item">
+            <a
+              role="button"
+              className="nav-link btn-lg"
+              onClick={() => openCreateModal(currentPagePath || '')}
+            >
+              <span className="material-symbols-outlined fs-2">edit</span>
+            </a>
+          </li>
+
           {
             !isSearchPage && (
               <li className="nav-item">
                 <a
                   role="button"
                   className="nav-link btn-lg"
-                  data-target="#grw-global-search-collapse"
+                  data-bs-target="#grw-global-search-collapse"
                   data-bs-toggle="collapse"
                 >
-                  <i className="icon-magnifier"></i>
+                  <span className="material-symbols-outlined fs-2">search</span>
                 </a>
               </li>
             )
           }
-          <li className="nav-item ms-auto">
+
+          <li className="nav-item">
             <a
               role="button"
               className="nav-link btn-lg"
-              onClick={() => openCreateModal(currentPagePath || '')}
+              onClick={() => {}}
             >
-              <i className="icon-pencil"></i>
+              <span className="material-symbols-outlined fs-2">notifications</span>
             </a>
           </li>
+
         </ul>
       </div>
 

+ 12 - 9
apps/app/src/components/Navbar/PageEditorModeManager.module.scss

@@ -7,22 +7,25 @@
     --bs-btn-font-size: 13px;
     --bs-btn-border-width: 2px;
 
-    width: 70px;
+    width: 90px;
+    height: 38px;
 
-    white-space: nowrap;
+    @include bs.media-breakpoint-up(md) {
+      width: 70px;
+      height: 30px;
+    }
 
     @include mixins.border-vertical('before', 70%, 1, true);
-    .grw-page-editor-mode-manager-icon {
-      @include bs.media-breakpoint-down(sm) {
-        font-size: 16px;
-      }
-    }
   }
 }
 
 .grw-page-editor-mode-manager-skeleton :global {
-  width: 139px;
-  height: calc(var(--bs-btn-line-height) + bs.$btn-padding-y*2 + bs.$btn-border-width*2);
+  width: 90px;
+  height: 38px;
+  @include bs.media-breakpoint-up(md) {
+    width: 179px;
+    height: 30px;
+  }
 }
 
 // == Colors

+ 36 - 30
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -1,8 +1,10 @@
-import React, { type ReactNode, useCallback } from 'react';
+import React, { type ReactNode, useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { EditorMode, useIsDeviceSmallerThanMd } from '~/stores/ui';
+import { EditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
+
+import { useOnPageEditorModeButtonClicked } from './hooks';
 
 import styles from './PageEditorModeManager.module.scss';
 
@@ -10,17 +12,16 @@ import styles from './PageEditorModeManager.module.scss';
 type PageEditorModeButtonProps = {
   currentEditorMode: EditorMode,
   editorMode: EditorMode,
-  icon: ReactNode,
-  label: ReactNode,
+  children?: ReactNode,
   isBtnDisabled?: boolean,
   onClick?: (mode: EditorMode) => void,
 }
 const PageEditorModeButton = React.memo((props: PageEditorModeButtonProps) => {
   const {
-    currentEditorMode, isBtnDisabled, editorMode, icon, label, onClick,
+    currentEditorMode, isBtnDisabled, editorMode, children, onClick,
   } = props;
 
-  const classNames = ['btn btn-outline-primary px-1'];
+  const classNames = ['btn btn-outline-primary py-1 px-2 d-flex align-items-center justify-content-center'];
   if (currentEditorMode === editorMode) {
     classNames.push('active');
   }
@@ -35,38 +36,43 @@ const PageEditorModeButton = React.memo((props: PageEditorModeButtonProps) => {
       onClick={() => onClick?.(editorMode)}
       data-testid={`${editorMode}-button`}
     >
-      <span className="d-flex flex-column flex-md-row justify-content-center">
-        <span className="grw-page-editor-mode-manager-icon me-md-1">{icon}</span>
-        <span className="grw-page-editor-mode-manager-label">{label}</span>
-      </span>
+      {children}
     </button>
   );
 });
 
 type Props = {
   editorMode: EditorMode | undefined,
-  onPageEditorModeButtonClicked?: (editorMode: EditorMode) => void,
-  isBtnDisabled?: boolean,
+  isBtnDisabled: boolean,
+  path?: string,
+  grant?: number,
+  grantUserGroupId?: string
 }
 
 export const PageEditorModeManager = (props: Props): JSX.Element => {
   const {
     editorMode = EditorMode.View,
     isBtnDisabled,
-    onPageEditorModeButtonClicked,
+    path,
+    grant,
+    grantUserGroupId,
   } = props;
 
   const { t } = useTranslation();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const [isCreating, setIsCreating] = useState(false);
+
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
 
-  const pageEditorModeButtonClickedHandler = useCallback((viewType) => {
-    if (isBtnDisabled ?? false) {
+  const onPageEditorModeButtonClicked = useOnPageEditorModeButtonClicked(setIsCreating, path, grant, grantUserGroupId);
+  const _isBtnDisabled = isCreating || isBtnDisabled;
+
+  const pageEditorModeButtonClickedHandler = useCallback((viewType: EditorMode) => {
+    if (_isBtnDisabled) {
       return;
     }
-    if (onPageEditorModeButtonClicked != null) {
-      onPageEditorModeButtonClicked(viewType);
-    }
-  }, [isBtnDisabled, onPageEditorModeButtonClicked]);
+
+    onPageEditorModeButtonClicked?.(viewType);
+  }, [_isBtnDisabled, onPageEditorModeButtonClicked]);
 
   return (
     <>
@@ -76,25 +82,25 @@ export const PageEditorModeManager = (props: Props): JSX.Element => {
         aria-label="page-editor-mode-manager"
         id="grw-page-editor-mode-manager"
       >
-        {(!isDeviceSmallerThanMd || editorMode !== EditorMode.View) && (
+        {(isDeviceLargerThanMd || editorMode !== EditorMode.View) && (
           <PageEditorModeButton
             currentEditorMode={editorMode}
             editorMode={EditorMode.View}
-            isBtnDisabled={isBtnDisabled}
+            isBtnDisabled={_isBtnDisabled}
             onClick={pageEditorModeButtonClickedHandler}
-            icon={<i className="icon-control-play" />}
-            label={t('view')}
-          />
+          >
+            <span className="material-symbols-outlined fs-4">play_arrow</span>{t('View')}
+          </PageEditorModeButton>
         )}
-        {(!isDeviceSmallerThanMd || editorMode === EditorMode.View) && (
+        {(isDeviceLargerThanMd || editorMode === EditorMode.View) && (
           <PageEditorModeButton
             currentEditorMode={editorMode}
             editorMode={EditorMode.Editor}
-            isBtnDisabled={isBtnDisabled}
+            isBtnDisabled={_isBtnDisabled}
             onClick={pageEditorModeButtonClickedHandler}
-            icon={<i className="icon-note" />}
-            label={t('Edit')}
-          />
+          >
+            <span className="material-symbols-outlined me-1 fs-5">edit_square</span>{t('Edit')}
+          </PageEditorModeButton>
         )}
       </div>
     </>

+ 58 - 0
apps/app/src/components/Navbar/hooks.tsx

@@ -0,0 +1,58 @@
+import { useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
+
+import { createPage } from '~/client/services/page-operation';
+import { toastError } from '~/client/util/toastr';
+import { useIsNotFound } from '~/stores/page';
+import { EditorMode, useEditorMode } from '~/stores/ui';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:Navbar:GrowiContextualSubNavigation');
+
+export const useOnPageEditorModeButtonClicked = (
+    setIsCreating:React.Dispatch<React.SetStateAction<boolean>>,
+    path?: string,
+    grant?: number,
+    grantUserGroupId?: string,
+): (editorMode: EditorMode) => Promise<void> => {
+  const router = useRouter();
+  const { t } = useTranslation('commons');
+  const { data: isNotFound } = useIsNotFound();
+  const { mutate: mutateEditorMode } = useEditorMode();
+
+  return useCallback(async(editorMode: EditorMode) => {
+    if (isNotFound == null || path == null || grant == null) {
+      return;
+    }
+
+    if (editorMode === EditorMode.Editor && isNotFound) {
+      try {
+        setIsCreating(true);
+
+        const params = {
+          isSlackEnabled: false,
+          slackChannels: '',
+          grant,
+          pageTags: [],
+          grantUserGroupId,
+        };
+
+        const response = await createPage(path, '', params);
+
+        // Should not mutateEditorMode as it might prevent transitioning during mutation
+        router.push(`${response.page.id}#edit`);
+      }
+      catch (err) {
+        logger.warn(err);
+        toastError(t('toaster.create_failed', { target: path }));
+      }
+      finally {
+        setIsCreating(false);
+      }
+    }
+
+    mutateEditorMode(editorMode);
+  }, [grant, grantUserGroupId, isNotFound, mutateEditorMode, path, router, setIsCreating, t]);
+};

+ 0 - 3
apps/app/src/components/Page/DisplaySwitcher.tsx

@@ -12,7 +12,6 @@ import { LazyRenderer } from '../Common/LazyRenderer';
 
 
 const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
-const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
 
 
 type Props = {
@@ -37,8 +36,6 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
       <LazyRenderer shouldRender={isEditable === true && editorMode === EditorMode.Editor}>
         <PageEditor />
       </LazyRenderer>
-
-      { isEditable && !isViewMode && <EditorNavbarBottom /> }
     </>
   );
 };

+ 6 - 0
apps/app/src/components/Page/PageView.tsx

@@ -17,6 +17,7 @@ import { useIsMobile } from '~/stores/ui';
 
 
 import type { CommentsProps } from '../Comments';
+import { PagePathNavSticky } from '../Common/PagePathNav';
 import { PageViewLayout } from '../Layout/PageViewLayout';
 import { PageAlerts } from '../PageAlert/PageAlerts';
 import { PageContentFooter } from '../PageContentFooter';
@@ -98,6 +99,10 @@ export const PageView = (props: Props): JSX.Element => {
     }
   }, [isForbidden, isIdenticalPathPage, isNotCreatable]);
 
+  const headerContents = (
+    <PagePathNavSticky pageId={page?._id} pagePath={pagePath} />
+  );
+
   const sideContents = !isNotFound && !isNotCreatable
     ? (
       <PageSideContents page={page} />
@@ -141,6 +146,7 @@ export const PageView = (props: Props): JSX.Element => {
 
   return (
     <PageViewLayout
+      headerContents={headerContents}
       sideContents={sideContents}
       footerContents={footerContents}
     >

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
-import CopyDropdown from '../../Page/CopyDropdown';
+import { CopyDropdown } from '../../Common/CopyDropdown';
 
 
 type ShareLinkTrProps = {

+ 5 - 0
apps/app/src/components/PageAuthorInfo/PageAuthorInfo.module.scss

@@ -0,0 +1,5 @@
+.grw-page-author-info :global {
+  li {
+    list-style: none;
+  }
+}

+ 46 - 0
apps/app/src/components/PageAuthorInfo/PageAuthorInfo.tsx

@@ -0,0 +1,46 @@
+import { memo } from 'react';
+
+import type { IUser } from '@growi/core';
+import { pagePathUtils } from '@growi/core/dist/utils';
+
+import { useCurrentPathname } from '~/stores/context';
+import { useSWRxCurrentPage } from '~/stores/page';
+import { useIsAbleToShowPageAuthors } from '~/stores/ui';
+
+import { AuthorInfo } from '../AuthorInfo';
+
+
+import styles from './PageAuthorInfo.module.scss';
+
+
+export const PageAuthorInfo = memo((): JSX.Element => {
+  const { data: currentPage } = useSWRxCurrentPage();
+
+  const { data: currentPathname } = useCurrentPathname();
+  const { data: isAbleToShowPageAuthors } = useIsAbleToShowPageAuthors();
+
+  if (!isAbleToShowPageAuthors) {
+    return <></>;
+  }
+
+  const path = currentPage?.path ?? currentPathname;
+
+  if (pagePathUtils.isUsersHomepage(path ?? '')) {
+    return <></>;
+  }
+
+  return (
+    <ul className={`grw-page-author-info ${styles['grw-page-author-info']} text-nowrap border-start d-none d-lg-block d-edit-none py-2 ps-4 mb-0 ms-3`}>
+      <li className="pb-1">
+        {currentPage != null && (
+          <AuthorInfo user={currentPage.creator as IUser} date={currentPage.createdAt} mode="create" locate="subnav" />
+        )}
+      </li>
+      <li className="mt-1 pt-1 border-top">
+        {currentPage != null && (
+          <AuthorInfo user={currentPage.lastUpdateUser as IUser} date={currentPage.updatedAt} mode="update" locate="subnav" />
+        )}
+      </li>
+    </ul>
+  );
+});

+ 2 - 2
apps/app/src/components/PageContentFooter.tsx

@@ -3,11 +3,11 @@ import React from 'react';
 import type { IPage, IUser } from '@growi/core';
 import dynamic from 'next/dynamic';
 
-import type { AuthorInfoProps } from './Navbar/AuthorInfo';
+import type { AuthorInfoProps } from './AuthorInfo';
 
 import styles from './PageContentFooter.module.scss';
 
-const AuthorInfo = dynamic<AuthorInfoProps>(() => import('./Navbar/AuthorInfo').then(mod => mod.AuthorInfo), { ssr: false });
+const AuthorInfo = dynamic<AuthorInfoProps>(() => import('./AuthorInfo').then(mod => mod.AuthorInfo), { ssr: false });
 
 export type PageContentFooterProps = {
   page: IPage,

+ 26 - 0
apps/app/src/components/PageControls/BookmarkButtons.module.scss

@@ -0,0 +1,26 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use './button-styles';
+
+.btn-group-bookmark :global {
+  .btn-bookmark {
+    @extend %btn-basis;
+  }
+  .dropdown .btn-bookmark {
+    padding-right: 1px;
+  }
+  .total-counts {
+    @extend %btn-total-counts-basis;
+    padding-left: 5px;
+  }
+}
+
+// == Colors
+.btn-group-bookmark :global {
+  .btn-bookmark {
+    @include btn-muted.colorize(bs.$orange);
+  }
+}
+

+ 33 - 35
apps/app/src/components/BookmarkButtons.tsx → apps/app/src/components/PageControls/BookmarkButtons.tsx

@@ -11,22 +11,22 @@ import UncontrolledTooltip from 'reactstrap/esm/UncontrolledTooltip';
 import { useSWRxBookmarkedUsers } from '~/stores/bookmark';
 import { useIsGuestUser } from '~/stores/context';
 
-import { BookmarkFolderMenu } from './Bookmarks/BookmarkFolderMenu';
-import UserPictureList from './User/UserPictureList';
+import { BookmarkFolderMenu } from '../Bookmarks/BookmarkFolderMenu';
+import UserPictureList from '../Common/UserPictureList';
 
 import styles from './BookmarkButtons.module.scss';
+import popoverStyles from './user-list-popover.module.scss';
 
 interface Props {
   pageId: string,
   isBookmarked?: boolean,
   bookmarkCount: number,
-  hideTotalNumber?: boolean,
 }
 
 export const BookmarkButtons: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const {
-    pageId, isBookmarked, bookmarkCount, hideTotalNumber,
+    pageId, isBookmarked, bookmarkCount,
   } = props;
 
   const [isBookmarkFolderMenuOpen, setBookmarkFolderMenuOpen] = useState(false);
@@ -73,45 +73,43 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
         <DropdownToggle
           id="bookmark-dropdown-btn"
           color="transparent"
-          className={`shadow-none btn btn-bookmark border-0
+          className={`btn btn-bookmark rounded-end-0
           ${isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
         >
-          <i className={`fa ${isBookmarked ? 'fa-bookmark' : 'fa-bookmark-o'}`}></i>
+          <span className={`material-symbols-outlined ${isBookmarked ? 'fill' : ''}`}>
+            bookmark
+          </span>
         </DropdownToggle>
       </BookmarkFolderMenu>
       <UncontrolledTooltip placement="top" data-testid="bookmark-button-tooltip" target="bookmark-dropdown-btn" fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
 
-      { !hideTotalNumber && (
-        <>
-          <button
-            type="button"
-            id="po-total-bookmarks"
-            className={`shadow-none btn btn-bookmark border-0
-              total-bookmarks ${isBookmarked ? 'active' : ''}`}
-          >
-            {bookmarkCount}
-          </button>
-          <Popover placement="bottom" isOpen={isBookmarkUsersPopoverOpen} target="po-total-bookmarks" toggle={toggleBookmarkUsersPopover} trigger="legacy">
-            <PopoverBody className="user-list-popover">
-              { isLoadingBookmarkedUsers && <i className="fa fa-spinner fa-pulse"></i> }
-              { !isLoadingBookmarkedUsers && bookmarkedUsers != null && (
-                <>
-                  { bookmarkedUsers.length > 0
-                    ? (
-                      <div className="px-2 text-end user-list-content text-truncate text-muted">
-                        <UserPictureList users={bookmarkedUsers} />
-                      </div>
-                    )
-                    : t('No users have bookmarked yet')
-                  }
-                </>
-              ) }
-            </PopoverBody>
-          </Popover>
-        </>
-      ) }
+      <button
+        type="button"
+        id="po-total-bookmarks"
+        className={`btn btn-bookmark
+          total-counts ${isBookmarked ? 'active' : ''}`}
+      >
+        {bookmarkCount}
+      </button>
+      <Popover placement="bottom" isOpen={isBookmarkUsersPopoverOpen} target="po-total-bookmarks" toggle={toggleBookmarkUsersPopover} trigger="legacy">
+        <PopoverBody className={`user-list-popover ${popoverStyles['user-list-popover']}`}>
+          { isLoadingBookmarkedUsers && <i className="fa fa-spinner fa-pulse"></i> }
+          { !isLoadingBookmarkedUsers && bookmarkedUsers != null && (
+            <>
+              { bookmarkedUsers.length > 0
+                ? (
+                  <div className="px-2 text-end user-list-content text-truncate text-muted">
+                    <UserPictureList users={bookmarkedUsers} />
+                  </div>
+                )
+                : t('No users have bookmarked yet')
+              }
+            </>
+          ) }
+        </PopoverBody>
+      </Popover>
     </div>
   );
 };

+ 25 - 0
apps/app/src/components/PageControls/LikeButtons.module.scss

@@ -0,0 +1,25 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use './button-styles';
+
+.btn-group-like :global {
+  .btn-like {
+    @extend %btn-basis;
+  }
+  .btn-like#like-button {
+    padding-right: 3px;
+  }
+  .total-counts {
+    @extend %btn-total-counts-basis;
+    padding-left: 5px;
+  }
+}
+
+// == Colors
+.btn-group-like :global {
+  .btn-like {
+    @include btn-muted.colorize(bs.$red);
+  }
+}

+ 19 - 24
apps/app/src/components/LikeButtons.tsx → apps/app/src/components/PageControls/LikeButtons.tsx

@@ -5,13 +5,12 @@ import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip, Popover, PopoverBody } from 'reactstrap';
 
 
-import UserPictureList from './User/UserPictureList';
+import UserPictureList from '../Common/UserPictureList';
 
 import styles from './LikeButtons.module.scss';
 
 type LikeButtonsProps = {
 
-  hideTotalNumber?: boolean,
   sumOfLikers: number,
   likers: IUser[],
 
@@ -30,7 +29,7 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
   };
 
   const {
-    hideTotalNumber, isGuestUser, isLiked, sumOfLikers, onLikeClicked,
+    isGuestUser, isLiked, sumOfLikers, onLikeClicked,
   } = props;
 
   const getTooltipMessage = useCallback(() => {
@@ -47,35 +46,31 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
         type="button"
         id="like-button"
         onClick={onLikeClicked}
-        className={`shadow-none btn btn-like border-0
+        className={`btn btn-like
             ${isLiked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
       >
-        <i className={`fa ${isLiked ? 'fa-heart' : 'fa-heart-o'}`}></i>
+        <span className={`material-symbols-outlined ${isLiked ? 'fill' : ''}`}>favorite</span>
       </button>
 
       <UncontrolledTooltip data-testid="like-button-tooltip" placement="top" target="like-button" autohide={false} fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
 
-      { !hideTotalNumber && (
-        <>
-          <button
-            type="button"
-            id="po-total-likes"
-            className={`shadow-none btn btn-like border-0
-              total-likes ${isLiked ? 'active' : ''}`}
-          >
-            {sumOfLikers}
-          </button>
-          <Popover placement="bottom" isOpen={isPopoverOpen} target="po-total-likes" toggle={togglePopover} trigger="legacy">
-            <PopoverBody className="user-list-popover">
-              <div className="px-2 text-end user-list-content text-truncate text-muted">
-                {props.likers?.length ? <UserPictureList users={props.likers} /> : t('No users have liked this yet.')}
-              </div>
-            </PopoverBody>
-          </Popover>
-        </>
-      ) }
+      <button
+        type="button"
+        id="po-total-likes"
+        className={`btn btn-like
+          total-counts ${isLiked ? 'active' : ''}`}
+      >
+        {sumOfLikers}
+      </button>
+      <Popover placement="bottom" isOpen={isPopoverOpen} target="po-total-likes" toggle={togglePopover} trigger="legacy">
+        <PopoverBody className="user-list-popover">
+          <div className="px-2 text-end user-list-content text-truncate text-muted">
+            {props.likers?.length ? <UserPictureList users={props.likers} /> : t('No users have liked this yet.')}
+          </div>
+        </PopoverBody>
+      </Popover>
     </div>
   );
 

+ 20 - 0
apps/app/src/components/PageControls/PageControls.module.scss

@@ -0,0 +1,20 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use './button-styles';
+
+// PageItemControl styles
+.grw-page-controls :global {
+  .btn-page-item-control {
+    @extend %btn-basis;
+  }
+}
+
+// == Colors
+// PageItemControl colors
+.grw-page-controls :global {
+  .btn-page-item-control {
+    @include btn-muted.colorize(bs.$gray-500);
+  }
+}

+ 19 - 18
apps/app/src/components/Navbar/SubNavButtons.tsx → apps/app/src/components/PageControls/PageControls.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useMemo } from 'react';
+import React, { memo, useCallback, useMemo } from 'react';
 
 import type {
   IPageInfoForOperation, IPageToDeleteWithMeta, IPageToRenameWithMeta,
@@ -14,18 +14,22 @@ import {
 } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/toastr';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
-import { IPageForPageDuplicateModal } from '~/stores/modal';
+import type { IPageForPageDuplicateModal } from '~/stores/modal';
 
 import { useSWRxPageInfo } from '../../stores/page';
 import { useSWRxUsersList } from '../../stores/user';
-import { BookmarkButtons } from '../BookmarkButtons';
 import {
   AdditionalMenuItemsRendererProps, ForceHideMenuItems, MenuItemType,
   PageItemControl,
 } from '../Common/Dropdown/PageItemControl';
-import LikeButtons from '../LikeButtons';
-import SubscribeButton from '../SubscribeButton';
-import SeenUserInfo from '../User/SeenUserInfo';
+
+import { BookmarkButtons } from './BookmarkButtons';
+import LikeButtons from './LikeButtons';
+import SeenUserInfo from './SeenUserInfo';
+import SubscribeButton from './SubscribeButton';
+
+
+import styles from './PageControls.module.scss';
 
 
 type WideViewMenuItemProps = AdditionalMenuItemsRendererProps & {
@@ -63,7 +67,6 @@ const WideViewMenuItem = (props: WideViewMenuItemProps): JSX.Element => {
 
 
 type CommonProps = {
-  isCompactMode?: boolean,
   disableSeenUserInfoPopover?: boolean,
   showPageControlDropdown?: boolean,
   forceHideMenuItems?: ForceHideMenuItems,
@@ -74,7 +77,7 @@ type CommonProps = {
   onClickSwitchContentWidth?: (pageId: string, value: boolean) => void,
 }
 
-type SubNavButtonsSubstanceProps = CommonProps & {
+type PageControlsSubstanceProps = CommonProps & {
   pageId: string,
   shareLinkId?: string | null,
   revisionId: string | null,
@@ -83,11 +86,11 @@ type SubNavButtonsSubstanceProps = CommonProps & {
   expandContentWidth?: boolean,
 }
 
-const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element => {
+const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element => {
   const {
     pageInfo,
     pageId, revisionId, path, shareLinkId, expandContentWidth,
-    isCompactMode, disableSeenUserInfoPopover, showPageControlDropdown, forceHideMenuItems, additionalMenuItemRenderer,
+    disableSeenUserInfoPopover, showPageControlDropdown, forceHideMenuItems, additionalMenuItemRenderer,
     onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem, onClickSwitchContentWidth,
   } = props;
 
@@ -212,7 +215,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
   ];
 
   return (
-    <div className="d-flex" style={{ gap: '2px' }}>
+    <div className={`grw-page-controls ${styles['grw-page-controls']} d-flex`} style={{ gap: '2px' }}>
       {revisionId != null && (
         <SubscribeButton
           status={pageInfo.subscriptionStatus}
@@ -221,7 +224,6 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
       )}
       {revisionId != null && (
         <LikeButtons
-          hideTotalNumber={isCompactMode}
           onLikeClicked={likeClickhandler}
           sumOfLikers={sumOfLikers}
           isLiked={isLiked}
@@ -233,10 +235,9 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
           pageId={pageId}
           isBookmarked={pageInfo.isBookmarked}
           bookmarkCount={pageInfo.bookmarkCount}
-          hideTotalNumber={isCompactMode}
         />
       )}
-      {revisionId != null && !isCompactMode && (
+      {revisionId != null && (
         <SeenUserInfo
           seenUsers={seenUsers}
           sumOfSeenUsers={sumOfSeenUsers}
@@ -262,7 +263,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
   );
 };
 
-export type SubNavButtonsProps = CommonProps & {
+type PageControlsProps = CommonProps & {
   pageId: string,
   shareLinkId?: string | null,
   revisionId?: string | null,
@@ -270,7 +271,7 @@ export type SubNavButtonsProps = CommonProps & {
   expandContentWidth?: boolean,
 };
 
-export const SubNavButtons = (props: SubNavButtonsProps): JSX.Element => {
+export const PageControls = memo((props: PageControlsProps): JSX.Element => {
   const {
     pageId, revisionId, path, shareLinkId, expandContentWidth,
     onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem, onClickSwitchContentWidth,
@@ -287,7 +288,7 @@ export const SubNavButtons = (props: SubNavButtonsProps): JSX.Element => {
   }
 
   return (
-    <SubNavButtonsSubstance
+    <PageControlsSubstance
       {...props}
       pageInfo={pageInfo}
       pageId={pageId}
@@ -300,4 +301,4 @@ export const SubNavButtons = (props: SubNavButtonsProps): JSX.Element => {
       expandContentWidth={expandContentWidth}
     />
   );
-};
+});

+ 25 - 0
apps/app/src/components/PageControls/SeenUserInfo.module.scss

@@ -0,0 +1,25 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use './button-styles';
+
+.grw-seen-user-info :global {
+  .btn-seen-user {
+    @extend %btn-basis;
+  }
+  .total-counts {
+    @extend %text-total-counts-basis;
+  }
+}
+
+
+// == Colors
+
+.grw-seen-user-info :global {
+  $color: #549c79;
+
+  .btn-seen-user {
+    @include btn-muted.colorize($color);
+  }
+}

+ 6 - 7
apps/app/src/components/User/SeenUserInfo.tsx → apps/app/src/components/PageControls/SeenUserInfo.tsx

@@ -5,10 +5,11 @@ import { FootstampIcon } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip, Popover, PopoverBody } from 'reactstrap';
 
-import UserPictureList from './UserPictureList';
+import UserPictureList from '../Common/UserPictureList';
 
 
 import styles from './SeenUserInfo.module.scss';
+import popoverStyles from './user-list-popover.module.scss';
 
 
 interface Props {
@@ -27,14 +28,12 @@ const SeenUserInfo: FC<Props> = (props: Props) => {
 
   return (
     <div className={`grw-seen-user-info ${styles['grw-seen-user-info']}`}>
-      <button type="button" id="btn-seen-user" className="shadow-none btn btn-seen-user border-0">
-        <span className="me-1 footstamp-icon">
-          <FootstampIcon />
-        </span>
-        <span className="seen-user-count">{sumOfSeenUsers || seenUsers.length}</span>
+      <button type="button" id="btn-seen-user" className="shadow-none btn btn-seen-user border-0 d-flex align-items-center">
+        <span className="material-symbols-outlined me-1">footprint</span>
+        <span className="total-counts">{sumOfSeenUsers || seenUsers.length}</span>
       </button>
       <Popover placement="bottom" isOpen={isPopoverOpen} target="btn-seen-user" toggle={togglePopover} trigger="legacy" disabled={disabled}>
-        <PopoverBody className="user-list-popover">
+        <PopoverBody className={`user-list-popover ${popoverStyles['user-list-popover']}`}>
           <div className="px-2 text-end user-list-content text-truncate text-muted">
             <UserPictureList users={seenUsers} />
           </div>

+ 18 - 0
apps/app/src/components/PageControls/SubscribeButton.module.scss

@@ -0,0 +1,18 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use './button-styles';
+
+.btn-subscribe :global {
+  @extend %btn-basis;
+
+  .total-counts {
+    @extend %btn-total-counts-basis;
+  }
+}
+
+// == Colors
+.btn-subscribe {
+  @include btn-muted.colorize(bs.$success);
+}

+ 3 - 1
apps/app/src/components/SubscribeButton.tsx → apps/app/src/components/PageControls/SubscribeButton.tsx

@@ -36,7 +36,9 @@ const SubscribeButton: FC<Props> = (props: Props) => {
         className={`shadow-none btn btn-subscribe ${styles['btn-subscribe']} border-0
           ${isSubscribing ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
       >
-        <i className={`fa ${isSubscribing ? 'fa-bell' : 'fa-bell-slash-o'}`}></i>
+        <span className={`material-symbols-outlined ${isSubscribing ? 'fill' : ''}`}>
+          {isSubscribing ? 'notifications' : 'notifications_off'}
+        </span>
       </button>
 
       <UncontrolledTooltip data-testid="subscribe-button-tooltip" placement="top" target="subscribe-button" fade={false}>

+ 17 - 0
apps/app/src/components/PageControls/_button-styles.scss

@@ -0,0 +1,17 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+%btn-basis {
+  --bs-btn-padding-x: 6px;
+  --bs-btn-padding-y: 8px;
+  --bs-btn-line-height: 1em;
+  --bs-btn-border-width: 0;
+  --bs-btn-box-shadow: none;
+}
+
+%btn-total-counts-basis {
+  --bs-btn-font-size: 13px;
+}
+
+%text-total-counts-basis {
+  font-size: 13px;
+}

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

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

+ 12 - 0
apps/app/src/components/PageControls/user-list-popover.module.scss

@@ -0,0 +1,12 @@
+.user-list-popover :global {
+  --bs-popover-max-width: 200px;
+  --bs-popover-body-padding-x: .5rem;
+  --bs-popover-body-padding-y: .5rem;
+
+  .user-list-content {
+    direction: rtl;
+  }
+  .cls-1 {
+    isolation: isolate;
+  }
+}

+ 1 - 0
apps/app/src/components/PageEditor/Editor.tsx

@@ -24,6 +24,7 @@ import { Cheatsheet } from './Cheatsheet';
 import pasteHelper from './PasteHelper';
 import TextAreaEditor from './TextAreaEditor';
 
+
 import styles from './Editor.module.scss';
 
 export type EditorPropsType = {

+ 42 - 0
apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss

@@ -0,0 +1,42 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+@use '~/styles/variables' as var;
+@use '~/styles/mixins';
+
+@include mixins.editing() {
+  .grw-editor-navbar-bottom :global {
+    height: var.$grw-editor-navbar-bottom-height;
+
+    .grw-grant-selector {
+      @include bs.media-breakpoint-down(sm) {
+        .btn .label {
+          display: none;
+        }
+      }
+      @include bs.media-breakpoint-up(md) {
+        .dropdown-toggle {
+          min-width: 100px;
+
+          // caret
+          &::after {
+            margin-left: 1em;
+          }
+        }
+      }
+    }
+
+    .btn-submit {
+      width: 100px;
+    }
+
+    .btn-expand {
+      // rotate icon
+      i {
+        display: inline-block;
+        transition: transform 200ms;
+      }
+      &.expand i {
+        transform: rotate(-180deg);
+      }
+    }
+  }
+}

+ 15 - 22
apps/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -9,10 +9,15 @@ import { useIsSlackConfigured } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
 import {
-  useDrawerOpened, useEditorMode, useIsDeviceSmallerThanMd,
+  useDrawerOpened, useEditorMode, useIsDeviceLargerThanLg, useIsDeviceLargerThanMd,
 } from '~/stores/ui';
 
 
+import styles from './EditorNavbarBottom.module.scss';
+
+const moduleClass = styles['grw-editor-navbar-bottom'];
+
+
 const SavePageControls = dynamic<SavePageControlsProps>(() => import('~/components/SavePageControls').then(mod => mod.SavePageControls), { ssr: false });
 const SlackLogo = dynamic(() => import('~/components/SlackLogo').then(mod => mod.SlackLogo), { ssr: false });
 const SlackNotification = dynamic(() => import('~/components/SlackNotification').then(mod => mod.SlackNotification), { ssr: false });
@@ -26,13 +31,12 @@ const EditorNavbarBottom = (): JSX.Element => {
 
   const { data: editorMode } = useEditorMode();
   const { data: isSlackConfigured } = useIsSlackConfigured();
-  const { mutate: mutateDrawerOpened } = useDrawerOpened();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
+  const { data: isDeviceLargerThanLg } = useIsDeviceLargerThanLg();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
 
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
-  const additionalClasses = ['grw-editor-navbar-bottom'];
 
   const [slackChannelsStr, setSlackChannelsStr] = useState<string>('');
 
@@ -54,16 +58,6 @@ const EditorNavbarBottom = (): JSX.Element => {
   }, []);
 
 
-  const renderDrawerButton = () => (
-    <button
-      type="button"
-      className="btn btn-outline-secondary border-0"
-      onClick={() => mutateDrawerOpened(true)}
-    >
-      <i className="icon-menu"></i>
-    </button>
-  );
-
   const renderExpandButton = () => (
     <div className="d-md-none ms-2">
       <button
@@ -76,14 +70,14 @@ const EditorNavbarBottom = (): JSX.Element => {
     </div>
   );
 
-  const isCollapsedOptionsSelectorEnabled = isDeviceSmallerThanMd;
+  const isCollapsedOptionsSelectorEnabled = !isDeviceLargerThanLg;
 
   return (
     <div className={`${isCollapsedOptionsSelectorEnabled ? 'fixed-bottom' : ''} `}>
       {/* Collapsed SlackNotification */}
       {isSlackConfigured && (
-        <Collapse isOpen={isSlackExpanded && isDeviceSmallerThanMd === true}>
-          <nav className={`navbar navbar-expand-lg border-top ${additionalClasses.join(' ')}`}>
+        <Collapse isOpen={isSlackExpanded && !isDeviceLargerThanLg}>
+          <nav className={`navbar navbar-expand-lg border-top ${moduleClass}`}>
             {isSlackEnabled != null
             && (
               <SlackNotification
@@ -99,15 +93,14 @@ const EditorNavbarBottom = (): JSX.Element => {
         </Collapse>
       )
       }
-      <div className={`flex-expand-horiz align-items-center border-top px-2 px-md-3 ${additionalClasses.join(' ')}`}>
+      <div className={`flex-expand-horiz align-items-center border-top px-2 px-md-3 ${moduleClass}`}>
         <form>
-          { isDeviceSmallerThanMd && renderDrawerButton() }
-          { !isDeviceSmallerThanMd && <OptionsSelector /> }
+          { isDeviceLargerThanMd && <OptionsSelector /> }
         </form>
         <form className="flex-nowrap ms-auto">
           {/* Responsive Design for the SlackNotification */}
           {/* Button or the normal Slack banner */}
-          {isSlackConfigured && (isDeviceSmallerThanMd ? (
+          {isSlackConfigured && (!isDeviceLargerThanMd ? (
             <Button
               className="grw-btn-slack border me-2"
               onClick={() => (setSlackExpanded(!isSlackExpanded))}
@@ -139,7 +132,7 @@ const EditorNavbarBottom = (): JSX.Element => {
       { isCollapsedOptionsSelectorEnabled && (
         <Collapse isOpen={isExpanded}>
           <div className="px-2"> {/* set padding for border-top */}
-            <div className={`navbar navbar-expand border-top px-0 ${additionalClasses.join(' ')}`}>
+            <div className={`navbar navbar-expand border-top px-0 ${moduleClass}`}>
               <form className="ms-auto">
                 <OptionsSelector />
               </form>

Некоторые файлы не были показаны из-за большого количества измененных файлов