Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) O3-4425 - add ability to define collapsed left nav #1279

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
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<typeof useOnClickOutside>[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<SideMenuPanelProps> = ({ expanded, hidePanel }) => {
const menuRef = useOnClickOutside(hidePanel, expanded);

useEffect(() => {
window.addEventListener('popstate', hidePanel);
return () => window.removeEventListener('popstate', hidePanel);
}, [hidePanel]);
const layout = useLayoutType();

return expanded && <LeftNavMenu ref={menuRef} isChildOfHeader />;
const leftNavContainer = window.document.getElementById('omrs-left-nav-container-root');
return (
<>
{!isDesktop(layout) && expanded && <LeftNavMenu ref={menuRef} isChildOfHeader />}
{isDesktop(layout) && createPortal(<LeftNavMenu ref={menuRef} isChildOfHeader />, leftNavContainer)}
</>
);
};

export default SideMenuPanel;
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -28,7 +29,8 @@ const HeaderItems: React.FC = () => {
const config = useConfig();
const [activeHeaderPanel, setActiveHeaderPanel] = useState<string>(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]);
Expand Down Expand Up @@ -114,7 +116,7 @@ const HeaderItems: React.FC = () => {
</HeaderGlobalAction>
)}
</HeaderGlobalBar>
{!isDesktop(layout) && <SideMenuPanel hidePanel={hidePanel('sideMenu')} expanded={isActivePanel('sideMenu')} />}
<SideMenuPanel hidePanel={hidePanel('sideMenu')} expanded={isActivePanel('sideMenu')} />
{showAppMenu && <AppMenuPanel expanded={isActivePanel('appMenu')} hidePanel={hidePanel('appMenu')} />}
<NotificationsMenuPanel expanded={isActivePanel('notificationsMenu')} />
{showUserMenu && <UserMenuPanel expanded={isActivePanel('userMenu')} hidePanel={hidePanel('userMenu')} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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),
Expand Down
23 changes: 22 additions & 1 deletion packages/framework/esm-framework/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 <SideMenuPanel> (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)

___

Expand Down Expand Up @@ -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`\>
Expand Down
2 changes: 2 additions & 0 deletions packages/framework/esm-styleguide/mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,5 @@ export const LocationPicker = jest.fn(({ onChange, selectedLocationUuid }) => {
</div>
);
});

export const useLeftNavStore = jest.fn();
2 changes: 1 addition & 1 deletion packages/framework/esm-styleguide/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
32 changes: 23 additions & 9 deletions packages/framework/esm-styleguide/src/left-nav/index.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 <SideMenuPanel> (where isChildOfHeader == false)
* is deprecated; it simply renders nothing.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit hacky. If we feel comfortable with this, we can opt for more aggressive breaking change where we stop exporting <LeftNavMenu> in @openmrs/esm-framework, and force other ESMs to remove it.

*/
export const LeftNavMenu = React.forwardRef<HTMLElement, LeftNavMenuProps>((props, ref) => {
const { slotName, basePath } = useStore(leftNavStore);
const { slotName, basePath } = useLeftNavStore();
const currentPath = window.location ?? { pathname: '' };

return (
<SideNav ref={ref} expanded aria-label="Left navigation" className={styles.leftNav} {...props}>
<ExtensionSlot name="global-nav-menu-slot" />
{slotName ? <ExtensionSlot name={slotName} state={{ basePath, currentPath }} /> : null}
</SideNav>
);
if (props.isChildOfHeader) {
return (
<SideNav ref={ref} expanded aria-label="Left navigation" className={styles.leftNav} {...props}>
<ExtensionSlot name="global-nav-menu-slot" />
{slotName ? <ExtensionSlot name={slotName} state={{ basePath, currentPath }} /> : null}
</SideNav>
);
} else {
return <></>;
}
});
1 change: 1 addition & 0 deletions packages/shell/esm-app-shell/src/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<div class="omrs-actionable-notifications-container"></div>
<div class="omrs-snackbars-container"></div>
<div class="omrs-modals-container"></div>
<div id="omrs-left-nav-container-root"></div>
<template id="loading-spinner">
<div class="omrs-loading-spinner">
<div data-inline-loading="" class="cds--inline-loading" role="alert" aria-live="assertive">
Expand Down
Loading