GrowiSubNavigationSwitcher.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import React, {
  2. useState, useRef, useEffect, useCallback,
  3. } from 'react';
  4. import StickyEvents from 'sticky-events';
  5. import { debounce } from 'throttle-debounce';
  6. import { useSWRxCurrentPage } from '~/stores/page';
  7. import { useSidebarCollapsed } from '~/stores/ui';
  8. import loggerFactory from '~/utils/logger';
  9. import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
  10. import styles from './GrowiSubNavigationSwitcher.module.scss';
  11. const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
  12. export type GrowiSubNavigationSwitcherProps = {
  13. isLinkSharingDisabled: boolean,
  14. }
  15. /**
  16. * GrowiSubNavigation
  17. *
  18. * needs:
  19. * #grw-subnav-fixed-container element
  20. * #grw-subnav-sticky-trigger element
  21. */
  22. export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProps): JSX.Element => {
  23. const { isLinkSharingDisabled } = props;
  24. const { data: currentPage } = useSWRxCurrentPage();
  25. const { data: isSidebarCollapsed } = useSidebarCollapsed();
  26. const [isVisible, setIsVisible] = useState<boolean>(false);
  27. const [width, setWidth] = useState<number>(0);
  28. // use more specific type HTMLDivElement for avoid assertion error.
  29. // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
  30. const fixedContainerRef = useRef<HTMLDivElement>(null);
  31. const clientWidth = fixedContainerRef.current?.parentElement?.clientWidth;
  32. // Do not use clientWidth as useCallback deps, resizing events will not work in production builds.
  33. const initWidth = useCallback(() => {
  34. if (fixedContainerRef.current != null && fixedContainerRef.current.parentElement != null) {
  35. // get parent elements width
  36. const { clientWidth } = fixedContainerRef.current.parentElement;
  37. setWidth(clientWidth);
  38. }
  39. }, []);
  40. const stickyChangeHandler = useCallback((event) => {
  41. logger.debug('StickyEvents.CHANGE detected');
  42. setIsVisible(event.detail.isSticky);
  43. }, []);
  44. // setup effect by sticky-events
  45. useEffect(() => {
  46. // sticky-events
  47. // See: https://github.com/ryanwalters/sticky-events
  48. const { stickySelector } = new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' });
  49. const elem = document.querySelector(stickySelector);
  50. elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  51. // return clean up handler
  52. return () => {
  53. elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  54. };
  55. }, [stickyChangeHandler]);
  56. // setup effect by resizing event
  57. useEffect(() => {
  58. const resizeHandler = debounce(100, initWidth);
  59. window.addEventListener('resize', resizeHandler);
  60. // return clean up handler
  61. return () => {
  62. window.removeEventListener('resize', resizeHandler);
  63. };
  64. }, [initWidth]);
  65. // update width when sidebar collapsing changed
  66. useEffect(() => {
  67. if (isSidebarCollapsed != null) {
  68. setTimeout(initWidth, 300);
  69. }
  70. }, [isSidebarCollapsed, initWidth]);
  71. /*
  72. * initialize width.
  73. * Since width is not recalculated at production build first rendering,
  74. * make initWidth execution dependent on clientWidth.
  75. */
  76. useEffect(() => {
  77. if (clientWidth != null) initWidth();
  78. }, [initWidth, clientWidth]);
  79. if (currentPage == null) {
  80. return <></>;
  81. }
  82. return (
  83. <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
  84. <div
  85. id="grw-subnav-fixed-container"
  86. className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
  87. ref={fixedContainerRef}
  88. style={{ width }}
  89. >
  90. <GrowiContextualSubNavigation currentPage={currentPage} isCompactMode isLinkSharingDisabled={isLinkSharingDisabled} />
  91. </div>
  92. </div>
  93. );
  94. };