GrowiSubNavigationSwitcher.jsx 3.2 KB

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