PagePathNavSticky.tsx 4.6 KB

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