PagePathNavSticky.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { type JSX, useEffect, useMemo, useRef, useState } from 'react';
  2. import { DevidedPagePath } from '@growi/core/dist/models';
  3. import { pagePathUtils } from '@growi/core/dist/utils';
  4. import Sticky from 'react-stickynode';
  5. import { usePrintMode } from '~/client/services/use-print-mode';
  6. import LinkedPagePath from '~/models/linked-page-path';
  7. import { usePageControlsX } from '~/states/ui/page';
  8. import { useCurrentProductNavWidth, useSidebarMode } from '~/states/ui/sidebar';
  9. import { PagePathHierarchicalLink } from '../../../components/Common/PagePathHierarchicalLink';
  10. import type { PagePathNavLayoutProps } from '../../../components/Common/PagePathNav';
  11. import {
  12. PagePathNav,
  13. PagePathNavLayout,
  14. Separator,
  15. } from '../../../components/Common/PagePathNav';
  16. import { CollapsedParentsDropdown } from './CollapsedParentsDropdown';
  17. import styles from './PagePathNavSticky.module.scss';
  18. const moduleClass = styles['grw-page-path-nav-sticky'];
  19. const { isTrashPage } = pagePathUtils;
  20. export const PagePathNavSticky = (
  21. props: PagePathNavLayoutProps,
  22. ): JSX.Element => {
  23. const { pagePath, latterLinkClassName, ...rest } = props;
  24. const isPrinting = usePrintMode();
  25. const pageControlsX = usePageControlsX();
  26. const [sidebarWidth] = useCurrentProductNavWidth();
  27. const { sidebarMode } = useSidebarMode();
  28. const pagePathNavRef = useRef<HTMLDivElement>(null);
  29. const [navMaxWidth, setNavMaxWidth] = useState<number | undefined>();
  30. useEffect(() => {
  31. if (
  32. pageControlsX == null ||
  33. pagePathNavRef.current == null ||
  34. sidebarWidth == null
  35. ) {
  36. return;
  37. }
  38. setNavMaxWidth(
  39. pageControlsX - pagePathNavRef.current.getBoundingClientRect().x - 10,
  40. );
  41. }, [pageControlsX, sidebarWidth]);
  42. useEffect(() => {
  43. // wait for the end of the animation of the opening and closing of the sidebar
  44. const timeout = setTimeout(() => {
  45. if (
  46. pageControlsX == null ||
  47. pagePathNavRef.current == null ||
  48. sidebarMode == null
  49. ) {
  50. return;
  51. }
  52. setNavMaxWidth(
  53. pageControlsX - pagePathNavRef.current.getBoundingClientRect().x - 10,
  54. );
  55. }, 200);
  56. return () => {
  57. clearTimeout(timeout);
  58. };
  59. }, [pageControlsX, sidebarMode]);
  60. const latterLink = useMemo(() => {
  61. const dPagePath = new DevidedPagePath(pagePath, false, true);
  62. const isInTrash = isTrashPage(pagePath);
  63. const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
  64. const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
  65. // not collapsed
  66. if (dPagePath.isRoot || dPagePath.isFormerRoot) {
  67. const linkedPagePath = new LinkedPagePath(pagePath);
  68. return (
  69. <PagePathHierarchicalLink
  70. linkedPagePath={linkedPagePath}
  71. isInTrash={isInTrash}
  72. />
  73. );
  74. }
  75. // collapsed
  76. return (
  77. <>
  78. <CollapsedParentsDropdown linkedPagePath={linkedPagePathFormer} />
  79. <Separator />
  80. <PagePathHierarchicalLink
  81. linkedPagePath={linkedPagePathLatter}
  82. basePath={dPagePath.former}
  83. isInTrash={isInTrash}
  84. />
  85. </>
  86. );
  87. }, [pagePath]);
  88. return (
  89. // Controlling pointer-events
  90. // 1. disable pointer-events with 'pe-none'
  91. <div ref={pagePathNavRef}>
  92. <Sticky
  93. className={moduleClass}
  94. enabled={!isPrinting}
  95. innerClass="z-2 pe-none"
  96. innerActiveClass="active z-3 mt-1"
  97. >
  98. {({ status }) => {
  99. const isStatusFixed = status === Sticky.STATUS_FIXED;
  100. return (
  101. <>
  102. {/*
  103. * Controlling pointer-events
  104. * 2. enable pointer-events with 'pe-auto' only against the children
  105. * which width is minimized by 'd-inline-block'
  106. */}
  107. {isStatusFixed && (
  108. <div className="d-inline-block pe-auto position-absolute">
  109. <PagePathNavLayout
  110. pagePath={pagePath}
  111. latterLink={latterLink}
  112. latterLinkClassName={`${latterLinkClassName} text-truncate`}
  113. maxWidth={navMaxWidth}
  114. {...rest}
  115. />
  116. </div>
  117. )}
  118. {/*
  119. * Use 'd-block' to make the children take the full width
  120. * This is to improve UX when opening/closing CopyDropdown
  121. */}
  122. <div
  123. className={`d-block pe-auto ${isStatusFixed ? 'invisible' : ''}`}
  124. >
  125. <PagePathNav
  126. pagePath={pagePath}
  127. latterLinkClassName={latterLinkClassName}
  128. inline
  129. {...rest}
  130. />
  131. </div>
  132. </>
  133. );
  134. }}
  135. </Sticky>
  136. </div>
  137. );
  138. };
  139. PagePathNavSticky.displayName = 'PagePathNavSticky';