GrowiSubNavigationSwitcher.jsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import React, {
  2. useMemo, useState, useRef, useEffect, useCallback,
  3. } from 'react';
  4. import PropTypes from 'prop-types';
  5. import StickyEvents from 'sticky-events';
  6. import { debounce } from 'throttle-debounce';
  7. import { useSWRxCurrentPage } from '~/stores/page';
  8. import { useSidebarCollapsed } from '~/stores/ui';
  9. import loggerFactory from '~/utils/logger';
  10. import { GrowiContextualSubNavigation } from './GrowiContextualSubNavigation';
  11. import styles from './GrowiSubNavigationSwitcher.module.scss';
  12. const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
  13. /**
  14. * Subnavigation
  15. *
  16. * needs:
  17. * #grw-subnav-fixed-container element
  18. * #grw-subnav-sticky-trigger element
  19. *
  20. * @param {object} props
  21. */
  22. const GrowiSubNavigationSwitcher = (props) => {
  23. const { data: currentPage } = useSWRxCurrentPage();
  24. const { data: isSidebarCollapsed } = useSidebarCollapsed();
  25. const [isVisible, setVisible] = useState(false);
  26. const [width, setWidth] = useState(null);
  27. const fixedContainerRef = useRef();
  28. /*
  29. * Comment out to prevent err >>> TypeError: Cannot read properties of null (reading 'bottom')
  30. * The above err occurs when moving to admin page after rendering normal pages.
  31. * This is because id "grw-subnav-sticky-trigger" does not exist on admin pages.
  32. */
  33. // const stickyEvents = useMemo(() => new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' }), []);
  34. const initWidth = useCallback(() => {
  35. const instance = fixedContainerRef.current;
  36. if (instance == null || instance.parentNode == null) {
  37. return;
  38. }
  39. // get parent width
  40. const { clientWidth } = instance.parentNode;
  41. // update style
  42. setWidth(clientWidth);
  43. }, []);
  44. // const initVisible = useCallback(() => {
  45. // const elements = stickyEvents.stickyElements;
  46. // for (const elem of elements) {
  47. // const bool = stickyEvents.isSticking(elem);
  48. // if (bool) {
  49. // setVisible(bool);
  50. // break;
  51. // }
  52. // }
  53. // }, [stickyEvents]);
  54. // setup effect by resizing event
  55. useEffect(() => {
  56. const resizeHandler = debounce(100, initWidth);
  57. window.addEventListener('resize', resizeHandler);
  58. // return clean up handler
  59. return () => {
  60. window.removeEventListener('resize', resizeHandler);
  61. };
  62. }, [initWidth]);
  63. const stickyChangeHandler = useCallback((event) => {
  64. logger.debug('StickyEvents.CHANGE detected');
  65. setVisible(event.detail.isSticky);
  66. }, []);
  67. // // setup effect by sticky event
  68. // useEffect(() => {
  69. // // sticky
  70. // // See: https://github.com/ryanwalters/sticky-events
  71. // const { stickySelector } = stickyEvents;
  72. // const elem = document.querySelector(stickySelector);
  73. // elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  74. // // return clean up handler
  75. // return () => {
  76. // elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  77. // };
  78. // }, [stickyChangeHandler, stickyEvents]);
  79. // update width when sidebar collapsing changed
  80. useEffect(() => {
  81. if (isSidebarCollapsed != null) {
  82. setTimeout(initWidth, 300);
  83. }
  84. }, [isSidebarCollapsed, initWidth]);
  85. // // initialize
  86. // useEffect(() => {
  87. // initWidth();
  88. // // check sticky state several times
  89. // setTimeout(initVisible, 100);
  90. // setTimeout(initVisible, 300);
  91. // setTimeout(initVisible, 2000);
  92. // }, [initWidth, initVisible]);
  93. // ${styles['grw-subnav-switcher']}
  94. return (
  95. <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
  96. <div
  97. id="grw-subnav-fixed-container"
  98. className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
  99. ref={fixedContainerRef}
  100. style={{ width }}
  101. >
  102. <GrowiContextualSubNavigation currentPage isCompactMode isLinkSharingDisabled />
  103. </div>
  104. </div>
  105. );
  106. };
  107. GrowiSubNavigationSwitcher.propTypes = {
  108. isLinkSharingDisabled: PropTypes.bool,
  109. };
  110. export default GrowiSubNavigationSwitcher;