DescendantsPageListModal.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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. /**
  21. * DescendantsPageListModalSubstance - Presentation component (all logic here)
  22. */
  23. type DescendantsPageListModalSubstanceProps = {
  24. path: string | undefined;
  25. closeModal: () => void;
  26. onExpandedChange?: (isExpanded: boolean) => void;
  27. };
  28. const DescendantsPageListModalSubstance = ({
  29. path,
  30. closeModal,
  31. onExpandedChange,
  32. }: DescendantsPageListModalSubstanceProps): React.JSX.Element => {
  33. const { t } = useTranslation();
  34. const [activeTab, setActiveTab] = useState('pagelist');
  35. const [isWindowExpanded, setIsWindowExpanded] = useState(false);
  36. const isSharedUser = useIsSharedUser();
  37. const { events } = useRouter();
  38. const [isDeviceLargerThanLg] = useDeviceLargerThanLg();
  39. useEffect(() => {
  40. events.on('routeChangeStart', closeModal);
  41. return () => {
  42. events.off('routeChangeStart', closeModal);
  43. };
  44. }, [closeModal, events]);
  45. const navTabMapping = useMemo(() => {
  46. return {
  47. pagelist: {
  48. Icon: () => <span className="material-symbols-outlined">subject</span>,
  49. Content: () => {
  50. if (path == null) {
  51. return <></>;
  52. }
  53. return <DescendantsPageList path={path} />;
  54. },
  55. i18n: t('page_list'),
  56. isLinkEnabled: () => !isSharedUser,
  57. },
  58. timeline: {
  59. Icon: () => <span data-testid="timeline-tab-button" className="material-symbols-outlined">timeline</span>,
  60. Content: () => {
  61. return <PageTimeline />;
  62. },
  63. i18n: t('Timeline View'),
  64. isLinkEnabled: () => !isSharedUser,
  65. },
  66. };
  67. }, [isSharedUser, path, t]);
  68. // Memoize event handlers
  69. const expandWindow = useCallback(() => {
  70. setIsWindowExpanded(true);
  71. onExpandedChange?.(true);
  72. }, [onExpandedChange]);
  73. const contractWindow = useCallback(() => {
  74. setIsWindowExpanded(false);
  75. onExpandedChange?.(false);
  76. }, [onExpandedChange]);
  77. const onNavSelected = useCallback((v: string) => setActiveTab(v), []);
  78. const buttons = useMemo(() => (
  79. <span className="me-3">
  80. <ExpandOrContractButton
  81. isWindowExpanded={isWindowExpanded}
  82. expandWindow={expandWindow}
  83. contractWindow={contractWindow}
  84. />
  85. <button type="button" className="btn btn-close ms-2" onClick={closeModal} aria-label="Close"></button>
  86. </span>
  87. ), [closeModal, isWindowExpanded, expandWindow, contractWindow]);
  88. return (
  89. <div>
  90. <ModalHeader className={isDeviceLargerThanLg ? 'p-0' : ''} toggle={closeModal} close={buttons}>
  91. {isDeviceLargerThanLg && (
  92. <CustomNavTab
  93. activeTab={activeTab}
  94. navTabMapping={navTabMapping}
  95. breakpointToHideInactiveTabsDown="md"
  96. onNavSelected={onNavSelected}
  97. hideBorderBottom
  98. />
  99. )}
  100. </ModalHeader>
  101. <ModalBody>
  102. {!isDeviceLargerThanLg && (
  103. <CustomNavDropdown
  104. activeTab={activeTab}
  105. navTabMapping={navTabMapping}
  106. onNavSelected={onNavSelected}
  107. />
  108. )}
  109. <CustomTabContent
  110. activeTab={activeTab}
  111. navTabMapping={navTabMapping}
  112. additionalClassNames={!isDeviceLargerThanLg ? ['grw-tab-content-style-md-down'] : undefined}
  113. />
  114. </ModalBody>
  115. </div>
  116. );
  117. };
  118. /**
  119. * DescendantsPageListModal - Container component (lightweight, always rendered)
  120. */
  121. export const DescendantsPageListModal = (): React.JSX.Element => {
  122. const [isWindowExpanded, setIsWindowExpanded] = useState(false);
  123. const status = useDescendantsPageListModalStatus();
  124. const { close } = useDescendantsPageListModalActions();
  125. const handleExpandedChange = useCallback((isExpanded: boolean) => {
  126. setIsWindowExpanded(isExpanded);
  127. }, []);
  128. return (
  129. <Modal
  130. size="xl"
  131. isOpen={status.isOpened}
  132. toggle={close}
  133. data-testid="descendants-page-list-modal"
  134. className={`grw-descendants-page-list-modal ${styles['grw-descendants-page-list-modal']} ${isWindowExpanded ? 'grw-modal-expanded' : ''}`}
  135. >
  136. {status.isOpened && (
  137. <DescendantsPageListModalSubstance
  138. path={status?.path}
  139. closeModal={close}
  140. onExpandedChange={handleExpandedChange}
  141. />
  142. )}
  143. </Modal>
  144. );
  145. };