From 1dc9875927d05a884b5f631434dd8b6846310153 Mon Sep 17 00:00:00 2001 From: Chi Bong Ho Date: Mon, 3 Feb 2025 10:48:28 -0500 Subject: [PATCH 1/2] (refactor) O3-4425 - make the desktop left nav menu be rendered by top nav --- .../side-menu-panel.component.tsx | 17 ++++++++-- .../components/navbar/navbar.component.tsx | 6 ++-- .../src/root.component.test.tsx | 3 ++ packages/framework/esm-framework/docs/API.md | 23 ++++++++++++- packages/framework/esm-styleguide/mock.tsx | 2 ++ .../framework/esm-styleguide/src/index.ts | 2 +- .../esm-styleguide/src/left-nav/index.tsx | 32 +++++++++++++------ packages/shell/esm-app-shell/src/index.ejs | 1 + 8 files changed, 71 insertions(+), 15 deletions(-) diff --git a/packages/apps/esm-primary-navigation-app/src/components/navbar-header-panels/side-menu-panel.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/navbar-header-panels/side-menu-panel.component.tsx index 84edca97f..dfa753e68 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/navbar-header-panels/side-menu-panel.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/navbar-header-panels/side-menu-panel.component.tsx @@ -1,11 +1,17 @@ import React, { useEffect } from 'react'; -import { LeftNavMenu, useOnClickOutside } from '@openmrs/esm-framework'; +import { isDesktop, LeftNavMenu, useLayoutType, useOnClickOutside } from '@openmrs/esm-framework'; +import { createPortal } from 'react-dom'; interface SideMenuPanelProps { expanded: boolean; hidePanel: Parameters[0]; } +/** + * This is the menu that pops up when clicking on the hamburger button + * on the top nav. It's also responsible for rendering the left nav + * in desktop mode (via a react portal). + */ const SideMenuPanel: React.FC = ({ expanded, hidePanel }) => { const menuRef = useOnClickOutside(hidePanel, expanded); @@ -13,8 +19,15 @@ const SideMenuPanel: React.FC = ({ expanded, hidePanel }) => window.addEventListener('popstate', hidePanel); return () => window.removeEventListener('popstate', hidePanel); }, [hidePanel]); + const layout = useLayoutType(); - return expanded && ; + const leftNavContainer = window.document.getElementById('omrs-left-nav-container-root'); + return ( + <> + {!isDesktop(layout) && expanded && } + {isDesktop(layout) && createPortal(, leftNavContainer)} + + ); }; export default SideMenuPanel; diff --git a/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx index 39301bc26..34d63d115 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx @@ -13,6 +13,7 @@ import { CloseIcon, UserAvatarIcon, SwitcherIcon, + useLeftNavStore, } from '@openmrs/esm-framework'; import { isDesktop } from '../../utils'; import AppMenuPanel from '../navbar-header-panels/app-menu-panel.component'; @@ -28,7 +29,8 @@ const HeaderItems: React.FC = () => { const config = useConfig(); const [activeHeaderPanel, setActiveHeaderPanel] = useState(null); const layout = useLayoutType(); - const navMenuItems = useAssignedExtensions('patient-chart-dashboard-slot').map((e) => e.id); + const { slotName } = useLeftNavStore(); + const navMenuItems = useAssignedExtensions(slotName); const appMenuItems = useAssignedExtensions('app-menu-slot'); const userMenuItems = useAssignedExtensions('user-panel-slot'); const isActivePanel = useCallback((panelName: string) => activeHeaderPanel === panelName, [activeHeaderPanel]); @@ -114,7 +116,7 @@ const HeaderItems: React.FC = () => { )} - {!isDesktop(layout) && } + {showAppMenu && } {showUserMenu && } diff --git a/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx b/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx index c6d40a201..ed5e5eedf 100644 --- a/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx +++ b/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx @@ -8,6 +8,7 @@ import { useSession, type AssignedExtension, type Session, + useLeftNavStore, } from '@openmrs/esm-framework'; import { isDesktop } from './utils'; import { mockUser } from '../__mocks__/mock-user'; @@ -21,12 +22,14 @@ const mockIsDesktop = jest.mocked(isDesktop); const mockUseConfig = jest.mocked(useConfig); const mockUseAssignedExtensions = jest.mocked(useAssignedExtensions); const mockUseSession = jest.mocked(useSession); +const mockUseLeftNavStore = jest.mocked(useLeftNavStore); mockUseConfig.mockReturnValue({ logo: { src: null, alt: null, name: 'Mock EMR', link: 'Mock EMR' }, }); mockUseAssignedExtensions.mockReturnValue(['mock-extension'] as unknown as AssignedExtension[]); mockUseSession.mockReturnValue(mockSession as unknown as Session); +mockUseLeftNavStore.mockReturnValue({ slotName: '', basePath: '' }); jest.mock('./root.resource', () => ({ getSynchronizedCurrentUser: jest.fn(() => mockUserObservable), diff --git a/packages/framework/esm-framework/docs/API.md b/packages/framework/esm-framework/docs/API.md index df436b239..eb31004cc 100644 --- a/packages/framework/esm-framework/docs/API.md +++ b/packages/framework/esm-framework/docs/API.md @@ -234,6 +234,7 @@ - [useBodyScrollLock](API.md#usebodyscrolllock) - [useFhirPagination](API.md#usefhirpagination) - [useLayoutType](API.md#uselayouttype) +- [useLeftNavStore](API.md#useleftnavstore) - [useOnClickOutside](API.md#useonclickoutside) - [useOpenmrsFetchAll](API.md#useopenmrsfetchall) - [useOpenmrsInfinite](API.md#useopenmrsinfinite) @@ -2309,9 +2310,15 @@ ___ • `Const` **LeftNavMenu**: `ForwardRefExoticComponent`<`SideNavProps` & `RefAttributes`<`HTMLElement`\>\> +This component renders the left nav in desktop mode. It's also used to render the same +nav when the hamburger menu is clicked on in tablet mode. See side-menu-panel.component.tsx + +Use of this component by anything other than (where isChildOfHeader == false) +is deprecated; it simply renders nothing. + #### Defined in -[packages/framework/esm-styleguide/src/left-nav/index.tsx:30](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/left-nav/index.tsx#L30) +[packages/framework/esm-styleguide/src/left-nav/index.tsx:40](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/left-nav/index.tsx#L40) ___ @@ -7503,6 +7510,20 @@ ___ ___ +### useLeftNavStore + +▸ **useLeftNavStore**(): `LeftNavStore` + +#### Returns + +`LeftNavStore` + +#### Defined in + +[packages/framework/esm-styleguide/src/left-nav/index.tsx:28](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/left-nav/index.tsx#L28) + +___ + ### useOnClickOutside ▸ **useOnClickOutside**<`T`\>(`handler`, `active?`): `RefObject`<`T`\> diff --git a/packages/framework/esm-styleguide/mock.tsx b/packages/framework/esm-styleguide/mock.tsx index 7ce270f1c..90ea67b5d 100644 --- a/packages/framework/esm-styleguide/mock.tsx +++ b/packages/framework/esm-styleguide/mock.tsx @@ -126,3 +126,5 @@ export const LocationPicker = jest.fn(({ onChange, selectedLocationUuid }) => { ); }); + +export const useLeftNavStore = jest.fn(); diff --git a/packages/framework/esm-styleguide/src/index.ts b/packages/framework/esm-styleguide/src/index.ts index fad7ff00e..952baa677 100644 --- a/packages/framework/esm-styleguide/src/index.ts +++ b/packages/framework/esm-styleguide/src/index.ts @@ -1,4 +1,4 @@ -import { defineConfigSchema, defineExtensionConfigSchema } from '@openmrs/esm-config'; +import { defineConfigSchema } from '@openmrs/esm-config'; import { setupLogo } from './logo'; import { setupIcons } from './icons/icon-registration'; import { setupBranding } from './brand'; diff --git a/packages/framework/esm-styleguide/src/left-nav/index.tsx b/packages/framework/esm-styleguide/src/left-nav/index.tsx index 9c289fa3a..511dc966c 100644 --- a/packages/framework/esm-styleguide/src/left-nav/index.tsx +++ b/packages/framework/esm-styleguide/src/left-nav/index.tsx @@ -1,8 +1,8 @@ /** @module @category UI */ -import React from 'react'; +import { SideNav } from '@carbon/react'; import { ExtensionSlot, useStore } from '@openmrs/esm-react-utils'; import { createGlobalStore } from '@openmrs/esm-state'; -import { SideNav } from '@carbon/react'; +import React from 'react'; import styles from './left-nav.module.scss'; interface LeftNavStore { @@ -25,16 +25,30 @@ export function unsetLeftNav(name) { } } +export function useLeftNavStore() { + return useStore(leftNavStore); +} type LeftNavMenuProps = SideNavProps; +/** + * This component renders the left nav in desktop mode. It's also used to render the same + * nav when the hamburger menu is clicked on in tablet mode. See side-menu-panel.component.tsx + * + * Use of this component by anything other than (where isChildOfHeader == false) + * is deprecated; it simply renders nothing. + */ export const LeftNavMenu = React.forwardRef((props, ref) => { - const { slotName, basePath } = useStore(leftNavStore); + const { slotName, basePath } = useLeftNavStore(); const currentPath = window.location ?? { pathname: '' }; - return ( - - - {slotName ? : null} - - ); + if (props.isChildOfHeader) { + return ( + + + {slotName ? : null} + + ); + } else { + return <>; + } }); diff --git a/packages/shell/esm-app-shell/src/index.ejs b/packages/shell/esm-app-shell/src/index.ejs index df1bd5654..2b2b5dacf 100644 --- a/packages/shell/esm-app-shell/src/index.ejs +++ b/packages/shell/esm-app-shell/src/index.ejs @@ -48,6 +48,7 @@
+