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

Merge branch 'dev/7.0.x' into support/131644-apply-colors-in-login-page

soumaeda 2 лет назад
Родитель
Сommit
829b75cfad
68 измененных файлов с 596 добавлено и 605 удалено
  1. 0 67
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss
  2. 2 16
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx
  3. 6 6
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  4. 1 0
      apps/app/package.json
  5. 5 8
      apps/app/src/components/AuthorInfo/AuthorInfo.module.scss
  6. 5 1
      apps/app/src/components/AuthorInfo/AuthorInfo.tsx
  7. 1 0
      apps/app/src/components/AuthorInfo/index.ts
  8. 1 4
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.jsx
  9. 0 0
      apps/app/src/components/Common/CopyDropdown/CopyDropdown.module.scss
  10. 1 0
      apps/app/src/components/Common/CopyDropdown/index.ts
  11. 6 0
      apps/app/src/components/Common/PagePathHierarchicalLink/CollapsedParentsDropdown.module.scss
  12. 35 0
      apps/app/src/components/Common/PagePathHierarchicalLink/CollapsedParentsDropdown.tsx
  13. 4 0
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss
  14. 8 11
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  15. 1 0
      apps/app/src/components/Common/PagePathHierarchicalLink/index.ts
  16. 21 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss
  17. 121 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  18. 1 0
      apps/app/src/components/Common/PagePathNav/index.ts
  19. 0 0
      apps/app/src/components/Common/UserPictureList.jsx
  20. 0 10
      apps/app/src/components/Layout/Admin.module.scss
  21. 1 1
      apps/app/src/components/Layout/AdminLayout.tsx
  22. 1 1
      apps/app/src/components/Layout/BasicLayout.tsx
  23. 1 1
      apps/app/src/components/Layout/PageViewLayout.module.scss
  24. 3 1
      apps/app/src/components/Layout/PageViewLayout.tsx
  25. 4 26
      apps/app/src/components/Layout/SearchResultLayout.module.scss
  26. 4 0
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.module.scss
  27. 78 135
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  28. 9 10
      apps/app/src/components/Navbar/PageEditorModeManager.module.scss
  29. 2 4
      apps/app/src/components/Navbar/PageEditorModeManager.tsx
  30. 6 0
      apps/app/src/components/Page/PageView.tsx
  31. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  32. 5 0
      apps/app/src/components/PageAuthorInfo/PageAuthorInfo.module.scss
  33. 46 0
      apps/app/src/components/PageAuthorInfo/PageAuthorInfo.tsx
  34. 2 2
      apps/app/src/components/PageContentFooter.tsx
  35. 0 0
      apps/app/src/components/PageControls/BookmarkButtons.module.scss
  36. 30 34
      apps/app/src/components/PageControls/BookmarkButtons.tsx
  37. 0 0
      apps/app/src/components/PageControls/LikeButtons.module.scss
  38. 17 22
      apps/app/src/components/PageControls/LikeButtons.tsx
  39. 30 0
      apps/app/src/components/PageControls/PageControls.module.scss
  40. 19 18
      apps/app/src/components/PageControls/PageControls.tsx
  41. 0 4
      apps/app/src/components/PageControls/SeenUserInfo.module.scss
  42. 4 3
      apps/app/src/components/PageControls/SeenUserInfo.tsx
  43. 0 0
      apps/app/src/components/PageControls/SubscribeButton.module.scss
  44. 0 0
      apps/app/src/components/PageControls/SubscribeButton.tsx
  45. 1 0
      apps/app/src/components/PageControls/index.ts
  46. 12 0
      apps/app/src/components/PageControls/user-list-popover.module.scss
  47. 1 1
      apps/app/src/components/PageList/PageListItemL.tsx
  48. 0 70
      apps/app/src/components/PagePathNav.tsx
  49. 1 1
      apps/app/src/components/SearchPage/SearchPageBase.tsx
  50. 0 6
      apps/app/src/components/SearchPage/SearchResultContent.module.scss
  51. 12 18
      apps/app/src/components/SearchPage/SearchResultContent.tsx
  52. 6 0
      apps/app/src/components/ShareLinkPageView.tsx
  53. 2 2
      apps/app/src/components/Sidebar/RecentChanges/RecentChangesSubstance.tsx
  54. 4 8
      apps/app/src/pages/[[...path]].page.tsx
  55. 3 5
      apps/app/src/pages/share/[[...path]].page.tsx
  56. 7 16
      apps/app/src/pages/trash.page.tsx
  57. 0 40
      apps/app/src/styles/_editor.scss
  58. 1 0
      apps/app/src/styles/_layout.scss
  59. 0 16
      apps/app/src/styles/_mixins.scss
  60. 0 6
      apps/app/src/styles/_page-path.scss
  61. 0 7
      apps/app/src/styles/_variables.scss
  62. 0 1
      apps/app/src/styles/style-app.scss
  63. 3 3
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts
  64. 12 12
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--click-page-icons.cy.ts
  65. 4 4
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--use-tools.cy.ts
  66. 1 1
      apps/app/test/cypress/e2e/22-sharelink/22-sharelink--access-to-sharelink.cy.ts
  67. 1 1
      apps/app/test/cypress/e2e/23-editor/23-editor--with-navigation.cy.ts
  68. 43 1
      yarn.lock

+ 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>

+ 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
   */

+ 1 - 0
apps/app/package.json

@@ -247,6 +247,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",

+ 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';

+ 1 - 4
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);
@@ -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';

+ 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


+ 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">

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

@@ -39,7 +39,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
         <div className="page-wrapper flex-row">
           <Sidebar />
 
-          <div className="flex-expand-vert">{/* neccessary for nested {children} make expanded */}
+          <div className="d-flex flex-grow-1 flex-column">{/* neccessary for nested {children} make expanded */}
             <AlertSiteUrlUndefined />
             {children}
           </div>

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

@@ -1,3 +1,3 @@
 .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
 }

+ 3 - 1
apps/app/src/components/Layout/PageViewLayout.tsx

@@ -4,19 +4,21 @@ 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="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">

+ 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;
       }
     }
   }

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

@@ -0,0 +1,4 @@
+.grw-contextual-sub-navigation :global {
+  background-color: rgba(var(--bs-body-bg-rgb), 0.7);
+  backdrop-filter: blur(35px);
+}

+ 78 - 135
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,22 @@ 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,
 } from '~/stores/ui';
 
 import CreateTemplateModal from '../CreateTemplateModal';
@@ -38,30 +38,19 @@ 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,12 +190,9 @@ 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: 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();
@@ -215,7 +200,6 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
   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
@@ -250,9 +234,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
@@ -309,124 +291,85 @@ 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={`grw-contextual-sub-navigation ${styles['grw-contextual-sub-navigation']}
+          d-flex align-items-center justify-content-end px-2 py-1 gap-2 gap-md-4
+        `}
+        data-testid="grw-contextual-sub-nav"
+      >
+        <div className="h-50">
+          {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}
+            />
+          )}
+        </div>
+        {isAbleToChangeEditorMode && (
+          <PageEditorModeManager
+            editorMode={editorMode}
+            isBtnDisabled={!!isGuestUser || !!isReadOnlyUser}
+            onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
+          />
+        )}
+      </div>
+
+      {path != null && currentUser != null && !isReadOnlyUser && (
+        <CreateTemplateModal
+          path={path}
+          isOpen={isPageTemplateModalShown}
+          onClose={() => setIsPageTempleteModalShown(false)}
+        />
+      )}
+    </>
   );
+
 };
 
 

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

@@ -7,22 +7,21 @@
     --bs-btn-font-size: 13px;
     --bs-btn-border-width: 2px;
 
-    width: 70px;
-
-    white-space: nowrap;
+    width: 90px;
+    @include bs.media-breakpoint-up(md) {
+      width: 70px;
+    }
 
     @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: 179px;
+  @include bs.media-breakpoint-down(sm) {
+    width: 90px;
+  }
+  height: 30px;
 }
 
 // == Colors

+ 2 - 4
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -35,10 +35,8 @@ 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>
+      <span className="me-1">{icon}</span>
+      <span>{label}</span>
     </button>
   );
 });

+ 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,

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


+ 30 - 34
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,7 +73,7 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
         <DropdownToggle
           id="bookmark-dropdown-btn"
           color="transparent"
-          className={`shadow-none btn btn-bookmark border-0
+          className={`shadow-none btn btn-bookmark border-0 rounded-end-0
           ${isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
         >
           <i className={`fa ${isBookmarked ? 'fa-bookmark' : 'fa-bookmark-o'}`}></i>
@@ -83,35 +83,31 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
         {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={`shadow-none btn btn-bookmark border-0
+          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>
   );
 };

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


+ 17 - 22
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(() => {
@@ -57,25 +56,21 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
         {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={`shadow-none btn btn-like border-0
+          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>
   );
 

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

@@ -0,0 +1,30 @@
+%page-controls-buttons-height {
+  height: 40px;
+}
+
+.grw-page-controls :global {
+
+  .btn-subscribe {
+    --bs-btn-font-size: 18px;
+    @extend %page-controls-buttons-height;
+  }
+
+  .btn-like,
+  .btn-bookmark,
+  .btn-seen-user {
+    --bs-btn-font-size: 18px;
+
+    @extend %page-controls-buttons-height;
+    padding-right: 6px;
+    padding-left: 8px;
+  }
+
+  .total-counts {
+    font-size: 13px;
+  }
+
+  .btn-page-item-control {
+    @extend %page-controls-buttons-height;
+  }
+
+}

+ 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}
     />
   );
-};
+});

+ 0 - 4
apps/app/src/components/User/SeenUserInfo.module.scss → apps/app/src/components/PageControls/SeenUserInfo.module.scss

@@ -6,14 +6,10 @@
     $color-seen-user: #549c79;
 
     @include bs.button-outline-variant($color-seen-user, $color-seen-user, rgba(lighten($color-seen-user, 10%), 0.15), rgba(lighten($color-seen-user, 10%), 0.5));
-    @include mixins-buttons.button-outline-svg-icon-variant($color-seen-user, $color-seen-user);
 
     &:not(:disabled):not(.disabled):active,
     &:not(:disabled):not(.disabled).active {
       color: $color-seen-user;
-      svg {
-        fill: $color-seen-user;
-      }
     }
     &:not(:disabled):not(.disabled):not(:hover) {
       background-color: transparent;

+ 4 - 3
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 {
@@ -31,10 +32,10 @@ const SeenUserInfo: FC<Props> = (props: Props) => {
         <span className="me-1 footstamp-icon">
           <FootstampIcon />
         </span>
-        <span className="seen-user-count">{sumOfSeenUsers || seenUsers.length}</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>

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


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


+ 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 - 1
apps/app/src/components/PageList/PageListItemL.tsx

@@ -33,7 +33,7 @@ import { useIsDeviceSmallerThanLg } from '~/stores/ui';
 
 import { useSWRMUTxPageInfo, useSWRxPageInfo } from '../../stores/page';
 import { ForceHideMenuItems, PageItemControl } from '../Common/Dropdown/PageItemControl';
-import PagePathHierarchicalLink from '../PagePathHierarchicalLink';
+import { PagePathHierarchicalLink } from '../Common/PagePathHierarchicalLink';
 
 type Props = {
   page: IPageWithSearchMeta | IPageWithMeta<IPageInfoForListing & IPageSearchMeta>,

+ 0 - 70
apps/app/src/components/PagePathNav.tsx

@@ -1,70 +0,0 @@
-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 { useIsNotFound } from '~/stores/page';
-
-import LinkedPagePath from '../models/linked-page-path';
-
-import PagePathHierarchicalLink from './PagePathHierarchicalLink';
-
-const { isTrashPage } = pagePathUtils;
-
-type Props = {
-  pagePath: string,
-  pageId?: string | null,
-  isSingleLineMode?:boolean,
-  isCompactMode?:boolean,
-}
-
-const CopyDropdown = dynamic(() => import('./Page/CopyDropdown'), { ssr: false });
-
-const PagePathNav: FC<Props> = (props: Props) => {
-  const {
-    pageId, pagePath, isSingleLineMode, isCompactMode,
-  } = 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 || isSingleLineMode) {
-    const linkedPagePath = new LinkedPagePath(pagePath);
-    latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePath} isInTrash={isInTrash} />;
-  }
-  // two line
-  else {
-    const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
-    const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
-    formerLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} isInTrash={isInTrash} />;
-    latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} isInTrash={isInTrash} />;
-  }
-
-  const copyDropdownId = `copydropdown${isCompactMode ? '-subnav-compact' : ''}-${pageId}`;
-  const copyDropdownToggleClassName = 'd-block btn-outline-secondary btn-copy border-0 text-muted p-2';
-
-  return (
-    <div className="grw-page-path-nav">
-      {formerLink}
-      <div className="d-flex align-items-center">
-        <h1 className="m-0">{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>
-  );
-};
-
-export default PagePathNav;

+ 1 - 1
apps/app/src/components/SearchPage/SearchPageBase.tsx

@@ -213,7 +213,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
 
       </div>
 
-      <div className="flex-expand-vert d-none d-lg-flex search-result-content">
+      <div className="flex-expand-vert d-none d-lg-flex">
         {pages != null && pages.length !== 0 && selectedPageWithMeta != null && (
           <SearchResultContent
             pageWithMeta={selectedPageWithMeta}

+ 0 - 6
apps/app/src/components/SearchPage/SearchResultContent.module.scss

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

+ 12 - 18
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -24,8 +24,7 @@ import { useSearchResultOptions } from '~/stores/renderer';
 import { mutateSearching } from '~/stores/search';
 
 import type { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
-import type { GrowiSubNavigationProps } from '../Navbar/GrowiSubNavigation';
-import type { SubNavButtonsProps } from '../Navbar/SubNavButtons';
+import { PagePathNav } from '../Common/PagePathNav';
 import { type RevisionLoaderProps } from '../Page/RevisionLoader';
 import { type PageCommentProps } from '../PageComment';
 import type { PageContentFooterProps } from '../PageContentFooter';
@@ -33,8 +32,7 @@ import type { PageContentFooterProps } from '../PageContentFooter';
 import styles from './SearchResultContent.module.scss';
 
 
-const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
-const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
+const SubNavButtons = dynamic(() => import('../PageControls').then(mod => mod.PageControls), { ssr: false });
 const RevisionLoader = dynamic<RevisionLoaderProps>(() => import('../Page/RevisionLoader').then(mod => mod.RevisionLoader), { ssr: false });
 const PageComment = dynamic<PageCommentProps>(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
 const PageContentFooter = dynamic<PageContentFooterProps>(() => import('../PageContentFooter').then(mod => mod.PageContentFooter), { ssr: false });
@@ -187,7 +185,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     const revisionId = getIdForRef(page.revision);
 
     return (
-      <div className="d-flex flex-column align-items-end justify-content-center py-md-2">
+      <div className="d-flex flex-column align-items-end justify-content-center px-2 py-1">
         <SubNavButtons
           pageId={page._id}
           revisionId={revisionId}
@@ -196,7 +194,6 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           showPageControlDropdown={showPageControlDropdown}
           forceHideMenuItems={forceHideMenuItems}
           additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />}
-          isCompactMode
           onClickDuplicateMenuItem={duplicateItemClickedHandler}
           onClickRenameMenuItem={renameItemClickedHandler}
           onClickDeleteMenuItem={deleteItemClickedHandler}
@@ -215,21 +212,18 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       data-testid="search-result-content"
       className={`dynamic-layout-root ${growiLayoutFluidClass} search-result-content ${styles['search-result-content']}`}
     >
-      <div className="grw-page-path-text-muted-container">
-        { isRenderable && (
-          <GrowiSubNavigation
-            pagePath={page.path}
-            pageId={page._id}
-            rightComponent={RightComponent}
-            isCompactMode
-            additionalClasses={['px-4']}
-          />
-        ) }
-      </div>
+      <RightComponent />
+
+      { isRenderable && (
+        <div className="container-lg grw-container-convertible pt-2 pb-2">
+          <PagePathNav pageId={page._id} pagePath={page.path} formerLinkClassName="small" latterLinkClassName="fs-3" />
+        </div>
+      ) }
+
       <div
         id="search-result-content-body-container"
         ref={scrollElementRef}
-        className="search-result-content-body-container main container-lg grw-container-convertible overflow-y-scroll"
+        className="search-result-content-body-container container-lg grw-container-convertible overflow-y-scroll"
       >
         { isRenderable && (
           <RevisionLoader

+ 6 - 0
apps/app/src/components/ShareLinkPageView.tsx

@@ -10,6 +10,7 @@ import { useIsNotFound } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
+import { PagePathNavSticky } from './Common/PagePathNav';
 import { PageViewLayout } from './Layout/PageViewLayout';
 import RevisionRenderer from './Page/RevisionRenderer';
 import ShareLinkAlert from './Page/ShareLinkAlert';
@@ -51,6 +52,10 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     }
   }, [disableLinkSharing, props.disableLinkSharing]);
 
+  const headerContents = (
+    <PagePathNavSticky pageId={page?._id} pagePath={pagePath} />
+  );
+
   const sideContents = !isNotFound
     ? (
       <PageSideContents page={page} />
@@ -86,6 +91,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
 
   return (
     <PageViewLayout
+      headerContents={headerContents}
       sideContents={sideContents}
     >
       { specialContents }

+ 2 - 2
apps/app/src/components/Sidebar/RecentChanges/RecentChangesSubstance.tsx

@@ -7,9 +7,9 @@ import { DevidedPagePath } from '@growi/core/dist/models';
 import { UserPicture, FootstampIcon } from '@growi/ui/dist/components';
 
 import { useKeywordManager } from '~/client/services/search-operation';
+import { PagePathHierarchicalLink } from '~/components/Common/PagePathHierarchicalLink';
 import FormattedDistanceDate from '~/components/FormattedDistanceDate';
 import InfiniteScroll from '~/components/InfiniteScroll';
-import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
 import LinkedPagePath from '~/models/linked-page-path';
 import { useSWRINFxRecentlyUpdated } from '~/stores/page-listing';
 import loggerFactory from '~/utils/logger';
@@ -53,7 +53,7 @@ const PageItem = memo(({ page, isSmall, onClickTag }: PageItemProps): JSX.Elemen
   const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
   const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
   const FormerLink = () => (
-    <div className="grw-page-path-text-muted-container small">
+    <div className="small">
       <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} />
     </div>
   );

+ 4 - 8
apps/app/src/pages/[[...path]].page.tsx

@@ -120,9 +120,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { isLinkSharingDisabled } = props;
   const { data: currentPage } = useSWRxCurrentPage();
   return (
-    <div data-testid="grw-contextual-sub-nav">
-      <GrowiContextualSubNavigationSubstance currentPage={currentPage} isLinkSharingDisabled={isLinkSharingDisabled} />
-    </div>
+    <GrowiContextualSubNavigationSubstance currentPage={currentPage} isLinkSharingDisabled={isLinkSharingDisabled} />
   );
 };
 
@@ -325,11 +323,9 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
         <title>{title}</title>
       </Head>
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} justify-content-between`}>
-        <header className="py-0 position-relative">
-          <div id="grw-subnav-container">
-            <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
-          </div>
-        </header>
+        <nav className="sticky-top">
+          <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
+        </nav>
 
         <DisplaySwitcher
           pageView={(

+ 3 - 5
apps/app/src/pages/share/[[...path]].page.tsx

@@ -76,9 +76,7 @@ const GrowiContextualSubNavigationForSharedPage = (props: GrowiContextualSubNavi
   const { page, isLinkSharingDisabled } = props;
 
   return (
-    <div data-testid="grw-contextual-sub-nav">
-      <GrowiContextualSubNavigationSubstance currentPage={page} isLinkSharingDisabled={isLinkSharingDisabled} />
-    </div>
+    <GrowiContextualSubNavigationSubstance currentPage={page} isLinkSharingDisabled={isLinkSharingDisabled} />
   );
 };
 
@@ -122,9 +120,9 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
       </Head>
 
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} justify-content-between`}>
-        <header className="py-0 position-relative">
+        <nav className="sticky-top">
           <GrowiContextualSubNavigationForSharedPage page={currentPage ?? props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />
-        </header>
+        </nav>
 
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 

+ 7 - 16
apps/app/src/pages/trash.page.tsx

@@ -6,17 +6,16 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 
-import { GrowiSubNavigation } from '~/components/Navbar/GrowiSubNavigation';
+import { PagePathNavSticky } from '~/components/Common/PagePathNav';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import { useCurrentPageId } from '~/stores/page';
-import { useDrawerMode } from '~/stores/ui';
 
 import { BasicLayout } from '../components/Layout/BasicLayout';
 import {
   useCurrentUser, useCurrentPathname, useGrowiCloudUri,
   useIsSearchServiceConfigured, useIsSearchServiceReachable,
-  useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL, useIsGuestUser, useIsReadOnlyUser,
+  useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL,
 } from '../stores/context';
 
 import type { NextPageWithLayout } from './_app.page';
@@ -28,6 +27,7 @@ import {
 const TrashPageList = dynamic(() => import('~/components/TrashPageList').then(mod => mod.TrashPageList), { ssr: false });
 const EmptyTrashModal = dynamic(() => import('~/components/EmptyTrashModal'), { ssr: false });
 
+
 type Props = CommonProps & {
   currentUser: IUser,
   isSearchServiceConfigured: boolean,
@@ -56,10 +56,6 @@ const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
 
   useShowPageLimitationXL(props.showPageLimitationXL);
 
-  const { data: isDrawerMode } = useDrawerMode();
-  const { data: isGuestUser } = useIsGuestUser();
-  const { data: isReadOnlyUser } = useIsReadOnlyUser();
-
   const title = generateCustomTitleForPage(props, '/trash');
 
   return (
@@ -68,17 +64,12 @@ const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
         <title>{title}</title>
       </Head>
       <div className="dynamic-layout-root">
-        <header className="py-0 position-relative">
-          <GrowiSubNavigation
-            pagePath="/trash"
-            showDrawerToggler={isDrawerMode}
-            isTagLabelsDisabled={!!isGuestUser || !!isReadOnlyUser}
-            isDrawerMode={isDrawerMode}
-            additionalClasses={['container-fluid']}
-          />
-        </header>
+        <nav className="sticky-top">
+          TODO: implement navigation for /trash
+        </nav>
 
         <div className="content-main container-lg grw-container-convertible mb-5 pb-5">
+          <PagePathNavSticky pagePath="/trash" />
           <TrashPageList />
         </div>
 

+ 0 - 40
apps/app/src/styles/_editor.scss

@@ -7,24 +7,6 @@
 .layout-root.editing {
   overflow-y: hidden !important;
 
-  .grw-navbar {
-    position: fixed !important;
-    width: 100vw;
-  }
-
-  // restrict height of subnav
-  .grw-subnav {
-    height: var.$grw-subnav-height-on-edit;
-    min-height: unset;
-    padding-top: 0;
-    padding-right: 15px;
-    padding-left: 15px;
-
-    @include bs.media-breakpoint-up(lg) {
-      height: var.$grw-subnav-height-lg-on-edit;
-    }
-  }
-
   .page-wrapper {
     top: 0;
     height: 100vh;
@@ -98,28 +80,6 @@
     }
   }
 
-  // ellipsis .grw-page-path-hierarchical-link
-  .grw-subnav-start-side {
-    overflow: hidden;
-    .grw-path-nav-container {
-      margin-right: 1rem;
-      overflow: hidden;
-      .grw-page-path-nav {
-        white-space: nowrap;
-
-        .grw-page-path-hierarchical-link {
-          width: 100%;
-          overflow: hidden;
-          text-overflow: ellipsis;
-        }
-
-        h1 {
-          overflow: hidden;
-        }
-      }
-    }
-  }
-
   .grw-copy-dropdown {
     .btn-copy {
       padding: 3px !important; // overwrite padding

+ 1 - 0
apps/app/src/styles/_layout.scss

@@ -13,6 +13,7 @@
 
 .dynamic-layout-root {
   @extend %flex-expand-vert;
+  overflow-y: unset;
 }
 
 .dynamic-layout-root.growi-layout-fluid .grw-container-convertible {

+ 0 - 16
apps/app/src/styles/_mixins.scss

@@ -1,22 +1,6 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use './variables' as var;
 
-@mixin variable-font-size($basesize) {
-  font-size: $basesize * 0.6;
-
-  @include bs.media-breakpoint-only(sm) {
-    font-size: #{$basesize * 0.7};
-  }
-  @include bs.media-breakpoint-only(md) {
-    font-size: #{$basesize * 0.8};
-  }
-  @include bs.media-breakpoint-only(lg) {
-    font-size: #{$basesize * 0.9};
-  }
-  @include bs.media-breakpoint-up(xl) {
-    font-size: $basesize;
-  }
-}
 
 @mixin apply-navigation-transition() {
   transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);

+ 0 - 6
apps/app/src/styles/_page-path.scss

@@ -1,6 +0,0 @@
-.grw-page-path-hierarchical-link {
-  .separator {
-    margin-right: 0.2em;
-    margin-left: 0.2em;
-  }
-}

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

@@ -6,13 +6,6 @@ $grw-marker-cyan: #6ff;
 $grw-marker-green: #6f6;
 
 //== Layout
-$grw-subnav-min-height: 95px;
-$grw-subnav-min-height-md: 115px;
-$grw-subnav-height-on-edit: 95px;
-$grw-subnav-height-lg-on-edit: 50px;
-
-$grw-subnav-search-preview-min-height: 90px;
-
 $grw-navbar-bottom-height: 48px;
 $grw-editor-navbar-bottom-height: 48px;
 

+ 0 - 1
apps/app/src/styles/style-app.scss

@@ -23,7 +23,6 @@
 @import 'layout';
 @import 'mirror_mode';
 @import 'modal';
-@import 'page-path';
 @import 'tag';
 @import 'installer';
 

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

@@ -258,7 +258,7 @@ context('Access to Template Editing Mode', () => {
 
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       // wait until
@@ -293,7 +293,7 @@ context('Access to Template Editing Mode', () => {
 
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       // Wait until
@@ -327,7 +327,7 @@ context('Access to Template Editing Mode', () => {
 
     cy.waitUntil(() => {
       //do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       // wait until

+ 12 - 12
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--click-page-icons.cy.ts

@@ -25,7 +25,7 @@ context('Click page icons button', () => {
     cy.getByTestid('subscribe-button-tooltip').should('not.exist');
 
     cy.waitUntilSkeletonDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}1-subscribe-page`) })
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}1-subscribe-page`) })
 
     // Unsubscribe
     cy.get('#subscribe-button').click({force: true});
@@ -40,7 +40,7 @@ context('Click page icons button', () => {
     cy.getByTestid('subscribe-button-tooltip').should('not.exist');
 
     cy.waitUntilSkeletonDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}2-unsubscribe-page`) })
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}2-unsubscribe-page`) })
   });
 
   it('Successfully Like / Dislike a page', () => {
@@ -60,12 +60,12 @@ context('Click page icons button', () => {
     cy.getByTestid('like-button-tooltip').should('not.exist');
 
     cy.waitUntilSpinnerDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}3-like-page`) });
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}3-like-page`) });
 
     // total liker (user-list-popover is commented out because it is sometimes displayed and sometimes not.)
     // cy.get('#po-total-likes').click({force: true});
     // cy.get('.user-list-popover').should('be.visible')
-    // cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}4-likes-counter`) });
+    // cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}4-likes-counter`) });
 
     // unlike
     cy.get('#like-button').click({force: true});
@@ -80,12 +80,12 @@ context('Click page icons button', () => {
     cy.getByTestid('like-button-tooltip').should('not.exist');
 
     cy.waitUntilSpinnerDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}5-dislike-page`) });
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}5-dislike-page`) });
 
     // total liker (user-list-popover is commented out because it is sometimes displayed and sometimes not.)
     // cy.get('#po-total-likes').click({force: true});
     // cy.get('.user-list-popover').should('be.visible');
-    // cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}6-likes-counter`) });
+    // cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}6-likes-counter`) });
   });
 
   it('Successfully Bookmark / Unbookmark a page', () => {
@@ -105,7 +105,7 @@ context('Click page icons button', () => {
     cy.getByTestid('bookmark-button-tooltip').should('not.exist');
 
     cy.waitUntilSpinnerDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}7-bookmark-page`) });
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}7-bookmark-page`) });
 
     // total bookmarker
     cy.waitUntil(() => {
@@ -117,7 +117,7 @@ context('Click page icons button', () => {
       });
     });
     cy.waitUntilSpinnerDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}8-bookmarks-counter`) });
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}8-bookmarks-counter`) });
 
     // unbookmark
     cy.get('#bookmark-dropdown-btn').click({force: true});
@@ -134,7 +134,7 @@ context('Click page icons button', () => {
     cy.getByTestid('bookmark-button-tooltip').should('not.exist');
 
     cy.waitUntilSpinnerDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}9-unbookmark-page`) });
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}9-unbookmark-page`) });
 
     // total bookmarker
     cy.waitUntil(() => {
@@ -146,7 +146,7 @@ context('Click page icons button', () => {
       });
     });
     cy.waitUntilSpinnerDisappear();
-    cy.get('#grw-subnav-container').within(() => { cy.screenshot(`${ssPrefix}10-bookmarks-counter`) });
+    cy.getByTestid('grw-contextual-sub-nav').within(() => { cy.screenshot(`${ssPrefix}10-bookmarks-counter`) });
   });
 
   // user-list-popover is commented out because it is sometimes displayed and sometimes not
@@ -154,7 +154,7 @@ context('Click page icons button', () => {
   //   cy.visit('/Sandbox');
   //   cy.waitUntilSkeletonDisappear();
 
-  //   cy.get('#grw-subnav-container').within(() => {
+  //   cy.getByTestid('grw-contextual-sub-nav').within(() => {
   //     cy.get('div.grw-seen-user-info').find('button#btn-seen-user').click({force: true});
   //   });
 
@@ -168,7 +168,7 @@ context('Click page icons button', () => {
 
   //   cy.get('.user-list-popover').should('be.visible')
 
-  //   cy.get('#grw-subnav-container').within(() => {
+  //   cy.getByTestid('grw-contextual-sub-nav').within(() => {
   //     cy.screenshot(`${ssPrefix}11-seen-user-list`);
   //   });
   // });

+ 4 - 4
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--use-tools.cy.ts

@@ -14,7 +14,7 @@ context('Modal for page operation', () => {
 
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       //wait until
@@ -47,7 +47,7 @@ context('Modal for page operation', () => {
 
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       // wait until
@@ -65,7 +65,7 @@ context('Modal for page operation', () => {
 
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       // wait until
@@ -97,7 +97,7 @@ context('Modal for page operation', () => {
 //   it('PresentationModal for "/" is shown successfully', () => {
 //     cy.visit('/');
 
-//     cy.get('#grw-subnav-container').within(() => {
+//     cy.getByTestid('grw-contextual-sub-nav').within(() => {
 //       cy.getByTestid('open-page-item-control-btn').click({force: true});
 //       cy.getByTestid('open-presentation-modal-btn').click({force: true});
 //     });

+ 1 - 1
apps/app/test/cypress/e2e/22-sharelink/22-sharelink--access-to-sharelink.cy.ts

@@ -14,7 +14,7 @@ context('Access to sharelink by guest', () => {
     // open dropdown
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn', { timeout: 14000 }).find('button').click({force: true});
       });
       // wait until

+ 1 - 1
apps/app/test/cypress/e2e/23-editor/23-editor--with-navigation.cy.ts

@@ -143,7 +143,7 @@ context.skip('Editor while navigation', () => {
     // open duplicate modal
     cy.waitUntil(() => {
       // do
-      cy.get('#grw-subnav-container').within(() => {
+      cy.getByTestid('grw-contextual-sub-nav').within(() => {
         cy.getByTestid('open-page-item-control-btn').find('button').click({force: true});
       });
       // wait until

+ 43 - 1
yarn.lock

@@ -5855,6 +5855,11 @@ cjs-module-lexer@^1.0.0:
   resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
   integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
 
+classnames@^2.0.0:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
+  integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
+
 classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.6:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
@@ -6312,6 +6317,11 @@ core-js@^3, core-js@^3.0.1, core-js@^3.2.1:
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.3.tgz#3b977612b15da6da0c9cc4aec487e8d24f371112"
   integrity sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q==
 
+core-js@^3.6.5:
+  version "3.33.0"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.0.tgz#70366dbf737134761edb017990cf5ce6c6369c40"
+  integrity sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==
+
 core-util-is@1.0.2, core-util-is@^1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -7846,7 +7856,7 @@ eventemitter2@6.4.7:
   resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d"
   integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==
 
-eventemitter3@^3.1.0:
+eventemitter3@^3.0.0, eventemitter3@^3.1.0:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
   integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
@@ -13377,6 +13387,13 @@ quick-lru@^4.0.1:
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
   integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
 
+raf@^3.0.0:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+  integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
+  dependencies:
+    performance-now "^2.1.0"
+
 random-bytes@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@@ -13652,6 +13669,17 @@ react-scroll@^1.8.7:
     lodash.throttle "^4.1.1"
     prop-types "^15.7.2"
 
+react-stickynode@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/react-stickynode/-/react-stickynode-4.1.0.tgz#ecd80987f64b98f999c589cd4b992eee6bed9562"
+  integrity sha512-zylWgfad75jLfh/gYIayDcDWIDwO4weZrsZqDpjZ/axhF06zRjdCWFBgUr33Pvv2+htKWqPSFksWTyB6aMQ1ZQ==
+  dependencies:
+    classnames "^2.0.0"
+    core-js "^3.6.5"
+    prop-types "^15.6.0"
+    shallowequal "^1.0.0"
+    subscribe-ui-event "^2.0.6"
+
 react-syntax-highlighter@^15.5.0:
   version "15.5.0"
   resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20"
@@ -14626,6 +14654,11 @@ sha.js@^2.4.11:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
+shallowequal@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+  integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
 shebang-command@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -15421,6 +15454,15 @@ stylis@^4.1.2:
   resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
   integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
 
+subscribe-ui-event@^2.0.6:
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/subscribe-ui-event/-/subscribe-ui-event-2.0.7.tgz#8d18b6339c35b25246a5335775573f0e5dc461f8"
+  integrity sha512-Acrtf9XXl6lpyHAWYeRD1xTPUQHDERfL4GHeNuYAtZMc4Z8Us2iDBP0Fn3xiRvkQ1FO+hx+qRLmPEwiZxp7FDQ==
+  dependencies:
+    eventemitter3 "^3.0.0"
+    lodash "^4.17.15"
+    raf "^3.0.0"
+
 superjson@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/superjson/-/superjson-1.9.1.tgz#e23bd2e8cf0f4ade131d6d769754cac7eaa8ab34"