diff --git a/.serena/memories/apps-app-page-path-nav-and-sub-navigation-layering.md b/.serena/memories/apps-app-page-path-nav-and-sub-navigation-layering.md new file mode 100644 index 00000000000..4e0cca75d0f --- /dev/null +++ b/.serena/memories/apps-app-page-path-nav-and-sub-navigation-layering.md @@ -0,0 +1,105 @@ +# PagePathNav と SubNavigation の z-index レイヤリング + +## 概要 + +PagePathNav(ページパス表示)と GrowiContextualSubNavigation(PageControls等を含むサブナビゲーション)の +Sticky 状態における z-index の重なり順を修正した際の知見。 + +## 修正したバグ + +### 症状 +スクロールしていって PagePathNav がウィンドウ上端に近づいたときに、PageControls のボタンが +PagePathNav の要素の裏側に回ってしまい、クリックできなくなる。 + +### 原因 +z-index 的に以下のように重なっていたため: + +**[Before]** 下層から順に: +1. PageView の children - z-0 +2. ( GroundGlassBar = PageControls ) ← 同じ層 z-1 +3. PagePathNav + +PageControls が PagePathNav より下層にいたため、sticky 境界付近でクリック不能になっていた。 + +## 修正後の構成 + +**[After]** 下層から順に: +1. PageView の children - z-0 +2. GroundGlassBar(磨りガラス背景)- z-1 +3. PagePathNav - z-2(通常時)/ z-3(sticky時) +4. PageControls(nav要素)- z-3 + +### ファイル構成 + +- `GrowiContextualSubNavigation.tsx` - GroundGlassBar を分離してレンダリング + - 1つ目: GroundGlassBar のみ(`position-fixed`, `z-1`) + - 2つ目: nav 要素(`z-3`) +- `PagePathNavSticky.tsx` - z-index を動的に切り替え + - 通常時: `z-2` + - sticky時: `z-3` + +## 実装のポイント + +### GroundGlassBar を分離した理由 +GroundGlassBar を `position-fixed` で常に固定表示にすることで、 +PageControls と切り離して独立した z-index 層として扱えるようにした。 + +これにより、GroundGlassBar → PagePathNav → PageControls という +理想的なレイヤー構造を実現できた。 + +## CopyDropdown が z-2 で動作しない理由(解決済み) + +### 問題 + +`PagePathNavSticky.tsx` の sticky 時の z-index について: + +```tsx +// これだと CopyDropdown(マウスオーバーで表示されるドロップダウン)が出ない +innerActiveClass="active z-2 mt-1" + +// これだと正常に動作する +innerActiveClass="active z-3 mt-1" +``` + +### 原因 + +1. `GrowiContextualSubNavigation` の sticky-inner-wrapper は `z-3` かつ横幅いっぱい(Flex アイテム) +2. この要素が PagePathNavSticky(`z-2`)の上に重なる +3. CopyDropdown は `.grw-page-path-nav-layout:hover` で `visibility: visible` になる仕組み + (参照: `PagePathNavLayout.module.scss`) +4. **z-3 の要素が上に被さっているため、hover イベントが PagePathNavSticky に届かない** +5. 結果、CopyDropdown のアイコンが表示されない + +### なぜ z-3 で動作するか + +- 同じ z-index: 3 になるため、DOM 順序で前後が決まる +- PagePathNavSticky は GrowiContextualSubNavigation より後にレンダリングされるため前面に来る +- hover イベントが正常に届き、CopyDropdown が表示される + +### 結論 + +PagePathNavSticky の sticky 時の z-index は `z-3` である必要がある。 +これは GrowiContextualSubNavigation と同じ層に置くことで、DOM 順序による前後関係を利用するため。 + +## 関連ファイル + +- `apps/app/src/client/components/PageView/PageView.tsx` +- `apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx` +- `apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.module.scss` +- `apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx` +- `apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss` +- `apps/app/src/components/Common/PagePathNav/PagePathNavLayout.tsx`(CopyDropdown を含む) + +## ライブラリの注意事項 + +### react-stickynode の deprecation +`react-stickynode` は **2025-12-31 で deprecated** となる予定。 +https://github.com/yahoo/react-stickynode + +将来的には CSS `position: sticky` + `IntersectionObserver` への移行を検討する必要がある。 + +## 注意事項 + +- z-index の値を変更する際は、上記のレイヤー構造を壊さないよう注意 +- Sticky コンポーネントの `innerActiveClass` で z-index を指定する際、 + 他のコンポーネントとの相互作用を確認すること diff --git a/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.module.scss b/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.module.scss index 889fda5488b..5404a9238d6 100644 --- a/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.module.scss +++ b/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.module.scss @@ -1,12 +1,8 @@ @use '~/styles/mixins'; @use '@growi/core-styles/scss/bootstrap/init' as bs; -.grw-contextual-sub-navigation { +.grw-min-height-sub-navigation { min-height: 46px; - - @include bs.media-breakpoint-up(lg) { - min-height: 46px; - } } @include mixins.at-editing() { diff --git a/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx b/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx index 521ffd649a9..3166a74b632 100644 --- a/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx +++ b/apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx @@ -73,6 +73,9 @@ import { Skeleton } from '../Skeleton'; import styles from './GrowiContextualSubNavigation.module.scss'; import PageEditorModeManagerStyles from './PageEditorModeManager.module.scss'; +const moduleClass = styles['grw-contextual-sub-navigation']; +const minHeightSubNavigation = styles['grw-min-height-sub-navigation']; + const PageEditorModeManager = dynamic( () => import('./PageEditorModeManager').then((mod) => mod.PageEditorModeManager), @@ -456,90 +459,94 @@ const GrowiContextualSubNavigation = ( return ( <> + {/* for App Title for mobile */} + {/* for Sub Navigation */} + + setStickyActive(status.status === Sticky.STATUS_FIXED) } innerActiveClass="w-100 end-0" > - - - + {!isLocalAccountRegistrationEnabled && ( + + {t('tooltip.login_required')} + + )} + + + login + {t('Sign in')} + + + )} + {path != null && currentUser != null && !isReadOnlyUser && ( diff --git a/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss b/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss index 2948618afcb..b6f8a08a8da 100644 --- a/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss +++ b/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.module.scss @@ -1,12 +1,4 @@ -@use '@growi/core-styles/scss/bootstrap/init' as bs; - .grw-page-path-nav-sticky :global { - .sticky-inner-wrapper { - z-index: bs.$zindex-sticky; - } - - // TODO:Responsive font size - // set smaller font-size when sticky .sticky-inner-wrapper.active { h1 { font-size: 1.75rem !important; diff --git a/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx b/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx index d6184a2934e..933584b39a5 100644 --- a/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx +++ b/apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx @@ -25,7 +25,7 @@ const { isTrashPage } = pagePathUtils; export const PagePathNavSticky = (props: PagePathNavLayoutProps): JSX.Element => { - const { pagePath } = props; + const { pagePath, latterLinkClassName, ...rest } = props; const isPrinting = usePrintMode(); @@ -84,33 +84,42 @@ export const PagePathNavSticky = (props: PagePathNavLayoutProps): JSX.Element => // Controlling pointer-events // 1. disable pointer-events with 'pe-none'
- + {({ status }) => { - const isParentsCollapsed = status === Sticky.STATUS_FIXED; - - // Controlling pointer-events - // 2. enable pointer-events with 'pe-auto' only against the children - // which width is minimized by 'd-inline-block' - // - if (isParentsCollapsed) { - return ( -
- -
- ); - } + const isStatusFixed = status === Sticky.STATUS_FIXED; return ( - // Use 'd-block' to make the children take the full width - // This is to improve UX when opening/closing CopyDropdown -
- -
+ <> + {/* + * Controlling pointer-events + * 2. enable pointer-events with 'pe-auto' only against the children + * which width is minimized by 'd-inline-block' + */} + { isStatusFixed && ( +
+ +
+ )} + + {/* + * Use 'd-block' to make the children take the full width + * This is to improve UX when opening/closing CopyDropdown + */} +
+ +
+ ); }}
diff --git a/apps/app/src/client/components/TrashPageList.tsx b/apps/app/src/client/components/TrashPageList.tsx index 4982feb62cd..c263f287381 100644 --- a/apps/app/src/client/components/TrashPageList.tsx +++ b/apps/app/src/client/components/TrashPageList.tsx @@ -123,7 +123,7 @@ export const TrashPageList = (): JSX.Element => { }, [t]); return ( -
+
( (useLoadingProps) => dynamic( @@ -42,15 +38,9 @@ export const PagePathNavTitle = ( setClient(true); }, []); - const className = `${moduleClass} mb-4`; - return isClient ? ( - + ) : ( - + ); }; diff --git a/apps/app/src/components/PageView/PageContentFooter.tsx b/apps/app/src/components/PageView/PageContentFooter.tsx index 11eacebffce..dddb445c649 100644 --- a/apps/app/src/components/PageView/PageContentFooter.tsx +++ b/apps/app/src/components/PageView/PageContentFooter.tsx @@ -27,7 +27,7 @@ export const PageContentFooter = ( } return ( -
+
{ const isNotCreatable = useIsNotCreatable(); const isNotFoundMeta = usePageNotFound(); + const contentContainerId = useId(); + const page = useCurrentPageData(); const { data: viewOptions } = useViewOptions(); @@ -129,9 +139,7 @@ const PageViewComponent = (props: Props): JSX.Element => { return; } - const contentContainer = document.getElementById( - 'page-view-content-container', - ); + const contentContainer = document.getElementById(contentContainerId); if (contentContainer == null) return; const targetId = decodeURIComponent(hash.slice(1)); @@ -152,7 +160,7 @@ const PageViewComponent = (props: Props): JSX.Element => { observer.observe(contentContainer, { childList: true, subtree: true }); return () => observer.disconnect(); - }, [currentPageId]); + }, [currentPageId, contentContainerId]); // ******************************* end ******************************* @@ -252,7 +260,7 @@ const PageViewComponent = (props: Props): JSX.Element => { {isUsersHomepagePath && page?.creator != null && ( )} -
+
diff --git a/apps/app/src/components/PageView/PageViewLayout.tsx b/apps/app/src/components/PageView/PageViewLayout.tsx index 834d81f7b03..a5d26c46e6f 100644 --- a/apps/app/src/components/PageView/PageViewLayout.tsx +++ b/apps/app/src/components/PageView/PageViewLayout.tsx @@ -36,7 +36,7 @@ export const PageViewLayout = (props: Props): JSX.Element => {
-
+
{headerContents != null && headerContents} {!isPrinting && sideContents != null ? (
diff --git a/apps/app/src/pages/trash/index.page.tsx b/apps/app/src/pages/trash/index.page.tsx index a53a4eab377..337930da4bc 100644 --- a/apps/app/src/pages/trash/index.page.tsx +++ b/apps/app/src/pages/trash/index.page.tsx @@ -67,8 +67,10 @@ const TrashPage: NextPageWithLayout = (props: Props) => {
- - +
+ + +