Browse Source

Merge pull request #9123 from weseek/fix/114434-153937-pagelistmodal-navtab

fix: Make CustomNavTab responsive
mergify[bot] 1 year ago
parent
commit
d68a7dd13a

+ 8 - 2
apps/app/src/client/components/Admin/Notification/NotificationSetting.jsx

@@ -14,7 +14,7 @@ import { toastError } from '~/client/util/toastr';
 import { toArrayIfNot } from '~/utils/array-utils';
 import { toArrayIfNot } from '~/utils/array-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { CustomNavTab } from '../../CustomNavigation/CustomNav';
+import CustomNav from '../../CustomNavigation/CustomNav';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
 
 
@@ -155,7 +155,13 @@ function NotificationSetting(props) {
 
 
       <h2 className="admin-setting-header mt-5">{t('notification_settings.notification_settings')}</h2>
       <h2 className="admin-setting-header mt-5">{t('notification_settings.notification_settings')}</h2>
 
 
-      <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
+      <CustomNav
+        activeTab={activeTab}
+        navTabMapping={navTabMapping}
+        onNavSelected={switchActiveTab}
+        hideBorderBottom
+        breakpointToSwitchDropdownDown="md"
+      />
 
 
       <TabContent activeTab={activeTab} className="p-5">
       <TabContent activeTab={activeTab} className="p-5">
         <TabPane tabId="user_trigger_notification">
         <TabPane tabId="user_trigger_notification">

+ 0 - 1
apps/app/src/client/components/CustomNavigation/CustomNav.module.scss

@@ -14,5 +14,4 @@
     border-bottom: 3px solid;
     border-bottom: 3px solid;
     transition: 0.3s ease-in-out;
     transition: 0.3s ease-in-out;
   }
   }
-
 }
 }

+ 19 - 3
apps/app/src/client/components/CustomNavigation/CustomNav.tsx

@@ -42,26 +42,42 @@ export const CustomNavDropdown = (props: CustomNavDropdownProps): JSX.Element =>
 
 
   const { Icon, i18n } = navTabMapping[activeTab];
   const { Icon, i18n } = navTabMapping[activeTab];
 
 
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+
+  const dropdownButtonRef = useRef<HTMLButtonElement>(null);
+
+  const toggleDropdown = () => {
+    setIsDropdownOpen(prev => !prev);
+  };
+
   const menuItemClickHandler = useCallback((key) => {
   const menuItemClickHandler = useCallback((key) => {
     if (onNavSelected != null) {
     if (onNavSelected != null) {
       onNavSelected(key);
       onNavSelected(key);
     }
     }
+    // Manually close the dropdown
+    setIsDropdownOpen(false);
+    if (dropdownButtonRef.current) {
+      dropdownButtonRef.current.classList.remove('show');
+    }
   }, [onNavSelected]);
   }, [onNavSelected]);
 
 
   return (
   return (
     <div className="btn-group">
     <div className="btn-group">
       <button
       <button
+        ref={dropdownButtonRef}
         className="btn btn-outline-primary btn-lg dropdown-toggle text-end"
         className="btn btn-outline-primary btn-lg dropdown-toggle text-end"
         type="button"
         type="button"
         data-bs-toggle="dropdown"
         data-bs-toggle="dropdown"
         aria-haspopup="true"
         aria-haspopup="true"
-        aria-expanded="false"
+        aria-expanded={isDropdownOpen}
+        onClick={toggleDropdown}
+        data-testid="custom-nav-dropdown"
       >
       >
         <span className="float-start">
         <span className="float-start">
           { Icon != null && <Icon /> } {i18n}
           { Icon != null && <Icon /> } {i18n}
         </span>
         </span>
       </button>
       </button>
-      <div className="dropdown-menu dropdown-menu-right">
+      <div className={`dropdown-menu dropdown-menu-right w-100 ${isDropdownOpen ? 'show' : ''} ${styles['dropdown-menu']}`}>
         {Object.entries(navTabMapping).map(([key, value]) => {
         {Object.entries(navTabMapping).map(([key, value]) => {
 
 
           const isActive = activeTab === key;
           const isActive = activeTab === key;
@@ -167,7 +183,7 @@ export const CustomNavTab = (props: CustomNavTabProps): JSX.Element => {
   }
   }
 
 
   return (
   return (
-    <div className={`grw-custom-nav-tab ${styles['grw-custom-nav-tab']}`}>
+    <div data-testid="custom-nav-tab" className={`grw-custom-nav-tab ${styles['grw-custom-nav-tab']}`}>
       <div ref={navContainerRef} className="d-flex justify-content-between">
       <div ref={navContainerRef} className="d-flex justify-content-between">
         <Nav className="nav-title">
         <Nav className="nav-title">
           {Object.entries(navTabMapping).map(([key, value]) => {
           {Object.entries(navTabMapping).map(([key, value]) => {

+ 4 - 0
apps/app/src/client/components/DescendantsPageListModal.module.scss

@@ -9,6 +9,10 @@
     padding: 25px 30px;
     padding: 25px 30px;
   }
   }
 
 
+  .grw-tab-content-style-md-down {
+    padding-top: 25px;
+  }
+
   .grw-modal-body-style {
   .grw-modal-body-style {
     max-height: calc(100vh - 100px);
     max-height: calc(100vh - 100px);
   }
   }

+ 70 - 0
apps/app/src/client/components/DescendantsPageListModal.spec.tsx

@@ -0,0 +1,70 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+
+import { DescendantsPageListModal } from './DescendantsPageListModal';
+
+const mockClose = vi.hoisted(() => vi.fn());
+const useIsDeviceLargerThanLg = vi.hoisted(() => vi.fn().mockReturnValue({ data: true }));
+
+vi.mock('next/router', () => ({
+  useRouter: () => ({
+    events: {
+      on: vi.fn(),
+      off: vi.fn(),
+    },
+  }),
+}));
+
+vi.mock('~/stores/modal', () => ({
+  useDescendantsPageListModal: vi.fn().mockReturnValue({
+    data: { isOpened: true },
+    close: mockClose,
+  }),
+}));
+
+vi.mock('~/stores/ui', () => ({
+  useIsDeviceLargerThanLg,
+}));
+
+describe('DescendantsPageListModal.tsx', () => {
+
+  it('should render the modal when isOpened is true', () => {
+    render(<DescendantsPageListModal />);
+    expect(screen.getByTestId('descendants-page-list-modal')).not.toBeNull();
+  });
+
+  it('should call close function when close button is clicked', () => {
+    render(<DescendantsPageListModal />);
+    const closeButton = screen.getByLabelText('Close');
+    fireEvent.click(closeButton);
+    expect(mockClose).toHaveBeenCalled();
+  });
+
+  describe('when device is larger than lg', () => {
+
+    it('should render CustomNavTab', () => {
+      render(<DescendantsPageListModal />);
+      expect(screen.getByTestId('custom-nav-tab')).not.toBeNull();
+    });
+
+    it('should not render CustomNavDropdown', () => {
+      render(<DescendantsPageListModal />);
+      expect(screen.queryByTestId('custom-nav-dropdown')).toBeNull();
+    });
+  });
+
+  describe('when device is smaller than lg', () => {
+    beforeEach(() => {
+      useIsDeviceLargerThanLg.mockReturnValue({ data: false });
+    });
+
+    it('should render CustomNavDropdown on devices smaller than lg', () => {
+      render(<DescendantsPageListModal />);
+      expect(screen.getByTestId('custom-nav-dropdown')).not.toBeNull();
+    });
+
+    it('should not render CustomNavTab', () => {
+      render(<DescendantsPageListModal />);
+      expect(screen.queryByTestId('custom-nav-tab')).toBeNull();
+    });
+  });
+});

+ 25 - 9
apps/app/src/client/components/DescendantsPageListModal.tsx

@@ -10,8 +10,9 @@ import {
 
 
 import { useIsSharedUser } from '~/stores-universal/context';
 import { useIsSharedUser } from '~/stores-universal/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useDescendantsPageListModal } from '~/stores/modal';
+import { useIsDeviceLargerThanLg } from '~/stores/ui';
 
 
-import { CustomNavTab } from './CustomNavigation/CustomNav';
+import { CustomNavDropdown, CustomNavTab } from './CustomNavigation/CustomNav';
 import CustomTabContent from './CustomNavigation/CustomTabContent';
 import CustomTabContent from './CustomNavigation/CustomTabContent';
 import type { DescendantsPageListProps } from './DescendantsPageList';
 import type { DescendantsPageListProps } from './DescendantsPageList';
 import ExpandOrContractButton from './ExpandOrContractButton';
 import ExpandOrContractButton from './ExpandOrContractButton';
@@ -34,6 +35,8 @@ export const DescendantsPageListModal = (): JSX.Element => {
 
 
   const { events } = useRouter();
   const { events } = useRouter();
 
 
+  const { data: isDeviceLargerThanLg } = useIsDeviceLargerThanLg();
+
   useEffect(() => {
   useEffect(() => {
     events.on('routeChangeStart', close);
     events.on('routeChangeStart', close);
     return () => {
     return () => {
@@ -93,17 +96,30 @@ export const DescendantsPageListModal = (): JSX.Element => {
       data-testid="descendants-page-list-modal"
       data-testid="descendants-page-list-modal"
       className={`grw-descendants-page-list-modal ${styles['grw-descendants-page-list-modal']} ${isWindowExpanded ? 'grw-modal-expanded' : ''} `}
       className={`grw-descendants-page-list-modal ${styles['grw-descendants-page-list-modal']} ${isWindowExpanded ? 'grw-modal-expanded' : ''} `}
     >
     >
-      <ModalHeader className="p-0" toggle={close} close={buttons}>
-        <CustomNavTab
+      <ModalHeader className={isDeviceLargerThanLg ? 'p-0' : ''} toggle={close} close={buttons}>
+        {isDeviceLargerThanLg && (
+          <CustomNavTab
+            activeTab={activeTab}
+            navTabMapping={navTabMapping}
+            breakpointToHideInactiveTabsDown="md"
+            onNavSelected={v => setActiveTab(v)}
+            hideBorderBottom
+          />
+        )}
+      </ModalHeader>
+      <ModalBody>
+        {!isDeviceLargerThanLg && (
+          <CustomNavDropdown
+            activeTab={activeTab}
+            navTabMapping={navTabMapping}
+            onNavSelected={v => setActiveTab(v)}
+          />
+        )}
+        <CustomTabContent
           activeTab={activeTab}
           activeTab={activeTab}
           navTabMapping={navTabMapping}
           navTabMapping={navTabMapping}
-          breakpointToHideInactiveTabsDown="md"
-          onNavSelected={v => setActiveTab(v)}
-          hideBorderBottom
+          additionalClassNames={!isDeviceLargerThanLg ? ['grw-tab-content-style-md-down'] : undefined}
         />
         />
-      </ModalHeader>
-      <ModalBody>
-        <CustomTabContent activeTab={activeTab} navTabMapping={navTabMapping} />
       </ModalBody>
       </ModalBody>
     </Modal>
     </Modal>
   );
   );