GrowiSubNavigationSwitcher.jsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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 GrowiSubNavigation from './GrowiSubNavigation';
  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 resetWidth = 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. // setup effect by resizing event
  36. useEffect(() => {
  37. const resizeHandler = debounce(100, resetWidth);
  38. window.addEventListener('resize', resizeHandler);
  39. // return clean up handler
  40. return () => {
  41. window.removeEventListener('resize', resizeHandler);
  42. };
  43. }, [resetWidth]);
  44. const stickyChangeHandler = useCallback((event) => {
  45. logger.debug('StickyEvents.CHANGE detected');
  46. setVisible(event.detail.isSticky);
  47. }, []);
  48. // setup effect by sticky event
  49. useEffect(() => {
  50. // sticky
  51. // See: https://github.com/ryanwalters/sticky-events
  52. const { stickySelector } = stickyEvents;
  53. const elem = document.querySelector(stickySelector);
  54. elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  55. // return clean up handler
  56. return () => {
  57. elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  58. };
  59. }, [stickyChangeHandler, stickyEvents]);
  60. // update width when sidebar collapsing changed
  61. useEffect(() => {
  62. if (isSidebarCollapsed != null) {
  63. setTimeout(resetWidth, 300);
  64. }
  65. }, [isSidebarCollapsed, resetWidth]);
  66. // initialize
  67. useEffect(() => {
  68. resetWidth();
  69. setTimeout(() => {
  70. stickyEvents.state.values((sticky) => {
  71. setVisible(sticky.isSticky);
  72. });
  73. }, 100);
  74. }, [resetWidth, stickyEvents]);
  75. return (
  76. <div className={`grw-subnav-switcher ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
  77. <div id="grw-subnav-fixed-container" className="grw-subnav-fixed-container position-fixed" ref={fixedContainerRef} style={{ width }}>
  78. <GrowiSubNavigation isCompactMode />
  79. </div>
  80. </div>
  81. );
  82. };
  83. GrowiSubNavigationSwitcher.propTypes = {
  84. };
  85. export default GrowiSubNavigationSwitcher;