PageAccessoriesModal.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import React, {
  2. useMemo, useCallback, useState, type JSX,
  3. } from 'react';
  4. import { useAtomValue } from 'jotai';
  5. import { useTranslation } from 'next-i18next';
  6. import dynamic from 'next/dynamic';
  7. import {
  8. Modal, ModalBody, ModalHeader,
  9. } from 'reactstrap';
  10. import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
  11. import { disableLinkSharingAtom } from '~/states/server-configurations';
  12. import { useDeviceLargerThanLg } from '~/states/ui/device';
  13. import { usePageAccessoriesModalStatus, usePageAccessoriesModalActions, PageAccessoriesModalContents } from '~/states/ui/modal/page-accessories';
  14. import { CustomNavDropdown, CustomNavTab } from '../CustomNavigation/CustomNav';
  15. import CustomTabContent from '../CustomNavigation/CustomTabContent';
  16. import ExpandOrContractButton from '../ExpandOrContractButton';
  17. import { useAutoOpenModalByQueryParam } from './hooks';
  18. import styles from './PageAccessoriesModal.module.scss';
  19. const PageAttachment = dynamic(() => import('./PageAttachment'), { ssr: false });
  20. const PageHistory = dynamic(() => import('./PageHistory').then(mod => mod.PageHistory), { ssr: false });
  21. const ShareLink = dynamic(() => import('./ShareLink').then(mod => mod.ShareLink), { ssr: false });
  22. interface PageAccessoriesModalSubstanceProps {
  23. isWindowExpanded: boolean;
  24. setIsWindowExpanded: (expanded: boolean) => void;
  25. }
  26. const PageAccessoriesModalSubstance = ({ isWindowExpanded, setIsWindowExpanded }: PageAccessoriesModalSubstanceProps): JSX.Element => {
  27. const { t } = useTranslation();
  28. const isSharedUser = useIsSharedUser();
  29. const isGuestUser = useIsGuestUser();
  30. const isReadOnlyUser = useIsReadOnlyUser();
  31. const isLinkSharingDisabled = useAtomValue(disableLinkSharingAtom);
  32. const [isDeviceLargerThanLg] = useDeviceLargerThanLg();
  33. const status = usePageAccessoriesModalStatus();
  34. const { close, selectContents } = usePageAccessoriesModalActions();
  35. useAutoOpenModalByQueryParam();
  36. // Memoize heavy navTabMapping calculation
  37. const navTabMapping = useMemo(() => {
  38. return {
  39. [PageAccessoriesModalContents.PageHistory]: {
  40. Icon: () => <span className="material-symbols-outlined">history</span>,
  41. Content: () => {
  42. return <PageHistory onClose={close} />;
  43. },
  44. i18n: t('History'),
  45. isLinkEnabled: () => !isGuestUser && !isSharedUser,
  46. },
  47. [PageAccessoriesModalContents.Attachment]: {
  48. Icon: () => <span className="material-symbols-outlined">attachment</span>,
  49. Content: () => {
  50. return <PageAttachment />;
  51. },
  52. i18n: t('attachment_data'),
  53. },
  54. [PageAccessoriesModalContents.ShareLink]: {
  55. Icon: () => <span className="material-symbols-outlined">share</span>,
  56. Content: () => {
  57. return <ShareLink />;
  58. },
  59. i18n: t('share_links.share_link_management'),
  60. isLinkEnabled: () => !isGuestUser && !isReadOnlyUser && !isSharedUser && !isLinkSharingDisabled,
  61. },
  62. };
  63. }, [t, close, isGuestUser, isReadOnlyUser, isSharedUser, isLinkSharingDisabled]);
  64. // Memoize expand/contract handlers
  65. const expandWindow = useCallback(() => setIsWindowExpanded(true), [setIsWindowExpanded]);
  66. const contractWindow = useCallback(() => setIsWindowExpanded(false), [setIsWindowExpanded]);
  67. const buttons = useMemo(() => (
  68. <span className="me-3">
  69. <ExpandOrContractButton
  70. isWindowExpanded={isWindowExpanded}
  71. expandWindow={expandWindow}
  72. contractWindow={contractWindow}
  73. />
  74. <button type="button" className="btn btn-close ms-2" onClick={close} aria-label="Close"></button>
  75. </span>
  76. ), [close, isWindowExpanded, expandWindow, contractWindow]);
  77. if (status == null || status.activatedContents == null) {
  78. return <></>;
  79. }
  80. return (
  81. <>
  82. <ModalHeader className={isDeviceLargerThanLg ? 'p-0' : ''} toggle={close} close={buttons}>
  83. {isDeviceLargerThanLg && (
  84. <CustomNavTab
  85. activeTab={status.activatedContents}
  86. navTabMapping={navTabMapping}
  87. breakpointToHideInactiveTabsDown="md"
  88. onNavSelected={selectContents}
  89. hideBorderBottom
  90. />
  91. )}
  92. </ModalHeader>
  93. <ModalBody className="overflow-auto grw-modal-body-style">
  94. {!isDeviceLargerThanLg && (
  95. <CustomNavDropdown
  96. activeTab={status.activatedContents}
  97. navTabMapping={navTabMapping}
  98. onNavSelected={selectContents}
  99. />
  100. )}
  101. <CustomTabContent
  102. activeTab={status.activatedContents}
  103. navTabMapping={navTabMapping}
  104. additionalClassNames={!isDeviceLargerThanLg ? ['grw-tab-content-style-md-down'] : undefined}
  105. />
  106. </ModalBody>
  107. </>
  108. );
  109. };
  110. export const PageAccessoriesModal = (): JSX.Element => {
  111. const status = usePageAccessoriesModalStatus();
  112. const { close } = usePageAccessoriesModalActions();
  113. const [isWindowExpanded, setIsWindowExpanded] = useState(false);
  114. if (status == null) {
  115. return <></>;
  116. }
  117. return (
  118. <Modal
  119. size="xl"
  120. isOpen={status.isOpened}
  121. toggle={close}
  122. data-testid="page-accessories-modal"
  123. className={`grw-page-accessories-modal ${styles['grw-page-accessories-modal']} ${isWindowExpanded ? 'grw-modal-expanded' : ''} `}
  124. >
  125. {status.isOpened && <PageAccessoriesModalSubstance isWindowExpanded={isWindowExpanded} setIsWindowExpanded={setIsWindowExpanded} />}
  126. </Modal>
  127. );
  128. };