DescendantsPageListModal.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import React, {
  2. useState, useMemo, useEffect, useCallback,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import dynamic from 'next/dynamic';
  6. import { useRouter } from 'next/router';
  7. import {
  8. Modal, ModalHeader, ModalBody,
  9. } from 'reactstrap';
  10. import { useIsSharedUser } from '~/states/context';
  11. import { useDeviceLargerThanLg } from '~/states/ui/device';
  12. import { useDescendantsPageListModalActions, useDescendantsPageListModalStatus } from '~/states/ui/modal/descendants-page-list';
  13. import { CustomNavDropdown, CustomNavTab } from './CustomNavigation/CustomNav';
  14. import CustomTabContent from './CustomNavigation/CustomTabContent';
  15. import type { DescendantsPageListProps } from './DescendantsPageList';
  16. import ExpandOrContractButton from './ExpandOrContractButton';
  17. import styles from './DescendantsPageListModal.module.scss';
  18. const DescendantsPageList = dynamic<DescendantsPageListProps>(() => import('./DescendantsPageList').then(mod => mod.DescendantsPageList), { ssr: false });
  19. const PageTimeline = dynamic(() => import('./PageTimeline').then(mod => mod.PageTimeline), { ssr: false });
  20. export const DescendantsPageListModal = (): React.JSX.Element => {
  21. const { t } = useTranslation();
  22. const [activeTab, setActiveTab] = useState('pagelist');
  23. const [isWindowExpanded, setIsWindowExpanded] = useState(false);
  24. const isSharedUser = useIsSharedUser();
  25. const status = useDescendantsPageListModalStatus();
  26. const { close } = useDescendantsPageListModalActions();
  27. const { events } = useRouter();
  28. const [isDeviceLargerThanLg] = useDeviceLargerThanLg();
  29. useEffect(() => {
  30. events.on('routeChangeStart', close);
  31. return () => {
  32. events.off('routeChangeStart', close);
  33. };
  34. }, [close, events]);
  35. const navTabMapping = useMemo(() => {
  36. return {
  37. pagelist: {
  38. Icon: () => <span className="material-symbols-outlined">subject</span>,
  39. Content: () => {
  40. if (status == null || status.path == null || !status.isOpened) {
  41. return <></>;
  42. }
  43. return <DescendantsPageList path={status.path} />;
  44. },
  45. i18n: t('page_list'),
  46. isLinkEnabled: () => !isSharedUser,
  47. },
  48. timeline: {
  49. Icon: () => <span data-testid="timeline-tab-button" className="material-symbols-outlined">timeline</span>,
  50. Content: () => {
  51. if (status == null || !status.isOpened) {
  52. return <></>;
  53. }
  54. return <PageTimeline />;
  55. },
  56. i18n: t('Timeline View'),
  57. isLinkEnabled: () => !isSharedUser,
  58. },
  59. };
  60. }, [isSharedUser, status, t]);
  61. // Memoize event handlers
  62. const expandWindow = useCallback(() => setIsWindowExpanded(true), []);
  63. const contractWindow = useCallback(() => setIsWindowExpanded(false), []);
  64. const onNavSelected = useCallback((v: string) => setActiveTab(v), []);
  65. const buttons = useMemo(() => (
  66. <span className="me-3">
  67. <ExpandOrContractButton
  68. isWindowExpanded={isWindowExpanded}
  69. expandWindow={expandWindow}
  70. contractWindow={contractWindow}
  71. />
  72. <button type="button" className="btn btn-close ms-2" onClick={close} aria-label="Close"></button>
  73. </span>
  74. ), [close, isWindowExpanded, expandWindow, contractWindow]);
  75. // Early return after all hooks
  76. if (status == null || !status.isOpened) {
  77. return <></>;
  78. }
  79. const { isOpened } = status;
  80. return (
  81. <Modal
  82. size="xl"
  83. isOpen={isOpened}
  84. toggle={close}
  85. data-testid="descendants-page-list-modal"
  86. className={`grw-descendants-page-list-modal ${styles['grw-descendants-page-list-modal']} ${isWindowExpanded ? 'grw-modal-expanded' : ''} `}
  87. >
  88. <ModalHeader className={isDeviceLargerThanLg ? 'p-0' : ''} toggle={close} close={buttons}>
  89. {isDeviceLargerThanLg && (
  90. <CustomNavTab
  91. activeTab={activeTab}
  92. navTabMapping={navTabMapping}
  93. breakpointToHideInactiveTabsDown="md"
  94. onNavSelected={onNavSelected}
  95. hideBorderBottom
  96. />
  97. )}
  98. </ModalHeader>
  99. <ModalBody>
  100. {!isDeviceLargerThanLg && (
  101. <CustomNavDropdown
  102. activeTab={activeTab}
  103. navTabMapping={navTabMapping}
  104. onNavSelected={onNavSelected}
  105. />
  106. )}
  107. <CustomTabContent
  108. activeTab={activeTab}
  109. navTabMapping={navTabMapping}
  110. additionalClassNames={!isDeviceLargerThanLg ? ['grw-tab-content-style-md-down'] : undefined}
  111. />
  112. </ModalBody>
  113. </Modal>
  114. );
  115. };