@medplum/react 2.0.15 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/dist/cjs/index.cjs +1583 -1100
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.min.cjs +1 -1
  4. package/dist/esm/AppShell/AppShell.mjs +37 -0
  5. package/dist/esm/AppShell/AppShell.mjs.map +1 -0
  6. package/dist/esm/AppShell/Header.mjs +88 -0
  7. package/dist/esm/AppShell/Header.mjs.map +1 -0
  8. package/dist/esm/AppShell/HeaderSearchInput.mjs +232 -0
  9. package/dist/esm/AppShell/HeaderSearchInput.mjs.map +1 -0
  10. package/dist/esm/AppShell/Navbar.mjs +113 -0
  11. package/dist/esm/AppShell/Navbar.mjs.map +1 -0
  12. package/dist/esm/AsyncAutocomplete/AsyncAutocomplete.mjs +6 -5
  13. package/dist/esm/AsyncAutocomplete/AsyncAutocomplete.mjs.map +1 -1
  14. package/dist/esm/CodeInput/CodeInput.mjs +1 -1
  15. package/dist/esm/CodeInput/CodeInput.mjs.map +1 -1
  16. package/dist/esm/GoogleButton/GoogleButton.mjs +2 -2
  17. package/dist/esm/GoogleButton/GoogleButton.mjs.map +1 -1
  18. package/dist/esm/Loading/Loading.mjs +10 -0
  19. package/dist/esm/Loading/Loading.mjs.map +1 -0
  20. package/dist/esm/MedplumLink/MedplumLink.mjs +1 -1
  21. package/dist/esm/MedplumLink/MedplumLink.mjs.map +1 -1
  22. package/dist/esm/SearchControl/SearchControl.mjs +3 -3
  23. package/dist/esm/SearchControl/SearchControl.mjs.map +1 -1
  24. package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs +2 -2
  25. package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs.map +1 -1
  26. package/dist/esm/auth/NewUserForm.mjs.map +1 -1
  27. package/dist/esm/auth/RegisterForm.mjs.map +1 -1
  28. package/dist/esm/index.min.mjs +1 -1
  29. package/dist/esm/index.mjs +7 -3
  30. package/dist/esm/index.mjs.map +1 -1
  31. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs +1 -1
  32. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs.map +1 -1
  33. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs +1 -1
  34. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs.map +1 -1
  35. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAdjustmentsHorizontal.mjs +1 -1
  36. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAdjustmentsHorizontal.mjs.map +1 -1
  37. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs +1 -1
  38. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs.map +1 -1
  39. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleach.mjs +1 -1
  40. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleach.mjs.map +1 -1
  41. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleachOff.mjs +1 -1
  42. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleachOff.mjs.map +1 -1
  43. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBoxMultiple.mjs +1 -1
  44. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBoxMultiple.mjs.map +1 -1
  45. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBracketsContain.mjs +1 -1
  46. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBracketsContain.mjs.map +1 -1
  47. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucket.mjs +1 -1
  48. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucket.mjs.map +1 -1
  49. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucketOff.mjs +1 -1
  50. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucketOff.mjs.map +1 -1
  51. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCalendar.mjs +1 -1
  52. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCalendar.mjs.map +1 -1
  53. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs +1 -1
  54. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs.map +1 -1
  55. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheckbox.mjs +1 -1
  56. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheckbox.mjs.map +1 -1
  57. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs +12 -0
  58. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs.map +1 -0
  59. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs +1 -1
  60. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs.map +1 -1
  61. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCirclePlus.mjs +1 -1
  62. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCirclePlus.mjs.map +1 -1
  63. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCloudUpload.mjs +1 -1
  64. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCloudUpload.mjs.map +1 -1
  65. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs +1 -1
  66. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs.map +1 -1
  67. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCurrencyDollar.mjs +1 -1
  68. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCurrencyDollar.mjs.map +1 -1
  69. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconDots.mjs +1 -1
  70. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconDots.mjs.map +1 -1
  71. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEdit.mjs +1 -1
  72. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEdit.mjs.map +1 -1
  73. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqual.mjs +1 -1
  74. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqual.mjs.map +1 -1
  75. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqualNot.mjs +1 -1
  76. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqualNot.mjs.map +1 -1
  77. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFileAlert.mjs +1 -1
  78. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFileAlert.mjs.map +1 -1
  79. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs +1 -1
  80. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs.map +1 -1
  81. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs +1 -1
  82. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs.map +1 -1
  83. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconListDetails.mjs +1 -1
  84. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconListDetails.mjs.map +1 -1
  85. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs +19 -0
  86. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs.map +1 -0
  87. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs +1 -1
  88. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs.map +1 -1
  89. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathLower.mjs +1 -1
  90. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathLower.mjs.map +1 -1
  91. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMessage.mjs +1 -1
  92. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMessage.mjs.map +1 -1
  93. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPin.mjs +1 -1
  94. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPin.mjs.map +1 -1
  95. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPinnedOff.mjs +1 -1
  96. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPinnedOff.mjs.map +1 -1
  97. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs +13 -0
  98. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs.map +1 -0
  99. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs +1 -1
  100. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs.map +1 -1
  101. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortAscending.mjs +1 -1
  102. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortAscending.mjs.map +1 -1
  103. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortDescending.mjs +1 -1
  104. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortDescending.mjs.map +1 -1
  105. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSquare.mjs +1 -1
  106. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSquare.mjs.map +1 -1
  107. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs +19 -0
  108. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs.map +1 -0
  109. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs +1 -1
  110. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs.map +1 -1
  111. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTrash.mjs +1 -1
  112. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTrash.mjs.map +1 -1
  113. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs +1 -1
  114. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs.map +1 -1
  115. package/dist/types/AddressDisplay/AddressDisplay.d.ts +0 -1
  116. package/dist/types/AddressInput/AddressInput.d.ts +0 -1
  117. package/dist/types/AnnotationInput/AnnotationInput.d.ts +0 -1
  118. package/dist/types/AppShell/AppShell.d.ts +9 -0
  119. package/dist/types/AppShell/Header.d.ts +8 -0
  120. package/dist/types/AppShell/HeaderSearchInput.d.ts +3 -0
  121. package/dist/types/AppShell/Navbar.d.ts +14 -0
  122. package/dist/types/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -1
  123. package/dist/types/AttachmentArrayDisplay/AttachmentArrayDisplay.d.ts +0 -1
  124. package/dist/types/AttachmentArrayInput/AttachmentArrayInput.d.ts +0 -1
  125. package/dist/types/AttachmentDisplay/AttachmentDisplay.d.ts +0 -1
  126. package/dist/types/AttachmentInput/AttachmentInput.d.ts +0 -1
  127. package/dist/types/BackboneElementDisplay/BackboneElementDisplay.d.ts +0 -1
  128. package/dist/types/BackboneElementInput/BackboneElementInput.d.ts +0 -1
  129. package/dist/types/CalendarInput/CalendarInput.d.ts +0 -1
  130. package/dist/types/CodeInput/CodeInput.d.ts +4 -1
  131. package/dist/types/CodeableConceptDisplay/CodeableConceptDisplay.d.ts +0 -1
  132. package/dist/types/CodeableConceptInput/CodeableConceptInput.d.ts +0 -1
  133. package/dist/types/CodingDisplay/CodingDisplay.d.ts +0 -1
  134. package/dist/types/CodingInput/CodingInput.d.ts +0 -1
  135. package/dist/types/ContactDetailDisplay/ContactDetailDisplay.d.ts +0 -1
  136. package/dist/types/ContactDetailInput/ContactDetailInput.d.ts +0 -1
  137. package/dist/types/ContactPointDisplay/ContactPointDisplay.d.ts +0 -1
  138. package/dist/types/ContactPointInput/ContactPointInput.d.ts +0 -1
  139. package/dist/types/Container/Container.d.ts +0 -1
  140. package/dist/types/DateTimeInput/DateTimeInput.d.ts +0 -1
  141. package/dist/types/DefaultResourceTimeline/DefaultResourceTimeline.d.ts +0 -1
  142. package/dist/types/DiagnosticReportDisplay/DiagnosticReportDisplay.d.ts +0 -1
  143. package/dist/types/Document/Document.d.ts +0 -1
  144. package/dist/types/EncounterTimeline/EncounterTimeline.d.ts +0 -1
  145. package/dist/types/ExtensionInput/ExtensionInput.d.ts +0 -1
  146. package/dist/types/FhirPathDisplay/FhirPathDisplay.d.ts +0 -1
  147. package/dist/types/GoogleButton/GoogleButton.d.ts +0 -1
  148. package/dist/types/HumanNameDisplay/HumanNameDisplay.d.ts +0 -1
  149. package/dist/types/HumanNameInput/HumanNameInput.d.ts +0 -1
  150. package/dist/types/IdentifierDisplay/IdentifierDisplay.d.ts +0 -1
  151. package/dist/types/IdentifierInput/IdentifierInput.d.ts +0 -1
  152. package/dist/types/Loading/Loading.d.ts +1 -0
  153. package/dist/types/Logo/Logo.d.ts +0 -1
  154. package/dist/types/MedplumLink/MedplumLink.d.ts +1 -1
  155. package/dist/types/MoneyDisplay/MoneyDisplay.d.ts +0 -1
  156. package/dist/types/MoneyInput/MoneyInput.d.ts +0 -1
  157. package/dist/types/NoteDisplay/NoteDisplay.d.ts +0 -1
  158. package/dist/types/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +0 -1
  159. package/dist/types/Panel/Panel.d.ts +0 -1
  160. package/dist/types/PatientTimeline/PatientTimeline.d.ts +0 -1
  161. package/dist/types/PeriodInput/PeriodInput.d.ts +0 -1
  162. package/dist/types/PlanDefinitionBuilder/PlanDefinitionBuilder.d.ts +0 -1
  163. package/dist/types/QuantityDisplay/QuantityDisplay.d.ts +0 -1
  164. package/dist/types/QuantityInput/QuantityInput.d.ts +0 -1
  165. package/dist/types/QuestionnaireBuilder/QuestionnaireBuilder.d.ts +0 -1
  166. package/dist/types/QuestionnaireForm/QuestionnaireForm.d.ts +0 -1
  167. package/dist/types/RangeDisplay/RangeDisplay.d.ts +0 -1
  168. package/dist/types/RangeInput/RangeInput.d.ts +0 -1
  169. package/dist/types/RatioDisplay/RatioDisplay.d.ts +0 -1
  170. package/dist/types/RatioInput/RatioInput.d.ts +0 -1
  171. package/dist/types/ReferenceDisplay/ReferenceDisplay.d.ts +0 -1
  172. package/dist/types/ReferenceInput/ReferenceInput.d.ts +0 -1
  173. package/dist/types/ReferenceRangeEditor/ReferenceRangeEditor.d.ts +0 -1
  174. package/dist/types/RequestGroupDisplay/RequestGroupDisplay.d.ts +0 -1
  175. package/dist/types/ResourceArrayDisplay/ResourceArrayDisplay.d.ts +0 -1
  176. package/dist/types/ResourceArrayInput/ResourceArrayInput.d.ts +0 -1
  177. package/dist/types/ResourceAvatar/ResourceAvatar.d.ts +0 -1
  178. package/dist/types/ResourceBadge/ResourceBadge.d.ts +0 -1
  179. package/dist/types/ResourceBlame/ResourceBlame.d.ts +0 -1
  180. package/dist/types/ResourceDiff/ResourceDiff.d.ts +0 -1
  181. package/dist/types/ResourceDiffTable/ResourceDiffTable.d.ts +0 -1
  182. package/dist/types/ResourceForm/ResourceForm.d.ts +0 -1
  183. package/dist/types/ResourceHistoryTable/ResourceHistoryTable.d.ts +0 -1
  184. package/dist/types/ResourceInput/ResourceInput.d.ts +0 -1
  185. package/dist/types/ResourceName/ResourceName.d.ts +0 -1
  186. package/dist/types/ResourcePropertyDisplay/ResourcePropertyDisplay.d.ts +0 -1
  187. package/dist/types/ResourcePropertyInput/ResourcePropertyInput.d.ts +0 -1
  188. package/dist/types/ResourceTable/ResourceTable.d.ts +0 -1
  189. package/dist/types/ResourceTimeline/ResourceTimeline.d.ts +0 -1
  190. package/dist/types/Scheduler/Scheduler.d.ts +0 -1
  191. package/dist/types/SearchControl/SearchUtils.d.ts +0 -1
  192. package/dist/types/SearchExportDialog/SearchExportDialog.d.ts +0 -1
  193. package/dist/types/SearchFieldEditor/SearchFieldEditor.d.ts +0 -1
  194. package/dist/types/SearchFilterEditor/SearchFilterEditor.d.ts +0 -1
  195. package/dist/types/SearchFilterValueDialog/SearchFilterValueDialog.d.ts +0 -1
  196. package/dist/types/SearchFilterValueDisplay/SearchFilterValueDisplay.d.ts +0 -1
  197. package/dist/types/SearchFilterValueInput/SearchFilterValueInput.d.ts +0 -1
  198. package/dist/types/SearchPopupMenu/SearchPopupMenu.d.ts +0 -1
  199. package/dist/types/ServiceRequestTimeline/ServiceRequestTimeline.d.ts +0 -1
  200. package/dist/types/StatusBadge/StatusBadge.d.ts +0 -1
  201. package/dist/types/TimingInput/TimingInput.d.ts +0 -1
  202. package/dist/types/ValueSetAutocomplete/ValueSetAutocomplete.d.ts +2 -1
  203. package/dist/types/auth/ChooseProfileForm.d.ts +0 -1
  204. package/dist/types/auth/ChooseScopeForm.d.ts +0 -1
  205. package/dist/types/auth/MfaForm.d.ts +0 -1
  206. package/dist/types/auth/NewProjectForm.d.ts +0 -1
  207. package/dist/types/auth/NewUserForm.d.ts +1 -1
  208. package/dist/types/auth/RegisterForm.d.ts +1 -1
  209. package/dist/types/index.d.ts +7 -3
  210. package/package.json +17 -17
@@ -0,0 +1,37 @@
1
+ import { useMantineTheme, AppShell as AppShell$1 } from '@mantine/core';
2
+ import React, { useState, Suspense } from 'react';
3
+ import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary.mjs';
4
+ import { Loading } from '../Loading/Loading.mjs';
5
+ import { useMedplum, useMedplumProfile } from '../MedplumProvider/MedplumProvider.mjs';
6
+ import { Header } from './Header.mjs';
7
+ import { Navbar } from './Navbar.mjs';
8
+
9
+ function AppShell(props) {
10
+ const theme = useMantineTheme();
11
+ const [navbarOpen, setNavbarOpen] = useState(localStorage['navbarOpen'] === 'true');
12
+ const medplum = useMedplum();
13
+ const profile = useMedplumProfile();
14
+ function setNavbarOpenWrapper(open) {
15
+ localStorage['navbarOpen'] = open.toString();
16
+ setNavbarOpen(open);
17
+ }
18
+ function closeNavbar() {
19
+ setNavbarOpenWrapper(false);
20
+ }
21
+ function toggleNavbar() {
22
+ setNavbarOpenWrapper(!navbarOpen);
23
+ }
24
+ if (medplum.isLoading()) {
25
+ return React.createElement(Loading, null);
26
+ }
27
+ return (React.createElement(AppShell$1, { styles: {
28
+ main: {
29
+ background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],
30
+ },
31
+ }, padding: 0, fixed: true, header: profile && React.createElement(Header, { logo: props.logo, version: props.version, navbarToggle: toggleNavbar }), navbar: profile && navbarOpen ? React.createElement(Navbar, { menus: props.menus, closeNavbar: closeNavbar }) : undefined },
32
+ React.createElement(ErrorBoundary, null,
33
+ React.createElement(Suspense, { fallback: React.createElement(Loading, null) }, props.children))));
34
+ }
35
+
36
+ export { AppShell };
37
+ //# sourceMappingURL=AppShell.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppShell.mjs","sources":["../../../src/AppShell/AppShell.tsx"],"sourcesContent":["import { AppShell as MantineAppShell, useMantineTheme } from '@mantine/core';\nimport React, { Suspense, useState } from 'react';\nimport { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary';\nimport { Loading } from '../Loading/Loading';\nimport { useMedplum, useMedplumProfile } from '../MedplumProvider/MedplumProvider';\nimport { Header } from './Header';\nimport { Navbar, NavbarMenu } from './Navbar';\n\nexport interface AppShellProps {\n logo: React.ReactNode;\n version?: string;\n menus?: NavbarMenu[];\n children: React.ReactNode;\n}\n\nexport function AppShell(props: AppShellProps): JSX.Element {\n const theme = useMantineTheme();\n const [navbarOpen, setNavbarOpen] = useState(localStorage['navbarOpen'] === 'true');\n const medplum = useMedplum();\n const profile = useMedplumProfile();\n\n function setNavbarOpenWrapper(open: boolean): void {\n localStorage['navbarOpen'] = open.toString();\n setNavbarOpen(open);\n }\n\n function closeNavbar(): void {\n setNavbarOpenWrapper(false);\n }\n\n function toggleNavbar(): void {\n setNavbarOpenWrapper(!navbarOpen);\n }\n\n if (medplum.isLoading()) {\n return <Loading />;\n }\n\n return (\n <MantineAppShell\n styles={{\n main: {\n background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],\n },\n }}\n padding={0}\n fixed={true}\n header={profile && <Header logo={props.logo} version={props.version} navbarToggle={toggleNavbar} />}\n navbar={profile && navbarOpen ? <Navbar menus={props.menus} closeNavbar={closeNavbar} /> : undefined}\n >\n <ErrorBoundary>\n <Suspense fallback={<Loading />}>{props.children}</Suspense>\n </ErrorBoundary>\n </MantineAppShell>\n );\n}\n"],"names":["MantineAppShell"],"mappings":";;;;;;;;AAeM,SAAU,QAAQ,CAAC,KAAoB,EAAA;AAC3C,IAAA,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;AAChC,IAAA,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC,CAAC;AACpF,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,IAAA,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IAEpC,SAAS,oBAAoB,CAAC,IAAa,EAAA;QACzC,YAAY,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7C,aAAa,CAAC,IAAI,CAAC,CAAC;KACrB;AAED,IAAA,SAAS,WAAW,GAAA;QAClB,oBAAoB,CAAC,KAAK,CAAC,CAAC;KAC7B;AAED,IAAA,SAAS,YAAY,GAAA;AACnB,QAAA,oBAAoB,CAAC,CAAC,UAAU,CAAC,CAAC;KACnC;AAED,IAAA,IAAI,OAAO,CAAC,SAAS,EAAE,EAAE;QACvB,OAAO,KAAA,CAAA,aAAA,CAAC,OAAO,EAAA,IAAA,CAAG,CAAC;AACpB,KAAA;AAED,IAAA,QACE,KAAA,CAAA,aAAA,CAACA,UAAe,EAAA,EACd,MAAM,EAAE;AACN,YAAA,IAAI,EAAE;gBACJ,UAAU,EAAE,KAAK,CAAC,WAAW,KAAK,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACvF,aAAA;SACF,EACD,OAAO,EAAE,CAAC,EACV,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,OAAO,IAAI,KAAA,CAAA,aAAA,CAAC,MAAM,EAAC,EAAA,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAA,CAAI,EACnG,MAAM,EAAE,OAAO,IAAI,UAAU,GAAG,KAAA,CAAA,aAAA,CAAC,MAAM,EAAA,EAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAI,CAAA,GAAG,SAAS,EAAA;AAEpG,QAAA,KAAA,CAAA,aAAA,CAAC,aAAa,EAAA,IAAA;AACZ,YAAA,KAAA,CAAA,aAAA,CAAC,QAAQ,EAAA,EAAC,QAAQ,EAAE,oBAAC,OAAO,EAAA,IAAA,CAAG,EAAG,EAAA,KAAK,CAAC,QAAQ,CAAY,CAC9C,CACA,EAClB;AACJ;;;;"}
@@ -0,0 +1,88 @@
1
+ import { createStyles, Header as Header$1, Group, UnstyledButton, Menu, Text, Stack, Avatar } from '@mantine/core';
2
+ import { formatHumanName, getReferenceString } from '@medplum/core';
3
+ import React, { useState } from 'react';
4
+ import { HumanNameDisplay } from '../HumanNameDisplay/HumanNameDisplay.mjs';
5
+ import { useMedplumContext } from '../MedplumProvider/MedplumProvider.mjs';
6
+ import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar.mjs';
7
+ import { HeaderSearchInput } from './HeaderSearchInput.mjs';
8
+ import IconChevronDown from '../node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs';
9
+ import IconSwitchHorizontal from '../node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs';
10
+ import IconSettings from '../node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs';
11
+ import IconLogout from '../node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs';
12
+
13
+ const useStyles = createStyles((theme) => ({
14
+ logoButton: {
15
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
16
+ borderRadius: theme.radius.sm,
17
+ transition: 'background-color 100ms ease',
18
+ '&:hover': {
19
+ backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
20
+ },
21
+ },
22
+ user: {
23
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
24
+ borderRadius: theme.radius.sm,
25
+ transition: 'background-color 100ms ease',
26
+ '&:hover': {
27
+ backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
28
+ },
29
+ },
30
+ userName: {
31
+ fontWeight: 500,
32
+ lineHeight: 1,
33
+ marginRight: 3,
34
+ [theme.fn.smallerThan('xs')]: {
35
+ display: 'none',
36
+ },
37
+ },
38
+ userActive: {
39
+ backgroundColor: theme.fn.lighten(theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background, 0.8),
40
+ },
41
+ }));
42
+ function Header(props) {
43
+ const context = useMedplumContext();
44
+ const { medplum, profile, navigate } = context;
45
+ const logins = medplum.getLogins();
46
+ const { classes, cx } = useStyles();
47
+ const [userMenuOpened, setUserMenuOpened] = useState(false);
48
+ return (React.createElement(Header$1, { height: 60, p: 8, style: { zIndex: 101 } },
49
+ React.createElement(Group, { position: "apart" },
50
+ React.createElement(Group, { spacing: "xs" },
51
+ React.createElement(UnstyledButton, { className: classes.logoButton, onClick: props.navbarToggle }, props.logo),
52
+ React.createElement(HeaderSearchInput, null)),
53
+ React.createElement(Menu, { width: 260, shadow: "xl", position: "bottom-end", transitionProps: { transition: 'pop-top-right' }, opened: userMenuOpened, onClose: () => setUserMenuOpened(false) },
54
+ React.createElement(Menu.Target, null,
55
+ React.createElement(UnstyledButton, { className: cx(classes.user, { [classes.userActive]: userMenuOpened }), onClick: () => setUserMenuOpened((o) => !o) },
56
+ React.createElement(Group, { spacing: 7 },
57
+ React.createElement(ResourceAvatar, { value: profile, radius: "xl", size: 24 }),
58
+ React.createElement(Text, { size: "sm", className: classes.userName }, formatHumanName(profile?.name?.[0])),
59
+ React.createElement(IconChevronDown, { size: 12, stroke: 1.5 })))),
60
+ React.createElement(Menu.Dropdown, null,
61
+ React.createElement(Stack, { align: "center", p: "xl" },
62
+ React.createElement(ResourceAvatar, { size: "xl", radius: 100, value: context.profile }),
63
+ React.createElement(HumanNameDisplay, { value: context.profile?.name?.[0] }),
64
+ React.createElement(Text, { color: "dimmed", size: "xs" }, medplum.getActiveLogin()?.project?.display)),
65
+ logins.length > 1 && React.createElement(Menu.Divider, null),
66
+ logins.map((login) => login.profile?.reference !== getReferenceString(context.profile) && (React.createElement(Menu.Item, { key: login.profile?.reference, onClick: () => {
67
+ medplum
68
+ .setActiveLogin(login)
69
+ .then(() => window.location.reload())
70
+ .catch(console.log);
71
+ } },
72
+ React.createElement(Group, null,
73
+ React.createElement(Avatar, { radius: "xl" }),
74
+ React.createElement("div", { style: { flex: 1 } },
75
+ React.createElement(Text, { size: "sm", weight: 500 }, login.profile?.display),
76
+ React.createElement(Text, { color: "dimmed", size: "xs" }, login.project?.display)))))),
77
+ React.createElement(Menu.Divider, null),
78
+ React.createElement(Menu.Item, { icon: React.createElement(IconSwitchHorizontal, { size: 14, stroke: 1.5 }), onClick: () => navigate('/signin') }, "Add another account"),
79
+ React.createElement(Menu.Item, { icon: React.createElement(IconSettings, { size: 14, stroke: 1.5 }), onClick: () => navigate(`/${getReferenceString(profile)}`) }, "Account settings"),
80
+ React.createElement(Menu.Item, { icon: React.createElement(IconLogout, { size: 14, stroke: 1.5 }), onClick: async () => {
81
+ await medplum.signOut();
82
+ navigate('/signin');
83
+ } }, "Sign out"),
84
+ React.createElement(Text, { size: "xs", color: "dimmed", align: "center" }, props.version))))));
85
+ }
86
+
87
+ export { Header };
88
+ //# sourceMappingURL=Header.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Header.mjs","sources":["../../../src/AppShell/Header.tsx"],"sourcesContent":["import { Avatar, createStyles, Group, Header as MantineHeader, Menu, Stack, Text, UnstyledButton } from '@mantine/core';\nimport { formatHumanName, getReferenceString, ProfileResource } from '@medplum/core';\nimport { HumanName } from '@medplum/fhirtypes';\nimport { IconChevronDown, IconLogout, IconSettings, IconSwitchHorizontal } from '@tabler/icons-react';\nimport React, { useState } from 'react';\nimport { HumanNameDisplay } from '../HumanNameDisplay/HumanNameDisplay';\nimport { useMedplumContext } from '../MedplumProvider/MedplumProvider';\nimport { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar';\nimport { HeaderSearchInput } from './HeaderSearchInput';\n\nconst useStyles = createStyles((theme) => ({\n logoButton: {\n padding: `${theme.spacing.xs} ${theme.spacing.sm}`,\n borderRadius: theme.radius.sm,\n transition: 'background-color 100ms ease',\n\n '&:hover': {\n backgroundColor: theme.fn.lighten(\n theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background as string,\n 0.8\n ),\n },\n },\n\n user: {\n padding: `${theme.spacing.xs} ${theme.spacing.sm}`,\n borderRadius: theme.radius.sm,\n transition: 'background-color 100ms ease',\n\n '&:hover': {\n backgroundColor: theme.fn.lighten(\n theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background as string,\n 0.8\n ),\n },\n },\n\n userName: {\n fontWeight: 500,\n lineHeight: 1,\n marginRight: 3,\n\n [theme.fn.smallerThan('xs')]: {\n display: 'none',\n },\n },\n\n userActive: {\n backgroundColor: theme.fn.lighten(\n theme.fn.variant({ variant: 'filled', color: theme.primaryColor }).background as string,\n 0.8\n ),\n },\n}));\n\ninterface HeaderProps {\n logo: React.ReactNode;\n version?: string;\n navbarToggle: () => void;\n}\n\nexport function Header(props: HeaderProps): JSX.Element {\n const context = useMedplumContext();\n const { medplum, profile, navigate } = context;\n const logins = medplum.getLogins();\n const { classes, cx } = useStyles();\n const [userMenuOpened, setUserMenuOpened] = useState(false);\n\n return (\n <MantineHeader height={60} p={8} style={{ zIndex: 101 }}>\n <Group position=\"apart\">\n <Group spacing=\"xs\">\n <UnstyledButton className={classes.logoButton} onClick={props.navbarToggle}>\n {props.logo}\n </UnstyledButton>\n <HeaderSearchInput />\n </Group>\n\n <Menu\n width={260}\n shadow=\"xl\"\n position=\"bottom-end\"\n transitionProps={{ transition: 'pop-top-right' }}\n opened={userMenuOpened}\n onClose={() => setUserMenuOpened(false)}\n >\n <Menu.Target>\n <UnstyledButton\n className={cx(classes.user, { [classes.userActive]: userMenuOpened })}\n onClick={() => setUserMenuOpened((o) => !o)}\n >\n <Group spacing={7}>\n <ResourceAvatar value={profile} radius=\"xl\" size={24} />\n <Text size=\"sm\" className={classes.userName}>\n {formatHumanName(profile?.name?.[0] as HumanName)}\n </Text>\n <IconChevronDown size={12} stroke={1.5} />\n </Group>\n </UnstyledButton>\n </Menu.Target>\n <Menu.Dropdown>\n <Stack align=\"center\" p=\"xl\">\n <ResourceAvatar size=\"xl\" radius={100} value={context.profile} />\n <HumanNameDisplay value={context.profile?.name?.[0] as HumanName} />\n <Text color=\"dimmed\" size=\"xs\">\n {medplum.getActiveLogin()?.project?.display}\n </Text>\n </Stack>\n {logins.length > 1 && <Menu.Divider />}\n {logins.map(\n (login) =>\n login.profile?.reference !== getReferenceString(context.profile as ProfileResource) && (\n <Menu.Item\n key={login.profile?.reference}\n onClick={() => {\n medplum\n .setActiveLogin(login)\n .then(() => window.location.reload())\n .catch(console.log);\n }}\n >\n <Group>\n <Avatar radius=\"xl\" />\n <div style={{ flex: 1 }}>\n <Text size=\"sm\" weight={500}>\n {login.profile?.display}\n </Text>\n <Text color=\"dimmed\" size=\"xs\">\n {login.project?.display}\n </Text>\n </div>\n </Group>\n </Menu.Item>\n )\n )}\n <Menu.Divider />\n <Menu.Item icon={<IconSwitchHorizontal size={14} stroke={1.5} />} onClick={() => navigate('/signin')}>\n Add another account\n </Menu.Item>\n <Menu.Item\n icon={<IconSettings size={14} stroke={1.5} />}\n onClick={() => navigate(`/${getReferenceString(profile as ProfileResource)}`)}\n >\n Account settings\n </Menu.Item>\n <Menu.Item\n icon={<IconLogout size={14} stroke={1.5} />}\n onClick={async () => {\n await medplum.signOut();\n navigate('/signin');\n }}\n >\n Sign out\n </Menu.Item>\n <Text size=\"xs\" color=\"dimmed\" align=\"center\">\n {props.version}\n </Text>\n </Menu.Dropdown>\n </Menu>\n </Group>\n </MantineHeader>\n );\n}\n"],"names":["MantineHeader"],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ import { createStyles, Group, Text } from '@mantine/core';
2
+ import { getDisplayString, getReferenceString, isUUID, formatHumanName } from '@medplum/core';
3
+ import React, { forwardRef, useCallback } from 'react';
4
+ import { useLocation } from 'react-router-dom';
5
+ import { AsyncAutocomplete } from '../AsyncAutocomplete/AsyncAutocomplete.mjs';
6
+ import { useMedplumNavigate, useMedplum } from '../MedplumProvider/MedplumProvider.mjs';
7
+ import { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar.mjs';
8
+ import IconSearch from '../node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs';
9
+
10
+ const useStyles = createStyles(() => {
11
+ return {
12
+ searchInput: {
13
+ input: {
14
+ width: 220,
15
+ transition: 'width 0.2s',
16
+ },
17
+ 'input:focus': {
18
+ width: 400,
19
+ },
20
+ '@media (max-width: 800px)': {
21
+ input: {
22
+ width: 150,
23
+ },
24
+ 'input:focus': {
25
+ width: 150,
26
+ },
27
+ },
28
+ },
29
+ };
30
+ });
31
+ function toKey(resource) {
32
+ return resource.id;
33
+ }
34
+ function toOption(resource) {
35
+ return {
36
+ value: resource.id,
37
+ label: getDisplayString(resource),
38
+ resource,
39
+ };
40
+ }
41
+ function HeaderSearchInput() {
42
+ const { classes } = useStyles();
43
+ const navigate = useMedplumNavigate();
44
+ const medplum = useMedplum();
45
+ const location = useLocation();
46
+ const loadData = useCallback(async (input, signal) => {
47
+ const query = buildGraphQLQuery(input);
48
+ const options = { signal };
49
+ const response = (await medplum.graphql(query, undefined, undefined, options));
50
+ return getResourcesFromResponse(response, input);
51
+ }, [medplum]);
52
+ const handleSelect = useCallback((item) => {
53
+ if (item.length > 0) {
54
+ navigate(`/${getReferenceString(item[0])}`);
55
+ }
56
+ }, [navigate]);
57
+ return (React.createElement(AsyncAutocomplete, { key: location.pathname, size: "sm", radius: "md", className: classes.searchInput, icon: React.createElement(IconSearch, { size: 16 }), placeholder: "Search", itemComponent: ItemComponent, toKey: toKey, toOption: toOption, onChange: handleSelect, loadOptions: loadData }));
58
+ }
59
+ const ItemComponent = forwardRef(({ resource, ...others }, ref) => {
60
+ let helpText = undefined;
61
+ if (resource.resourceType === 'Patient') {
62
+ helpText = resource.birthDate;
63
+ }
64
+ else if (resource.resourceType === 'ServiceRequest') {
65
+ helpText = resource.subject?.display;
66
+ }
67
+ return (React.createElement("div", { ref: ref, ...others },
68
+ React.createElement(Group, { noWrap: true },
69
+ React.createElement(ResourceAvatar, { value: resource }),
70
+ React.createElement("div", null,
71
+ React.createElement(Text, null, getDisplayString(resource)),
72
+ React.createElement(Text, { size: "xs", color: "dimmed" }, helpText)))));
73
+ });
74
+ function buildGraphQLQuery(input) {
75
+ const escaped = JSON.stringify(input);
76
+ if (isUUID(input)) {
77
+ return `{
78
+ Patients1: PatientList(_id: ${escaped}, _count: 1) {
79
+ resourceType
80
+ id
81
+ identifier {
82
+ system
83
+ value
84
+ }
85
+ name {
86
+ given
87
+ family
88
+ }
89
+ birthDate
90
+ }
91
+ ServiceRequestList(_id: ${escaped}, _count: 1) {
92
+ resourceType
93
+ id
94
+ identifier {
95
+ system
96
+ value
97
+ }
98
+ subject {
99
+ display
100
+ }
101
+ }
102
+ }`.replace(/\s+/g, ' ');
103
+ }
104
+ return `{
105
+ Patients1: PatientList(name: ${escaped}, _count: 5) {
106
+ resourceType
107
+ id
108
+ identifier {
109
+ system
110
+ value
111
+ }
112
+ name {
113
+ given
114
+ family
115
+ }
116
+ birthDate
117
+ }
118
+ Patients2: PatientList(identifier: ${escaped}, _count: 5) {
119
+ resourceType
120
+ id
121
+ identifier {
122
+ system
123
+ value
124
+ }
125
+ name {
126
+ given
127
+ family
128
+ }
129
+ birthDate
130
+ }
131
+ ServiceRequestList(identifier: ${escaped}, _count: 5) {
132
+ resourceType
133
+ id
134
+ identifier {
135
+ system
136
+ value
137
+ }
138
+ subject {
139
+ display
140
+ }
141
+ }
142
+ }`.replace(/\s+/g, ' ');
143
+ }
144
+ /**
145
+ * Returns a de-duped and sorted list of resources from the search response.
146
+ * The search request is actually 3+ separate searches, which can include duplicates.
147
+ * This function combines the results, de-dupes, and sorts by relevance.
148
+ * @param response The response from a search query.
149
+ * @param query The user entered search query.
150
+ * @returns The resources to display in the autocomplete.
151
+ */
152
+ function getResourcesFromResponse(response, query) {
153
+ const resources = [];
154
+ if (response.data.Patients1) {
155
+ resources.push(...response.data.Patients1);
156
+ }
157
+ if (response.data.Patients2) {
158
+ resources.push(...response.data.Patients2);
159
+ }
160
+ if (response.data.ServiceRequestList) {
161
+ resources.push(...response.data.ServiceRequestList);
162
+ }
163
+ return sortByRelevance(dedupeResources(resources), query).slice(0, 5);
164
+ }
165
+ /**
166
+ * Removes duplicate resources from an array by ID.
167
+ * @param resources The array of resources with possible duplicates.
168
+ * @returns The array of resources with no duplicates.
169
+ */
170
+ function dedupeResources(resources) {
171
+ const ids = new Set();
172
+ const result = [];
173
+ for (const resource of resources) {
174
+ if (!ids.has(resource.id)) {
175
+ ids.add(resource.id);
176
+ result.push(resource);
177
+ }
178
+ }
179
+ return result;
180
+ }
181
+ /**
182
+ * Sorts an array of resources by relevance.
183
+ * @param resources The candidate resources.
184
+ * @param query The user entered search string.
185
+ * @returns The sorted array of resources.
186
+ */
187
+ function sortByRelevance(resources, query) {
188
+ return resources.sort((a, b) => {
189
+ return getResourceScore(b, query) - getResourceScore(a, query);
190
+ });
191
+ }
192
+ /**
193
+ * Calculates a relevance score of a candidate resource.
194
+ * Higher scores are better.
195
+ * @param resource The candidate resource.
196
+ * @param query The user entered search string.
197
+ * @returns The relevance score of the candidate resource.
198
+ */
199
+ function getResourceScore(resource, query) {
200
+ let bestScore = 0;
201
+ if (resource.identifier) {
202
+ for (const identifier of resource.identifier) {
203
+ bestScore = Math.max(bestScore, getStringScore(identifier.value, query));
204
+ }
205
+ }
206
+ if (resource.resourceType === 'Patient' && resource.name) {
207
+ for (const name of resource.name) {
208
+ bestScore = Math.max(bestScore, getStringScore(formatHumanName(name), query));
209
+ }
210
+ }
211
+ return bestScore;
212
+ }
213
+ /**
214
+ * Calculates a relevance score of a candidate display string.
215
+ * Higher scores are better.
216
+ * @param str The candidate display string.
217
+ * @param query The user entered search string.
218
+ * @returns The relevance score of the candidate string.
219
+ */
220
+ function getStringScore(str, query) {
221
+ if (!str) {
222
+ return 0;
223
+ }
224
+ const index = str.toLowerCase().indexOf(query.toLowerCase());
225
+ if (index < 0) {
226
+ return 0;
227
+ }
228
+ return 100 - index;
229
+ }
230
+
231
+ export { HeaderSearchInput };
232
+ //# sourceMappingURL=HeaderSearchInput.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeaderSearchInput.mjs","sources":["../../../src/AppShell/HeaderSearchInput.tsx"],"sourcesContent":["import { createStyles, Group, Text } from '@mantine/core';\nimport { formatHumanName, getDisplayString, getReferenceString, isUUID } from '@medplum/core';\nimport { Patient, ServiceRequest } from '@medplum/fhirtypes';\nimport { IconSearch } from '@tabler/icons-react';\nimport React, { forwardRef, useCallback } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { AsyncAutocomplete, AsyncAutocompleteOption } from '../AsyncAutocomplete/AsyncAutocomplete';\nimport { useMedplum, useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\nimport { ResourceAvatar } from '../ResourceAvatar/ResourceAvatar';\n\nexport type HeaderSearchTypes = Patient | ServiceRequest;\n\nconst useStyles = createStyles(() => {\n return {\n searchInput: {\n input: {\n width: 220,\n transition: 'width 0.2s',\n },\n 'input:focus': {\n width: 400,\n },\n '@media (max-width: 800px)': {\n input: {\n width: 150,\n },\n 'input:focus': {\n width: 150,\n },\n },\n },\n };\n});\n\ninterface SearchGraphQLResponse {\n readonly data: {\n readonly Patients1: Patient[] | undefined;\n readonly Patients2: Patient[] | undefined;\n readonly ServiceRequestList: ServiceRequest[] | undefined;\n };\n}\n\nfunction toKey(resource: HeaderSearchTypes): string {\n return resource.id as string;\n}\n\nfunction toOption(resource: HeaderSearchTypes): AsyncAutocompleteOption<HeaderSearchTypes> {\n return {\n value: resource.id as string,\n label: getDisplayString(resource),\n resource,\n };\n}\n\nexport function HeaderSearchInput(): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const medplum = useMedplum();\n const location = useLocation();\n\n const loadData = useCallback(\n async (input: string, signal: AbortSignal): Promise<HeaderSearchTypes[]> => {\n const query = buildGraphQLQuery(input);\n const options = { signal };\n const response = (await medplum.graphql(query, undefined, undefined, options)) as SearchGraphQLResponse;\n return getResourcesFromResponse(response, input);\n },\n [medplum]\n );\n\n const handleSelect = useCallback(\n (item: HeaderSearchTypes[]): void => {\n if (item.length > 0) {\n navigate(`/${getReferenceString(item[0])}`);\n }\n },\n [navigate]\n );\n\n return (\n <AsyncAutocomplete\n key={location.pathname}\n size=\"sm\"\n radius=\"md\"\n className={classes.searchInput}\n icon={<IconSearch size={16} />}\n placeholder=\"Search\"\n itemComponent={ItemComponent}\n toKey={toKey}\n toOption={toOption}\n onChange={handleSelect}\n loadOptions={loadData}\n />\n );\n}\n\nconst ItemComponent = forwardRef<HTMLDivElement, any>(\n ({ resource, ...others }: AsyncAutocompleteOption<HeaderSearchTypes>, ref) => {\n let helpText: string | undefined = undefined;\n\n if (resource.resourceType === 'Patient') {\n helpText = resource.birthDate;\n } else if (resource.resourceType === 'ServiceRequest') {\n helpText = resource.subject?.display;\n }\n\n return (\n <div ref={ref} {...others}>\n <Group noWrap>\n <ResourceAvatar value={resource} />\n <div>\n <Text>{getDisplayString(resource)}</Text>\n <Text size=\"xs\" color=\"dimmed\">\n {helpText}\n </Text>\n </div>\n </Group>\n </div>\n );\n }\n);\n\nfunction buildGraphQLQuery(input: string): string {\n const escaped = JSON.stringify(input);\n if (isUUID(input)) {\n return `{\n Patients1: PatientList(_id: ${escaped}, _count: 1) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n ServiceRequestList(_id: ${escaped}, _count: 1) {\n resourceType\n id\n identifier {\n system\n value\n }\n subject {\n display\n }\n }\n }`.replace(/\\s+/g, ' ');\n }\n return `{\n Patients1: PatientList(name: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n Patients2: PatientList(identifier: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n name {\n given\n family\n }\n birthDate\n }\n ServiceRequestList(identifier: ${escaped}, _count: 5) {\n resourceType\n id\n identifier {\n system\n value\n }\n subject {\n display\n }\n }\n }`.replace(/\\s+/g, ' ');\n}\n\n/**\n * Returns a de-duped and sorted list of resources from the search response.\n * The search request is actually 3+ separate searches, which can include duplicates.\n * This function combines the results, de-dupes, and sorts by relevance.\n * @param response The response from a search query.\n * @param query The user entered search query.\n * @returns The resources to display in the autocomplete.\n */\nfunction getResourcesFromResponse(response: SearchGraphQLResponse, query: string): HeaderSearchTypes[] {\n const resources = [];\n if (response.data.Patients1) {\n resources.push(...response.data.Patients1);\n }\n if (response.data.Patients2) {\n resources.push(...response.data.Patients2);\n }\n if (response.data.ServiceRequestList) {\n resources.push(...response.data.ServiceRequestList);\n }\n return sortByRelevance(dedupeResources(resources), query).slice(0, 5);\n}\n\n/**\n * Removes duplicate resources from an array by ID.\n * @param resources The array of resources with possible duplicates.\n * @returns The array of resources with no duplicates.\n */\nfunction dedupeResources(resources: HeaderSearchTypes[]): HeaderSearchTypes[] {\n const ids = new Set<string>();\n const result = [];\n\n for (const resource of resources) {\n if (!ids.has(resource.id as string)) {\n ids.add(resource.id as string);\n result.push(resource);\n }\n }\n\n return result;\n}\n\n/**\n * Sorts an array of resources by relevance.\n * @param resources The candidate resources.\n * @param query The user entered search string.\n * @returns The sorted array of resources.\n */\nfunction sortByRelevance(resources: HeaderSearchTypes[], query: string): HeaderSearchTypes[] {\n return resources.sort((a: HeaderSearchTypes, b: HeaderSearchTypes) => {\n return getResourceScore(b, query) - getResourceScore(a, query);\n });\n}\n\n/**\n * Calculates a relevance score of a candidate resource.\n * Higher scores are better.\n * @param resource The candidate resource.\n * @param query The user entered search string.\n * @returns The relevance score of the candidate resource.\n */\nfunction getResourceScore(resource: HeaderSearchTypes, query: string): number {\n let bestScore = 0;\n\n if (resource.identifier) {\n for (const identifier of resource.identifier) {\n bestScore = Math.max(bestScore, getStringScore(identifier.value, query));\n }\n }\n\n if (resource.resourceType === 'Patient' && resource.name) {\n for (const name of resource.name) {\n bestScore = Math.max(bestScore, getStringScore(formatHumanName(name), query));\n }\n }\n\n return bestScore;\n}\n\n/**\n * Calculates a relevance score of a candidate display string.\n * Higher scores are better.\n * @param str The candidate display string.\n * @param query The user entered search string.\n * @returns The relevance score of the candidate string.\n */\nfunction getStringScore(str: string | undefined, query: string): number {\n if (!str) {\n return 0;\n }\n const index = str.toLowerCase().indexOf(query.toLowerCase());\n if (index < 0) {\n return 0;\n }\n return 100 - index;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ import { createStyles, Navbar as Navbar$1, Text, Space } from '@mantine/core';
2
+ import React from 'react';
3
+ import { useLocation, useSearchParams } from 'react-router-dom';
4
+ import { CodeInput } from '../CodeInput/CodeInput.mjs';
5
+ import { MedplumLink } from '../MedplumLink/MedplumLink.mjs';
6
+ import { useMedplumNavigate } from '../MedplumProvider/MedplumProvider.mjs';
7
+
8
+ const useStyles = createStyles((theme) => {
9
+ return {
10
+ menuTitle: {
11
+ margin: '20px 0 4px 6px',
12
+ fontSize: '9px',
13
+ fontWeight: 'normal',
14
+ textTransform: 'uppercase',
15
+ letterSpacing: '2px',
16
+ },
17
+ link: {
18
+ ...theme.fn.focusStyles(),
19
+ display: 'flex',
20
+ alignItems: 'center',
21
+ textDecoration: 'none',
22
+ fontSize: theme.fontSizes.sm,
23
+ color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],
24
+ padding: `8px 12px`,
25
+ borderRadius: theme.radius.sm,
26
+ fontWeight: 500,
27
+ '&:hover': {
28
+ backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
29
+ color: theme.colorScheme === 'dark' ? theme.white : theme.black,
30
+ textDecoration: 'none',
31
+ [`& svg`]: {
32
+ color: theme.colorScheme === 'dark' ? theme.white : theme.black,
33
+ },
34
+ },
35
+ '& svg': {
36
+ color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],
37
+ marginRight: theme.spacing.sm,
38
+ strokeWidth: 1.5,
39
+ width: 18,
40
+ height: 18,
41
+ },
42
+ },
43
+ linkActive: {
44
+ '&, &:hover': {
45
+ backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,
46
+ color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,
47
+ [`& svg`]: {
48
+ color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,
49
+ },
50
+ },
51
+ },
52
+ };
53
+ });
54
+ function Navbar(props) {
55
+ const { classes } = useStyles();
56
+ const navigate = useMedplumNavigate();
57
+ function onLinkClick(e, to) {
58
+ e.stopPropagation();
59
+ e.preventDefault();
60
+ navigate(to);
61
+ if (window.innerWidth < 768) {
62
+ props.closeNavbar();
63
+ }
64
+ }
65
+ function navigateResourceType(resourceType) {
66
+ if (resourceType) {
67
+ navigate(`/${resourceType}`);
68
+ }
69
+ }
70
+ return (React.createElement(Navbar$1, { width: { sm: 250 }, p: "xs" },
71
+ React.createElement(Navbar$1.Section, null,
72
+ React.createElement(CodeInput, { key: window.location.pathname, name: "resourceType", placeholder: "Resource Type", property: {
73
+ binding: {
74
+ valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',
75
+ },
76
+ }, onChange: (newValue) => navigateResourceType(newValue), creatable: false, maxSelectedValues: 0, clearSearchOnChange: true, clearable: false })),
77
+ props.menus && (React.createElement(Navbar$1.Section, { grow: true }, props.menus.map((menu) => (React.createElement(React.Fragment, { key: `menu-${menu.title}` },
78
+ React.createElement(Text, { className: classes.menuTitle }, menu.title),
79
+ menu.links?.map((link) => (React.createElement(NavbarLink, { key: link.href, to: link.href, onClick: (e) => onLinkClick(e, link.href) },
80
+ React.createElement(NavLinkIcon, { to: link.href, icon: link.icon }),
81
+ React.createElement("span", null, link.label)))))))))));
82
+ }
83
+ function NavbarLink(props) {
84
+ const { classes, cx } = useStyles();
85
+ const location = useLocation();
86
+ const [searchParams] = useSearchParams();
87
+ const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);
88
+ const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);
89
+ return (React.createElement(MedplumLink, { onClick: props.onClick, to: props.to, className: cx(classes.link, { [classes.linkActive]: isActive }) }, props.children));
90
+ }
91
+ /**
92
+ * Returns true if the search params match.
93
+ * @param searchParams The current search params.
94
+ * @param toUrl The destination URL of the link.
95
+ * @returns True if the search params match.
96
+ */
97
+ function matchesParams(searchParams, toUrl) {
98
+ for (const [key, value] of toUrl.searchParams.entries()) {
99
+ if (searchParams.get(key) !== value) {
100
+ return false;
101
+ }
102
+ }
103
+ return true;
104
+ }
105
+ function NavLinkIcon(props) {
106
+ if (props.icon) {
107
+ return props.icon;
108
+ }
109
+ return React.createElement(Space, { w: 30 });
110
+ }
111
+
112
+ export { Navbar };
113
+ //# sourceMappingURL=Navbar.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Navbar.mjs","sources":["../../../src/AppShell/Navbar.tsx"],"sourcesContent":["import { createStyles, Navbar as MantineNavbar, Space, Text } from '@mantine/core';\nimport React from 'react';\nimport { useLocation, useSearchParams } from 'react-router-dom';\nimport { CodeInput } from '../CodeInput/CodeInput';\nimport { MedplumLink } from '../MedplumLink/MedplumLink';\nimport { useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\n\nconst useStyles = createStyles((theme) => {\n return {\n menuTitle: {\n margin: '20px 0 4px 6px',\n fontSize: '9px',\n fontWeight: 'normal',\n textTransform: 'uppercase',\n letterSpacing: '2px',\n },\n\n link: {\n ...theme.fn.focusStyles(),\n display: 'flex',\n alignItems: 'center',\n textDecoration: 'none',\n fontSize: theme.fontSizes.sm,\n color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],\n padding: `8px 12px`,\n borderRadius: theme.radius.sm,\n fontWeight: 500,\n\n '&:hover': {\n backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n textDecoration: 'none',\n\n [`& svg`]: {\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n },\n },\n\n '& svg': {\n color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],\n marginRight: theme.spacing.sm,\n strokeWidth: 1.5,\n width: 18,\n height: 18,\n },\n },\n\n linkActive: {\n '&, &:hover': {\n backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n [`& svg`]: {\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n },\n },\n },\n };\n});\n\nexport interface NavbarLink {\n icon?: JSX.Element;\n label?: string;\n href: string;\n}\n\nexport interface NavbarMenu {\n title?: string;\n links?: NavbarLink[];\n}\n\nexport interface NavbarProps {\n menus?: NavbarMenu[];\n closeNavbar: () => void;\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n\n function onLinkClick(e: React.SyntheticEvent, to: string): void {\n e.stopPropagation();\n e.preventDefault();\n navigate(to);\n if (window.innerWidth < 768) {\n props.closeNavbar();\n }\n }\n\n function navigateResourceType(resourceType: string | undefined): void {\n if (resourceType) {\n navigate(`/${resourceType}`);\n }\n }\n\n return (\n <MantineNavbar width={{ sm: 250 }} p=\"xs\">\n <MantineNavbar.Section>\n <CodeInput\n key={window.location.pathname}\n name=\"resourceType\"\n placeholder=\"Resource Type\"\n property={{\n binding: {\n valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',\n },\n }}\n onChange={(newValue) => navigateResourceType(newValue)}\n creatable={false}\n maxSelectedValues={0}\n clearSearchOnChange={true}\n clearable={false}\n />\n </MantineNavbar.Section>\n {props.menus && (\n <MantineNavbar.Section grow>\n {props.menus.map((menu) => (\n <React.Fragment key={`menu-${menu.title}`}>\n <Text className={classes.menuTitle}>{menu.title}</Text>\n {menu.links?.map((link) => (\n <NavbarLink key={link.href} to={link.href} onClick={(e) => onLinkClick(e, link.href)}>\n <NavLinkIcon to={link.href} icon={link.icon} />\n <span>{link.label}</span>\n </NavbarLink>\n ))}\n </React.Fragment>\n ))}\n </MantineNavbar.Section>\n )}\n </MantineNavbar>\n );\n}\n\ninterface NavbarLinkProps {\n to: string;\n onClick: React.MouseEventHandler;\n children: React.ReactNode;\n}\n\nfunction NavbarLink(props: NavbarLinkProps): JSX.Element {\n const { classes, cx } = useStyles();\n const location = useLocation();\n const [searchParams] = useSearchParams();\n const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);\n const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);\n\n return (\n <MedplumLink onClick={props.onClick} to={props.to} className={cx(classes.link, { [classes.linkActive]: isActive })}>\n {props.children}\n </MedplumLink>\n );\n}\n\n/**\n * Returns true if the search params match.\n * @param searchParams The current search params.\n * @param toUrl The destination URL of the link.\n * @returns True if the search params match.\n */\nfunction matchesParams(searchParams: URLSearchParams, toUrl: URL): boolean {\n for (const [key, value] of toUrl.searchParams.entries()) {\n if (searchParams.get(key) !== value) {\n return false;\n }\n }\n return true;\n}\n\ninterface NavLinkIconProps {\n to: string;\n icon?: JSX.Element;\n}\n\nfunction NavLinkIcon(props: NavLinkIconProps): JSX.Element {\n if (props.icon) {\n return props.icon;\n }\n return <Space w={30} />;\n}\n"],"names":["MantineNavbar"],"mappings":"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"}
@@ -3,7 +3,7 @@ import React, { useRef, useState, useCallback, useEffect } from 'react';
3
3
  import { killEvent } from '../utils/dom.mjs';
4
4
 
5
5
  function AsyncAutocomplete(props) {
6
- const { defaultValue, toKey, toOption, loadOptions, onChange, onCreate, ...rest } = props;
6
+ const { defaultValue, toKey, toOption, loadOptions, onChange, onCreate, creatable, ...rest } = props;
7
7
  const defaultItems = toDefaultItems(defaultValue);
8
8
  const inputRef = useRef(null);
9
9
  const [lastValue, setLastValue] = useState(undefined);
@@ -61,13 +61,14 @@ function AsyncAutocomplete(props) {
61
61
  const result = [];
62
62
  for (const value of values) {
63
63
  let item = optionsRef.current?.find((option) => option.value === value)?.resource;
64
- if (!item) {
64
+ if (!item && creatable !== false) {
65
65
  item = onCreate(value);
66
66
  }
67
- result.push(item);
67
+ if (item)
68
+ result.push(item);
68
69
  }
69
70
  onChange(result);
70
- }, [onChange, onCreate]);
71
+ }, [creatable, onChange, onCreate]);
71
72
  const handleKeyDown = useCallback((e) => {
72
73
  if (e.key === 'Enter') {
73
74
  if (!timerRef.current && !abortControllerRef.current) {
@@ -97,7 +98,7 @@ function AsyncAutocomplete(props) {
97
98
  }
98
99
  };
99
100
  }, []);
100
- return (React.createElement(MultiSelect, { ...rest, ref: inputRef, defaultValue: defaultItems.map(toKey), searchable: true, onKeyDown: handleKeyDown, onSearchChange: handleSearchChange, data: options, onFocus: handleTimer, onChange: handleChange, onCreate: handleCreate, rightSectionWidth: 40, rightSection: abortController ? React.createElement(Loader, { size: 16 }) : null, filter: handleFilter }));
101
+ return (React.createElement(MultiSelect, { ...rest, ref: inputRef, defaultValue: defaultItems.map(toKey), searchable: true, onKeyDown: handleKeyDown, onSearchChange: handleSearchChange, data: options, onFocus: handleTimer, onChange: handleChange, onCreate: handleCreate, rightSectionWidth: 40, rightSection: abortController ? React.createElement(Loader, { size: 16 }) : null, filter: handleFilter, creatable: true }));
101
102
  }
102
103
  function toDefaultItems(defaultValue) {
103
104
  if (!defaultValue) {