GrowiSubNavigationSwitcher.tsx 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import React, {
  2. useState, useRef, useEffect, useCallback,
  3. } from 'react';
  4. import { debounce } from 'throttle-debounce';
  5. import { useSticky } from '~/client/services/side-effects/use-sticky';
  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 [width, setWidth] = useState<number>(0);
  27. // use more specific type HTMLDivElement for avoid assertion error.
  28. // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
  29. const fixedContainerRef = useRef<HTMLDivElement>(null);
  30. const clientWidth = fixedContainerRef.current?.parentElement?.clientWidth;
  31. // Get sticky status
  32. const isSticky = useSticky('#grw-subnav-sticky-trigger');
  33. // Do not use clientWidth as useCallback deps, resizing events will not work in production builds.
  34. const initWidth = useCallback(() => {
  35. if (fixedContainerRef.current != null && fixedContainerRef.current.parentElement != null) {
  36. // get parent elements width
  37. const { clientWidth } = fixedContainerRef.current.parentElement;
  38. setWidth(clientWidth);
  39. }
  40. }, []);
  41. // setup effect by resizing event
  42. useEffect(() => {
  43. const resizeHandler = debounce(100, initWidth);
  44. window.addEventListener('resize', resizeHandler);
  45. // return clean up handler
  46. return () => {
  47. window.removeEventListener('resize', resizeHandler);
  48. };
  49. }, [initWidth]);
  50. // update width when sidebar collapsing changed
  51. useEffect(() => {
  52. if (isSidebarCollapsed != null) {
  53. setTimeout(initWidth, 300);
  54. }
  55. }, [isSidebarCollapsed, initWidth]);
  56. /*
  57. * initialize width.
  58. * Since width is not recalculated at production build first rendering,
  59. * make initWidth execution dependent on clientWidth.
  60. */
  61. useEffect(() => {
  62. if (clientWidth != null) initWidth();
  63. }, [initWidth, clientWidth]);
  64. if (currentPage == null) {
  65. return <></>;
  66. }
  67. return (
  68. <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`} data-testid="grw-subnav-switcher">
  69. <div
  70. id="grw-subnav-fixed-container"
  71. className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
  72. ref={fixedContainerRef}
  73. style={{ width }}
  74. >
  75. <GrowiContextualSubNavigation currentPage={currentPage} isCompactMode isLinkSharingDisabled={isLinkSharingDisabled} />
  76. </div>
  77. </div>
  78. );
  79. };