2
0

GrowiSubNavigationSwitcher.jsx 3.4 KB

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