DescendantsPageListModal.tsx 5.7 KB

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