Skip to main content
Glama
AppShell.tsx4.41 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { AppShellHeaderConfiguration, AppShellNavbarConfiguration } from '@mantine/core'; import { AppShell as MantineAppShell } from '@mantine/core'; import { useMedplum, useMedplumProfile } from '@medplum/react-hooks'; import type { JSX, ReactNode } from 'react'; import { Suspense, useState } from 'react'; import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary'; import { Loading } from '../Loading/Loading'; import classes from './AppShell.module.css'; import { Header } from './Header'; import type { NavbarMenu } from './Navbar'; import { Navbar } from './Navbar'; const OPEN_WIDTH = 250; const CLOSED_WIDTH = 70; export interface AppShellProps { readonly logo: ReactNode; readonly pathname?: string; readonly searchParams?: URLSearchParams; readonly headerSearchDisabled?: boolean; readonly version?: string; readonly menus?: NavbarMenu[]; readonly children: ReactNode; readonly displayAddBookmark?: boolean; readonly resourceTypeSearchDisabled?: boolean; readonly notifications?: ReactNode; readonly layoutVersion?: 'v1' | 'v2'; } export function AppShell(props: AppShellProps): JSX.Element { const [navbarOpen, setNavbarOpen] = useState(localStorage['navbarOpen'] === 'true'); const [layoutVersion] = useState( props.layoutVersion ?? (localStorage['appShellLayoutVersion'] as 'v1' | 'v2' | undefined) ?? 'v1' ); const medplum = useMedplum(); const profile = useMedplumProfile(); function setNavbarOpenWrapper(open: boolean): void { localStorage['navbarOpen'] = open.toString(); setNavbarOpen(open); } function closeNavbar(): void { setNavbarOpenWrapper(false); } function toggleNavbar(): void { setNavbarOpenWrapper(!navbarOpen); } if (medplum.isLoading()) { return <Loading />; } let headerProp: AppShellHeaderConfiguration | undefined; let navbarProp: AppShellNavbarConfiguration | undefined; let headerComponent: ReactNode | undefined; let navbarComponent: ReactNode | undefined; if (layoutVersion === 'v2') { // Layout version v2: // - No header // - Navbar is either open or closed based on state headerProp = { height: 0 }; navbarProp = { width: navbarOpen ? OPEN_WIDTH : CLOSED_WIDTH, breakpoint: 0, collapsed: { desktop: !profile, mobile: !profile, }, }; headerComponent = undefined; navbarComponent = profile ? ( <Navbar logo={props.logo} pathname={props.pathname} searchParams={props.searchParams} menus={props.menus} navbarToggle={toggleNavbar} closeNavbar={closeNavbar} displayAddBookmark={props.displayAddBookmark} resourceTypeSearchDisabled={true} opened={navbarOpen} spotlightEnabled={true} userMenuEnabled={true} version={props.version} /> ) : undefined; } else { // Default to layout version v1 headerProp = { height: 60 }; navbarProp = { width: OPEN_WIDTH, breakpoint: 'sm', collapsed: { desktop: !profile || !navbarOpen, mobile: !profile || !navbarOpen, }, }; headerComponent = profile && ( <Header pathname={props.pathname} searchParams={props.searchParams} headerSearchDisabled={props.headerSearchDisabled} logo={props.logo} version={props.version} navbarOpen={navbarOpen} navbarToggle={toggleNavbar} notifications={props.notifications} /> ); navbarComponent = profile && navbarOpen ? ( <Navbar pathname={props.pathname} searchParams={props.searchParams} menus={props.menus} navbarToggle={toggleNavbar} closeNavbar={closeNavbar} displayAddBookmark={props.displayAddBookmark} resourceTypeSearchDisabled={props.resourceTypeSearchDisabled} /> ) : undefined; } return ( <MantineAppShell header={headerProp} navbar={navbarProp} padding={0}> {headerComponent} {navbarComponent} <MantineAppShell.Main className={classes.main}> <ErrorBoundary> <Suspense fallback={<Loading />}>{props.children}</Suspense> </ErrorBoundary> </MantineAppShell.Main> </MantineAppShell> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server