@openmrs/esm-styleguide 10.0.1-pre.5702 → 10.0.1-pre.5707

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 (53) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/config-schema.js +2 -2
  3. package/dist/diagnosis-tags/diagnosis-tags.component.d.ts +10 -9
  4. package/dist/diagnosis-tags/diagnosis-tags.component.d.ts.map +1 -1
  5. package/dist/diagnosis-tags/diagnosis-tags.component.js +96 -17
  6. package/dist/diagnosis-tags/diagnosis-tags.module.scss +25 -1
  7. package/dist/diagnosis-tags/diagnosis-tags.stories.js +24 -0
  8. package/dist/public.d.ts +1 -1
  9. package/dist/public.d.ts.map +1 -1
  10. package/dist/public.js +1 -1
  11. package/dist/workspaces2/active-workspace-window.component.d.ts +11 -1
  12. package/dist/workspaces2/active-workspace-window.component.d.ts.map +1 -1
  13. package/dist/workspaces2/active-workspace-window.component.js +182 -71
  14. package/dist/workspaces2/exported-workspace.component.d.ts +42 -0
  15. package/dist/workspaces2/exported-workspace.component.d.ts.map +1 -0
  16. package/dist/workspaces2/exported-workspace.component.js +110 -0
  17. package/dist/workspaces2/exported-workspace.module.scss +8 -0
  18. package/dist/workspaces2/index.d.ts +1 -0
  19. package/dist/workspaces2/index.d.ts.map +1 -1
  20. package/dist/workspaces2/index.js +1 -0
  21. package/dist/workspaces2/workspace-window-actions.d.ts +36 -0
  22. package/dist/workspaces2/workspace-window-actions.d.ts.map +1 -0
  23. package/dist/workspaces2/workspace-window-actions.js +69 -0
  24. package/dist/workspaces2/workspace-windows-and-menu.component.d.ts.map +1 -1
  25. package/dist/workspaces2/workspace-windows-and-menu.component.js +19 -2
  26. package/dist/workspaces2/workspace2.component.d.ts +10 -0
  27. package/dist/workspaces2/workspace2.component.d.ts.map +1 -1
  28. package/dist/workspaces2/workspace2.component.js +12 -140
  29. package/dist/workspaces2/workspace2.d.ts +39 -13
  30. package/dist/workspaces2/workspace2.d.ts.map +1 -1
  31. package/dist/workspaces2/workspace2.js +173 -100
  32. package/mock-jest.tsx +12 -7
  33. package/mock.tsx +12 -7
  34. package/package.json +25 -25
  35. package/src/config-schema.ts +2 -2
  36. package/src/diagnosis-tags/diagnosis-tags.component.tsx +103 -23
  37. package/src/diagnosis-tags/diagnosis-tags.module.scss +25 -1
  38. package/src/diagnosis-tags/diagnosis-tags.stories.tsx +11 -0
  39. package/src/diagnosis-tags/diagnosis-tags.test.tsx +174 -0
  40. package/src/public.ts +3 -0
  41. package/src/workspaces2/active-workspace-window.component.tsx +241 -66
  42. package/src/workspaces2/active-workspace-window.test.tsx +160 -34
  43. package/src/workspaces2/exported-workspace.component.tsx +140 -0
  44. package/src/workspaces2/exported-workspace.module.scss +8 -0
  45. package/src/workspaces2/exported-workspace.test.tsx +363 -0
  46. package/src/workspaces2/index.tsx +1 -0
  47. package/src/workspaces2/workspace-window-actions.test.ts +93 -0
  48. package/src/workspaces2/workspace-window-actions.ts +105 -0
  49. package/src/workspaces2/workspace-windows-and-menu.component.tsx +29 -2
  50. package/src/workspaces2/workspace2.component.test.tsx +72 -0
  51. package/src/workspaces2/workspace2.component.tsx +24 -150
  52. package/src/workspaces2/workspace2.test.ts +171 -106
  53. package/src/workspaces2/workspace2.ts +215 -109
@@ -1,3 +1,3 @@
1
- [0] Successfully compiled: 115 files, copied 169 files with swc (258.34ms)
1
+ [0] Successfully compiled: 117 files, copied 170 files with swc (252.19ms)
2
2
  [0] swc --strip-leading-paths --copy-files src -d dist && svgo -rf dist --quiet exited with code 0
3
3
  [1] tsc --project tsconfig.build.json exited with code 0
@@ -4,7 +4,7 @@ const diagnosisTagConfigSchema = {
4
4
  primaryColor: {
5
5
  _type: Type.String,
6
6
  _description: 'The color for displaying primary diagnoses tags',
7
- _default: 'red',
7
+ _default: 'blue',
8
8
  _validators: [
9
9
  validators.oneOf(carbonTagColors)
10
10
  ]
@@ -12,7 +12,7 @@ const diagnosisTagConfigSchema = {
12
12
  secondaryColor: {
13
13
  _type: Type.String,
14
14
  _description: 'The color for displaying secondary diagnoses tags',
15
- _default: 'blue',
15
+ _default: 'gray',
16
16
  _validators: [
17
17
  validators.oneOf(carbonTagColors)
18
18
  ]
@@ -1,13 +1,14 @@
1
1
  import React from 'react';
2
- import { type Diagnosis } from '@openmrs/esm-emr-api';
3
- interface DiagnosisTagsProps {
4
- diagnoses: Array<Diagnosis>;
2
+ import type { Diagnosis } from '@openmrs/esm-emr-api';
3
+ export interface DiagnosisTagsProps {
4
+ diagnoses: Array<Pick<Diagnosis, 'uuid' | 'display' | 'rank' | 'certainty'>>;
5
+ /**
6
+ * Mark provisional diagnoses with a leading "?". Confirmed diagnoses, and any with missing or
7
+ * unknown certainty, show just the name. Off by default because diagnoses saved before
8
+ * certainty was captured are stored as provisional.
9
+ */
10
+ showCertainty?: boolean;
5
11
  }
6
- /**
7
- * This component takes a list of diagnoses and displays them as
8
- * Carbon tags, with colors configured base on whether the diagnoses are primary
9
- * or secondary.
10
- */
12
+ /** Displays diagnoses with configured rank colours, optionally marking provisional ones. */
11
13
  export declare const DiagnosisTags: React.FC<DiagnosisTagsProps>;
12
- export {};
13
14
  //# sourceMappingURL=diagnosis-tags.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diagnosis-tags.component.d.ts","sourceRoot":"","sources":["../../src/diagnosis-tags/diagnosis-tags.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAItD,UAAU,kBAAkB;IAC1B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqBtD,CAAC"}
1
+ {"version":3,"file":"diagnosis-tags.component.d.ts","sourceRoot":"","sources":["../../src/diagnosis-tags/diagnosis-tags.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAKjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKtD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC;IAC7E;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,4FAA4F;AAC5F,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAgBtD,CAAC"}
@@ -1,28 +1,107 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Tag } from "@carbon/react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useLayoutEffect, useRef, useState } from "react";
3
+ import { Button, Tooltip, TooltipTrigger } from "react-aria-components";
4
+ import classNames from "classnames";
5
+ import { getCoreTranslation } from "@openmrs/esm-translations";
4
6
  import { useConfig } from "@openmrs/esm-react-utils";
5
7
  import styles from "./diagnosis-tags.module.scss";
6
- /**
7
- * This component takes a list of diagnoses and displays them as
8
- * Carbon tags, with colors configured base on whether the diagnoses are primary
9
- * or secondary.
10
- */ export const DiagnosisTags = ({ diagnoses })=>{
8
+ /** Displays diagnoses with configured rank colours, optionally marking provisional ones. */ export const DiagnosisTags = ({ diagnoses, showCertainty = false })=>{
11
9
  const { diagnosisTags } = useConfig({
12
10
  externalModuleName: '@openmrs/esm-styleguide'
13
11
  });
14
12
  return /*#__PURE__*/ _jsx("div", {
15
13
  className: styles.container,
16
14
  children: diagnoses.map((diagnosis)=>{
17
- const { rank, uuid, display } = diagnosis;
18
- // technically rank can be any integer, but we only use 1 for primary
19
- // and 2 for secondary
20
- const key = rank === 1 ? 'primaryColor' : 'secondaryColor';
21
- const color = diagnosisTags[key];
22
- return /*#__PURE__*/ _jsx(Tag, {
23
- type: color,
24
- children: display
25
- }, uuid);
15
+ const isProvisional = showCertainty && diagnosis.certainty === 'PROVISIONAL';
16
+ const color = diagnosis.rank === 1 ? diagnosisTags?.primaryColor ?? 'blue' : diagnosisTags?.secondaryColor ?? 'gray';
17
+ return /*#__PURE__*/ _jsx(DiagnosisPill, {
18
+ color: color,
19
+ isProvisional: isProvisional,
20
+ name: diagnosis.display
21
+ }, diagnosis.uuid);
26
22
  })
27
23
  });
28
24
  };
25
+ function DiagnosisPill({ color, isProvisional, name }) {
26
+ const [isOpen, setIsOpen] = useState(false);
27
+ const [isFocused, setIsFocused] = useState(false);
28
+ const [isTruncated, setIsTruncated] = useState(false);
29
+ const isInteractive = isTruncated || isFocused;
30
+ const labelRef = useRef(null);
31
+ useLayoutEffect(()=>{
32
+ const label = labelRef.current;
33
+ if (!label) {
34
+ return;
35
+ }
36
+ const measure = ()=>{
37
+ const truncated = label.scrollWidth > label.clientWidth;
38
+ setIsTruncated(truncated);
39
+ if (!truncated) {
40
+ setIsOpen(false);
41
+ }
42
+ };
43
+ measure();
44
+ const observer = new ResizeObserver(measure);
45
+ observer.observe(label);
46
+ return ()=>observer.disconnect();
47
+ }, [
48
+ name,
49
+ isProvisional,
50
+ isInteractive
51
+ ]);
52
+ const className = classNames('cds--tag', 'cds--tag--md', 'cds--layout--size-md', `cds--tag--${color}`, styles.diagnosisTag);
53
+ const content = /*#__PURE__*/ _jsxs(_Fragment, {
54
+ children: [
55
+ isProvisional && /*#__PURE__*/ _jsxs(_Fragment, {
56
+ children: [
57
+ /*#__PURE__*/ _jsx("span", {
58
+ "aria-hidden": "true",
59
+ className: styles.provisionalMark,
60
+ children: "?"
61
+ }),
62
+ /*#__PURE__*/ _jsxs("span", {
63
+ className: "cds--visually-hidden",
64
+ children: [
65
+ getCoreTranslation('provisional', 'Provisional'),
66
+ " "
67
+ ]
68
+ })
69
+ ]
70
+ }),
71
+ /*#__PURE__*/ _jsx("span", {
72
+ ref: labelRef,
73
+ className: "cds--tag__label",
74
+ children: name
75
+ })
76
+ ]
77
+ });
78
+ if (!isInteractive) {
79
+ return /*#__PURE__*/ _jsx("span", {
80
+ className: className,
81
+ "data-testid": "diagnosis-tag",
82
+ children: content
83
+ });
84
+ }
85
+ return /*#__PURE__*/ _jsxs(TooltipTrigger, {
86
+ delay: 0,
87
+ closeDelay: 150,
88
+ isOpen: isOpen,
89
+ onOpenChange: setIsOpen,
90
+ children: [
91
+ /*#__PURE__*/ _jsx(Button, {
92
+ className: className,
93
+ "data-testid": "diagnosis-tag",
94
+ onFocusChange: setIsFocused,
95
+ onPress: ()=>setIsOpen(true),
96
+ children: content
97
+ }),
98
+ /*#__PURE__*/ _jsx(Tooltip, {
99
+ className: styles.tagTooltip,
100
+ placement: "bottom",
101
+ offset: 4,
102
+ containerPadding: 8,
103
+ children: name
104
+ })
105
+ ]
106
+ });
107
+ }
@@ -1,8 +1,32 @@
1
+ @use '@carbon/colors';
1
2
  @use '@carbon/layout';
3
+ @use '@carbon/type';
2
4
 
3
5
  .container {
4
6
  display: flex;
5
- flex-flow: row wrap;
7
+ flex-wrap: wrap;
6
8
  align-items: center;
7
9
  gap: layout.$spacing-02;
8
10
  }
11
+
12
+ .diagnosisTag:focus-visible {
13
+ outline: 2px solid colors.$blue-60;
14
+ outline-offset: 1px;
15
+ }
16
+
17
+ .tagTooltip {
18
+ @include type.type-style('body-compact-01');
19
+ max-width: min(18rem, calc(100vw - 1rem));
20
+ overflow-wrap: anywhere;
21
+ padding: layout.$spacing-01 layout.$spacing-03;
22
+ border-radius: 2px;
23
+ background-color: colors.$gray-80;
24
+ color: colors.$white-0;
25
+ }
26
+
27
+ // The name truncates; the "?" marking a provisional diagnosis must stay fully visible inside the tag
28
+ .provisionalMark {
29
+ margin-inline-end: layout.$spacing-01;
30
+ font-weight: 600;
31
+ flex-shrink: 0;
32
+ }
@@ -71,3 +71,27 @@ export const MultipleDiagnoses = {
71
71
  ]
72
72
  }
73
73
  };
74
+ export const WithCertainty = {
75
+ args: {
76
+ showCertainty: true,
77
+ diagnoses: [
78
+ {
79
+ uuid: '1',
80
+ display: 'Extrinsic Asthma with Asthma Attack',
81
+ rank: 1,
82
+ certainty: 'CONFIRMED'
83
+ },
84
+ {
85
+ uuid: '2',
86
+ display: 'Malaria due to Plasmodium falciparum',
87
+ rank: 2,
88
+ certainty: 'PROVISIONAL'
89
+ },
90
+ {
91
+ uuid: '3',
92
+ display: 'Anemia',
93
+ rank: 2
94
+ }
95
+ ]
96
+ }
97
+ };
package/dist/public.d.ts CHANGED
@@ -23,5 +23,5 @@ export * from './pictograms/pictograms';
23
23
  export * from './responsive-wrapper';
24
24
  export { showSnackbar, type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbars';
25
25
  export { showToast, type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toasts';
26
- export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, type Workspace2Definition, type Workspace2DefinitionProps, } from './workspaces2';
26
+ export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, type Workspace2Definition, type Workspace2DefinitionProps, ExportedWorkspace, type ExportedWorkspaceProps, type ExportedWorkspaceWindowInfo, } from './workspaces2';
27
27
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,cAAc,SAAS,CAAC;AACxB,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACL,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,GAChC,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAClH,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1G,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACvG,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,GAC/B,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,cAAc,SAAS,CAAC;AACxB,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACL,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,GAChC,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAClH,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1G,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACvG,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,GACjC,MAAM,eAAe,CAAC"}
package/dist/public.js CHANGED
@@ -20,4 +20,4 @@ export * from "./pictograms/pictograms.js";
20
20
  export * from "./responsive-wrapper/index.js";
21
21
  export { showSnackbar } from "./snackbars/index.js";
22
22
  export { showToast } from "./toasts/index.js";
23
- export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2 } from "./workspaces2/index.js";
23
+ export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, ExportedWorkspace } from "./workspaces2/index.js";
@@ -1,11 +1,21 @@
1
1
  import React from 'react';
2
2
  import { type OpenedWindow } from '@openmrs/esm-extensions';
3
+ import { type WorkspaceWindowActions } from './workspace-window-actions';
3
4
  interface WorkspaceWindowProps {
4
5
  openedWindow: OpenedWindow;
6
+ groupProps: Record<string, any> | null;
7
+ actions: WorkspaceWindowActions;
8
+ /**
9
+ * Whether to render the workspace window "chrome" (containers, title bar, maximize / hide / close).
10
+ * Only the global workspace window system renders it; an `<ExportedWorkspace>` renders the bare
11
+ * workspace content.
12
+ */
13
+ renderChrome: boolean;
5
14
  showActionMenu: boolean;
6
15
  }
7
16
  /**
8
- * Renders an opened workspace window.
17
+ * Renders an opened workspace window. `actions` determines which store the window's workspaces
18
+ * navigate within: the global workspace window system, or an `<ExportedWorkspace>`'s local store.
9
19
  */
10
20
  declare const ActiveWorkspaceWindow: React.FC<WorkspaceWindowProps>;
11
21
  export default ActiveWorkspaceWindow;
@@ -1 +1 @@
1
- {"version":3,"file":"active-workspace-window.component.d.ts","sourceRoot":"","sources":["../../src/workspaces2/active-workspace-window.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,OAAO,EAAE,KAAK,YAAY,EAA8D,MAAM,yBAAyB,CAAC;AAOxH,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;CACzB;AACD;;GAEG;AACH,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4CzD,CAAC;AA4IF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"active-workspace-window.component.d.ts","sourceRoot":"","sources":["../../src/workspaces2/active-workspace-window.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAM5E,OAAO,EACL,KAAK,YAAY,EAKlB,MAAM,yBAAyB,CAAC;AAOjC,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGzE,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACvC,OAAO,EAAE,sBAAsB,CAAC;IAChC;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB;AAKD;;;GAGG;AACH,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4DzD,CAAC;AAoRF,eAAe,qBAAqB,CAAC"}
@@ -2,15 +2,21 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React, { useEffect, useMemo, useState } from "react";
3
3
  import classNames from "classnames";
4
4
  import Parcel from "single-spa-react/parcel";
5
- import { InlineLoading } from "@carbon/react";
5
+ import { Header, HeaderGlobalAction, HeaderGlobalBar, HeaderName, InlineLoading } from "@carbon/react";
6
+ import { DownToBottom, Maximize, Minimize } from "@carbon/react/icons";
6
7
  import { createParcelMounter, workspace2Store } from "@openmrs/esm-extensions";
8
+ import { isDesktop, useLayoutType, useStore } from "@openmrs/esm-react-utils";
7
9
  import { loadLifeCycles } from "@openmrs/esm-routes";
8
10
  import { getCoreTranslation } from "@openmrs/esm-translations";
9
- import { promptForClosingWorkspaces, useWorkspace2Store } from "./workspace2.js";
11
+ import { ArrowRightIcon, CloseIcon } from "../icons/index.js";
12
+ import { closeWorkspaceGroup2, useWorkspace2Store } from "./workspace2.js";
10
13
  import styles from "./workspace2.module.scss";
14
+ // module-level so the store subscription's snapshot getter stays stable across renders
15
+ const selectRegisteredWorkspacesByName = (state)=>state.registeredWorkspacesByName;
11
16
  /**
12
- * Renders an opened workspace window.
13
- */ const ActiveWorkspaceWindow = ({ openedWindow, showActionMenu })=>{
17
+ * Renders an opened workspace window. `actions` determines which store the window's workspaces
18
+ * navigate within: the global workspace window system, or an `<ExportedWorkspace>`'s local store.
19
+ */ const ActiveWorkspaceWindow = ({ openedWindow, groupProps, actions, renderChrome, showActionMenu })=>{
14
20
  const { openedWorkspaces } = openedWindow;
15
21
  // Keyed by opened workspace uuid rather than stack index. The lifecycles load
16
22
  // asynchronously, so when the stack changes (a workspace at some position is
@@ -20,30 +26,39 @@ import styles from "./workspace2.module.scss";
20
26
  // mounts whatever config it first receives, so the wrong component would then
21
27
  // render with the new workspace's props.
22
28
  const [lifeCyclesByUuid, setLifeCyclesByUuid] = useState({});
23
- const { registeredWorkspacesByName } = workspace2Store.getState();
29
+ // Reactive so the effect retries when a workspace registers after mount.
30
+ const registeredWorkspacesByName = useStore(workspace2Store, selectRegisteredWorkspacesByName);
24
31
  useEffect(()=>{
25
32
  let cancelled = false;
26
33
  Promise.all(openedWorkspaces.map(async (openedWorkspace)=>{
27
- const { moduleName, component } = registeredWorkspacesByName[openedWorkspace.workspaceName];
34
+ const workspaceDef = registeredWorkspacesByName[openedWorkspace.workspaceName];
35
+ if (!workspaceDef) {
36
+ return null;
37
+ }
38
+ const { moduleName, component } = workspaceDef;
28
39
  return [
29
40
  openedWorkspace.uuid,
30
41
  await loadLifeCycles(moduleName, component)
31
42
  ];
32
43
  })).then((entries)=>{
33
44
  if (!cancelled) {
34
- setLifeCyclesByUuid(Object.fromEntries(entries));
45
+ setLifeCyclesByUuid(Object.fromEntries(entries.filter((entry)=>entry != null)));
35
46
  }
36
47
  });
37
48
  return ()=>{
38
49
  cancelled = true;
39
50
  };
40
51
  }, [
41
- openedWorkspaces
52
+ openedWorkspaces,
53
+ registeredWorkspacesByName
42
54
  ]);
43
55
  return /*#__PURE__*/ _jsx(_Fragment, {
44
56
  children: openedWorkspaces.map((openedWorkspace, i)=>/*#__PURE__*/ _jsx(ActiveWorkspace, {
45
57
  openedWorkspace: openedWorkspace,
46
58
  openedWindow: openedWindow,
59
+ groupProps: groupProps,
60
+ actions: actions,
61
+ renderChrome: renderChrome,
47
62
  lifeCycle: lifeCyclesByUuid[openedWorkspace.uuid],
48
63
  isRootWorkspace: i === 0,
49
64
  isLeafWorkspace: i === openedWorkspaces.length - 1,
@@ -52,31 +67,21 @@ import styles from "./workspace2.module.scss";
52
67
  });
53
68
  };
54
69
  const mountParcel = createParcelMounter();
55
- const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, isRootWorkspace, isLeafWorkspace, showActionMenu })=>{
56
- const { openedGroup, closeWorkspace, openChildWorkspace } = useWorkspace2Store();
70
+ const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, groupProps, actions, renderChrome, isRootWorkspace, isLeafWorkspace, showActionMenu })=>{
57
71
  const props = useMemo(()=>openedWorkspace && {
58
72
  closeWorkspace: async (options = {})=>{
59
73
  const { closeWindow = false, discardUnsavedChanges = false } = options;
60
74
  if (closeWindow) {
61
- const okToCloseWorkspaces = discardUnsavedChanges || await promptForClosingWorkspaces({
62
- reason: 'CLOSE_WINDOW',
63
- explicit: true,
64
- windowName: openedWindow.windowName
65
- });
75
+ const okToCloseWorkspaces = discardUnsavedChanges || await actions.promptForClosingWorkspaces();
66
76
  if (okToCloseWorkspaces) {
67
- closeWorkspace(openedWindow.openedWorkspaces[0].workspaceName);
77
+ actions.closeWorkspace(openedWindow.openedWorkspaces[0].workspaceName);
68
78
  return true;
69
79
  }
70
80
  return false;
71
81
  } else {
72
- const okToCloseWorkspaces = discardUnsavedChanges || await promptForClosingWorkspaces({
73
- reason: 'CLOSE_WORKSPACE',
74
- explicit: true,
75
- windowName: openedWindow.windowName,
76
- workspaceName: openedWorkspace.workspaceName
77
- });
82
+ const okToCloseWorkspaces = discardUnsavedChanges || await actions.promptForClosingWorkspaces(openedWorkspace.workspaceName);
78
83
  if (okToCloseWorkspaces) {
79
- closeWorkspace(openedWorkspace.workspaceName);
84
+ actions.closeWorkspace(openedWorkspace.workspaceName);
80
85
  return true;
81
86
  }
82
87
  return false;
@@ -95,23 +100,20 @@ const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, isRootWorks
95
100
  // Prompt if any of them have unsaved changes.
96
101
  const workspacesAboveParent = openedWorkspaces.slice(parentIndex + 1);
97
102
  if (workspacesAboveParent.some((w)=>w.hasUnsavedChanges)) {
98
- const okToClose = await promptForClosingWorkspaces({
99
- reason: 'CLOSE_WORKSPACE',
100
- explicit: true,
101
- windowName: openedWindow.windowName,
102
- workspaceName: openedWorkspaces[parentIndex + 1].workspaceName
103
- });
103
+ const okToClose = await actions.promptForClosingWorkspaces(openedWorkspaces[parentIndex + 1].workspaceName);
104
104
  if (!okToClose) {
105
105
  return;
106
106
  }
107
107
  }
108
108
  }
109
- openChildWorkspace(parentWorkspaceName, childWorkspaceName, childWorkspaceProps || {});
109
+ actions.openChildWorkspace(parentWorkspaceName, childWorkspaceName, childWorkspaceProps || {});
110
110
  },
111
+ setWorkspaceTitle: (title)=>actions.setWorkspaceTitle(openedWorkspace.uuid, title),
112
+ setHasUnsavedChanges: (hasUnsavedChanges)=>actions.setHasUnsavedChanges(openedWorkspace.uuid, hasUnsavedChanges),
111
113
  workspaceName: openedWorkspace.workspaceName,
112
114
  workspaceProps: openedWorkspace.props,
113
115
  windowProps: openedWindow.props,
114
- groupProps: openedGroup && openedGroup.props ? openedGroup.props : null,
116
+ groupProps,
115
117
  workspaceMeta: workspace2Store.getState().registeredWorkspacesByName[openedWorkspace.workspaceName]?.meta ?? {},
116
118
  isRootWorkspace,
117
119
  isLeafWorkspace,
@@ -119,51 +121,160 @@ const ActiveWorkspace = ({ lifeCycle, openedWorkspace, openedWindow, isRootWorks
119
121
  showActionMenu
120
122
  }, [
121
123
  openedWorkspace,
122
- closeWorkspace,
123
- openedGroup,
124
- openedWindow
124
+ openedWindow,
125
+ groupProps,
126
+ actions,
127
+ isRootWorkspace,
128
+ isLeafWorkspace,
129
+ showActionMenu
130
+ ]);
131
+ // In bare mode (<ExportedWorkspace>) there is no chrome to stack workspaces, so every non-leaf
132
+ // workspace is hidden (not unmounted, so it keeps its state) and the leaf covers them. In chrome
133
+ // mode the absolute-positioned WorkspaceChrome containers handle stacking, so this class is not
134
+ // applied there.
135
+ const hideBehindLeaf = !renderChrome && !isLeafWorkspace;
136
+ const content = useMemo(()=>lifeCycle ? /*#__PURE__*/ _jsx(Parcel, {
137
+ config: lifeCycle,
138
+ mountParcel: mountParcel,
139
+ wrapWith: "div",
140
+ wrapClassName: classNames(styles.workspaceContent, {
141
+ [styles.hiddenExtraWorkspace]: hideBehindLeaf
142
+ }),
143
+ ...props
144
+ }, openedWorkspace.workspaceName) : null, [
145
+ lifeCycle,
146
+ openedWorkspace.workspaceName,
147
+ props,
148
+ hideBehindLeaf
125
149
  ]);
126
- if (!lifeCycle) {
127
- const { registeredWorkspacesByName } = workspace2Store.getState();
128
- const workspaceDef = registeredWorkspacesByName[openedWorkspace.workspaceName];
129
- const windowName = workspaceDef && workspaceDef.window ? workspaceDef.window : undefined;
130
- const { registeredWindowsByName } = workspace2Store.getState();
131
- const windowDef = windowName ? registeredWindowsByName[windowName] : undefined;
132
- const width = windowDef && windowDef.width ? windowDef.width : 'narrow';
133
- const isActionMenuOpened = Object.values(registeredWindowsByName).some((window)=>window.group === openedGroup?.groupName && window.icon !== undefined);
134
- return /*#__PURE__*/ _jsxs("div", {
135
- className: classNames(styles.workspaceOuterContainer, {
136
- [styles.narrowWorkspace]: width === 'narrow',
137
- [styles.widerWorkspace]: width === 'wider',
138
- [styles.extraWideWorkspace]: width === 'extra-wide',
139
- [styles.isActionMenuOpened]: isActionMenuOpened
150
+ if (!renderChrome) {
151
+ return content ?? /*#__PURE__*/ _jsx(InlineLoading, {
152
+ className: classNames(styles.loader, {
153
+ [styles.hiddenExtraWorkspace]: hideBehindLeaf
154
+ }),
155
+ description: `${getCoreTranslation('loading')}`
156
+ });
157
+ }
158
+ return /*#__PURE__*/ _jsx(WorkspaceChrome, {
159
+ openedWorkspace: openedWorkspace,
160
+ openedWindow: openedWindow,
161
+ isRootWorkspace: isRootWorkspace,
162
+ showActionMenu: showActionMenu,
163
+ closeWindow: ()=>props.closeWorkspace({
164
+ closeWindow: true
165
+ }),
166
+ children: content
167
+ });
168
+ };
169
+ /**
170
+ * Renders the containers, title bar and window buttons around a workspace in the global workspace
171
+ * window system.
172
+ */ const WorkspaceChrome = ({ openedWorkspace, openedWindow, isRootWorkspace, showActionMenu, closeWindow, children })=>{
173
+ const layout = useLayoutType();
174
+ const { openedGroup, openedWindows, registeredWindowsByName, registeredGroupsByName, isMostRecentlyOpenedWindowHidden, setWindowMaximized, hideWindow } = useWorkspace2Store();
175
+ const windowName = openedWindow.windowName;
176
+ const { maximized } = openedWindow;
177
+ const title = openedWorkspace.title ?? '';
178
+ const windowDef = registeredWindowsByName[windowName];
179
+ const width = windowDef?.width ?? 'narrow';
180
+ const icon = windowDef?.icon;
181
+ const canMaximize = windowDef?.canMaximize;
182
+ const canCloseGroup = openedGroup ? registeredGroupsByName[openedGroup.groupName]?.persistence === 'closable' : false;
183
+ const canHide = !!icon && !canCloseGroup;
184
+ const openedWindowIndex = openedWindows.findIndex((w)=>w.windowName === windowName);
185
+ const isWindowHidden = openedWindowIndex < openedWindows.length - 1 || isMostRecentlyOpenedWindowHidden;
186
+ const isActionMenuOpened = Object.values(registeredWindowsByName).some((window)=>window.group === openedGroup?.groupName && window.icon !== undefined);
187
+ return /*#__PURE__*/ _jsxs("div", {
188
+ className: classNames(styles.workspaceOuterContainer, {
189
+ [styles.narrowWorkspace]: width === 'narrow',
190
+ [styles.widerWorkspace]: width === 'wider',
191
+ [styles.extraWideWorkspace]: width === 'extra-wide',
192
+ [styles.isActionMenuOpened]: isActionMenuOpened
193
+ }),
194
+ children: [
195
+ /*#__PURE__*/ _jsx("div", {
196
+ className: classNames(styles.workspaceSpacer, {
197
+ [styles.hidden]: isWindowHidden
198
+ })
140
199
  }),
141
- children: [
142
- /*#__PURE__*/ _jsx("div", {
143
- className: styles.workspaceSpacer
200
+ /*#__PURE__*/ _jsx("div", {
201
+ className: classNames(styles.workspaceMiddleContainer, {
202
+ [styles.maximized]: maximized,
203
+ [styles.hidden]: isWindowHidden,
204
+ [styles.isRootWorkspace]: isRootWorkspace,
205
+ [styles.showActionMenu]: showActionMenu
144
206
  }),
145
- /*#__PURE__*/ _jsx("div", {
146
- className: classNames(styles.workspaceMiddleContainer, {
147
- [styles.isRootWorkspace]: isRootWorkspace,
148
- [styles.showActionMenu]: showActionMenu
207
+ children: /*#__PURE__*/ _jsx("div", {
208
+ className: classNames(styles.workspaceInnerContainer, {
209
+ [styles.maximized]: maximized,
210
+ [styles.hidden]: isWindowHidden,
211
+ [styles.isRootWorkspace]: isRootWorkspace
149
212
  }),
150
- children: /*#__PURE__*/ _jsx("div", {
151
- className: classNames(styles.workspaceInnerContainer, {
152
- [styles.isRootWorkspace]: isRootWorkspace
153
- }),
154
- children: /*#__PURE__*/ _jsx(InlineLoading, {
155
- className: styles.loader,
156
- description: `${getCoreTranslation('loading')} ...`
157
- })
213
+ children: children ? /*#__PURE__*/ _jsxs(_Fragment, {
214
+ children: [
215
+ /*#__PURE__*/ _jsxs(Header, {
216
+ "aria-label": getCoreTranslation('workspaceHeader'),
217
+ className: styles.header,
218
+ children: [
219
+ /*#__PURE__*/ _jsx(HeaderName, {
220
+ prefix: "",
221
+ children: title
222
+ }),
223
+ /*#__PURE__*/ _jsx("div", {
224
+ className: styles.overlayHeaderSpacer
225
+ }),
226
+ /*#__PURE__*/ _jsx(HeaderGlobalBar, {
227
+ className: styles.headerButtons,
228
+ children: isDesktop(layout) ? /*#__PURE__*/ _jsxs(_Fragment, {
229
+ children: [
230
+ (canMaximize || maximized) && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
231
+ "aria-label": maximized ? getCoreTranslation('minimize') : getCoreTranslation('maximize'),
232
+ onClick: ()=>setWindowMaximized(windowName, !maximized),
233
+ children: maximized ? /*#__PURE__*/ _jsx(Minimize, {}) : /*#__PURE__*/ _jsx(Maximize, {})
234
+ }),
235
+ canHide && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
236
+ "aria-label": getCoreTranslation('hide'),
237
+ onClick: ()=>hideWindow(),
238
+ children: /*#__PURE__*/ _jsx(ArrowRightIcon, {})
239
+ }),
240
+ !canCloseGroup && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
241
+ "aria-label": getCoreTranslation('close'),
242
+ onClick: closeWindow,
243
+ children: /*#__PURE__*/ _jsx(CloseIcon, {})
244
+ })
245
+ ]
246
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
247
+ children: [
248
+ canHide && /*#__PURE__*/ _jsx(HeaderGlobalAction, {
249
+ "aria-label": getCoreTranslation('hide'),
250
+ onClick: ()=>hideWindow(),
251
+ children: /*#__PURE__*/ _jsx(DownToBottom, {})
252
+ }),
253
+ /*#__PURE__*/ _jsx(HeaderGlobalAction, {
254
+ "aria-label": getCoreTranslation('close'),
255
+ onClick: ()=>{
256
+ if (canCloseGroup) {
257
+ void closeWorkspaceGroup2();
258
+ } else {
259
+ closeWindow();
260
+ }
261
+ },
262
+ children: /*#__PURE__*/ _jsx(CloseIcon, {})
263
+ })
264
+ ]
265
+ })
266
+ })
267
+ ]
268
+ }),
269
+ children
270
+ ]
271
+ }) : /*#__PURE__*/ _jsx(InlineLoading, {
272
+ className: styles.loader,
273
+ description: `${getCoreTranslation('loading')} ...`
158
274
  })
159
275
  })
160
- ]
161
- });
162
- }
163
- return /*#__PURE__*/ _jsx(Parcel, {
164
- config: lifeCycle,
165
- mountParcel: mountParcel,
166
- ...props
167
- }, openedWorkspace.workspaceName);
276
+ })
277
+ ]
278
+ });
168
279
  };
169
280
  export default ActiveWorkspaceWindow;