@openmrs/esm-implementer-tools-app 3.2.0 → 3.2.1-pre.1015

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 (71) hide show
  1. package/dist/217.js +1 -1
  2. package/dist/217.js.map +1 -1
  3. package/dist/237.js +1 -1
  4. package/dist/237.js.map +1 -1
  5. package/dist/25.js +1 -1
  6. package/dist/25.js.map +1 -1
  7. package/dist/307.js +1 -1
  8. package/dist/307.js.map +1 -1
  9. package/dist/389.js +2 -0
  10. package/dist/389.js.map +1 -0
  11. package/dist/574.js +1 -1
  12. package/dist/597.js +3 -0
  13. package/dist/{682.js.LICENSE.txt → 597.js.LICENSE.txt} +0 -0
  14. package/dist/{682.js.map → 597.js.map} +1 -1
  15. package/dist/664.js +2 -0
  16. package/dist/664.js.map +1 -0
  17. package/dist/743.js +2 -0
  18. package/dist/743.js.map +1 -0
  19. package/dist/788.js +1 -1
  20. package/dist/788.js.map +1 -1
  21. package/dist/839.js +1 -1
  22. package/dist/839.js.map +1 -1
  23. package/dist/865.js +2 -0
  24. package/dist/{185.js.map → 865.js.map} +1 -1
  25. package/dist/openmrs-esm-implementer-tools-app.js +1 -2
  26. package/dist/openmrs-esm-implementer-tools-app.js.buildmanifest.json +101 -101
  27. package/dist/openmrs-esm-implementer-tools-app.js.map +1 -1
  28. package/dist/openmrs-esm-implementer-tools-app.old +2 -0
  29. package/jest.config.js +1 -1
  30. package/package.json +4 -3
  31. package/src/backend-dependencies/backend-dependencies.component.tsx +1 -1
  32. package/src/configuration/config-tree-for-module.component.tsx +2 -2
  33. package/src/configuration/config-tree.component.tsx +22 -25
  34. package/src/configuration/configuration.component.tsx +20 -21
  35. package/src/configuration/configuration.styles.scss +70 -0
  36. package/src/configuration/configuration.test.tsx +19 -13
  37. package/src/configuration/description.component.tsx +20 -6
  38. package/src/configuration/description.styles.scss +23 -0
  39. package/src/configuration/display-value.tsx +21 -9
  40. package/src/configuration/editable-value.component.tsx +19 -15
  41. package/src/configuration/{editable-value.styles.css → editable-value.styles.scss} +11 -0
  42. package/src/configuration/extension-slots-config-tree.tsx +5 -19
  43. package/src/configuration/value-editor.tsx +9 -13
  44. package/src/configuration/value-editors/array-editor.tsx +1 -2
  45. package/src/configuration/value-editors/{concept-search.styles.css → concept-search.styles.scss} +10 -0
  46. package/src/configuration/value-editors/concept-search.tsx +53 -31
  47. package/src/configuration/value-editors/extension-slot-add.tsx +9 -4
  48. package/src/configuration/value-editors/extension-slot-remove.tsx +2 -2
  49. package/src/configuration/value-editors/person-attribute-search.resource.tsx +18 -0
  50. package/src/configuration/value-editors/person-attribute-search.tsx +123 -0
  51. package/src/configuration/value-editors/uuid-search.scss +23 -0
  52. package/src/configuration/value-editors/value-editor-field.tsx +12 -1
  53. package/src/declarations.d.tsx +2 -4
  54. package/src/implementer-tools.button.tsx +3 -7
  55. package/src/implementer-tools.component.tsx +3 -2
  56. package/src/implementer-tools.styles.scss +19 -0
  57. package/src/popup/popup.component.tsx +4 -9
  58. package/src/popup/{popup.styles.css → popup.styles.scss} +10 -17
  59. package/src/ui-editor/ui-editor.tsx +12 -14
  60. package/translations/en.json +7 -1
  61. package/dist/143.js +0 -2
  62. package/dist/143.js.map +0 -1
  63. package/dist/185.js +0 -2
  64. package/dist/682.js +0 -3
  65. package/dist/684.js +0 -2
  66. package/dist/684.js.map +0 -1
  67. package/dist/728.js +0 -2
  68. package/dist/728.js.map +0 -1
  69. package/src/configuration/configuration.styles.css +0 -70
  70. package/src/configuration/description.styles.css +0 -19
  71. package/src/implementer-tools.styles.css +0 -10
@@ -1,28 +1,42 @@
1
1
  import React from "react";
2
- import styles from "./description.styles.css";
2
+ import styles from "./description.styles.scss";
3
3
  import { useStore } from "@openmrs/esm-framework";
4
4
  import { implementerToolsStore } from "../store";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  export function Description() {
8
+ const { t } = useTranslation();
7
9
  const { activeItemDescription } = useStore(implementerToolsStore);
8
10
  return (
9
11
  <div>
10
12
  {activeItemDescription ? (
11
13
  <>
12
- <p className={styles.path}>
14
+ <h2 className={styles.path}>
13
15
  {activeItemDescription.path.slice(1).join(" → ")}
14
- </p>
16
+ </h2>
15
17
  <p className={styles.description}>
16
18
  {activeItemDescription.description}
17
19
  </p>
18
20
  <p className={styles.source}>
19
21
  {activeItemDescription.source === "default" ? (
20
- <>The current value is the default.</>
22
+ <>
23
+ {t(
24
+ "itemDescriptionSourceDefaultText",
25
+ "The current value is the default."
26
+ )}
27
+ </>
21
28
  ) : activeItemDescription.source ? (
22
- <>The current value comes from {activeItemDescription.source}.</>
29
+ <>
30
+ {t("activeItemSourceText", "The current value comes from ")}
31
+ {activeItemDescription.source}
32
+ </>
23
33
  ) : null}
24
34
  </p>
25
- {activeItemDescription.value ? <p>Value:</p> : null}
35
+ {activeItemDescription.value ? (
36
+ <h4 className={styles.productiveHeading01}>
37
+ {t("value", "Value")}
38
+ </h4>
39
+ ) : null}
26
40
  <p className={styles.value}>
27
41
  {Array.isArray(activeItemDescription.value)
28
42
  ? activeItemDescription.value.map((v, i) => (
@@ -0,0 +1,23 @@
1
+ @import "../implementer-tools.styles.scss";
2
+
3
+ .path {
4
+ @include carbon--type-style("productive-heading-02");
5
+ margin-bottom: $spacing-03;
6
+ }
7
+
8
+ .description {
9
+ @include carbon--type-style("body-short-01");
10
+ margin-bottom: $spacing-05;
11
+ }
12
+
13
+ .source {
14
+ @include carbon--type-style("body-short-01");
15
+ font-style: italic;
16
+ margin-bottom: $spacing-03;
17
+ }
18
+
19
+ .value {
20
+ font-family: monospace;
21
+ margin-top: $spacing-03;
22
+ margin-left: $spacing-03;
23
+ }
@@ -1,4 +1,6 @@
1
+ import { StructuredListCell, StructuredListRow } from "carbon-components-react";
1
2
  import React from "react";
3
+ import styles from "./configuration.styles.scss";
2
4
 
3
5
  export interface DisplayValueProps {
4
6
  value: any;
@@ -7,26 +9,36 @@ export interface DisplayValueProps {
7
9
  export function DisplayValue({ value }: DisplayValueProps) {
8
10
  // TODO: Make this show the concept name for ConceptUUID type values
9
11
  return (
10
- <>
12
+ <div>
11
13
  {Array.isArray(value)
12
14
  ? typeof value[0] === "object"
13
15
  ? value.map((v, i) => (
14
- <div key={`${v}-${i}`} style={{ marginBottom: "1em" }}>
15
- <DisplayValue value={v} />
16
- </div>
16
+ <StructuredListRow key={`${v}-${i}`}>
17
+ <StructuredListCell className={styles.smallListCell}>
18
+ {i + 1}
19
+ </StructuredListCell>
20
+ <StructuredListCell className={styles.smallListCell}>
21
+ <DisplayValue value={v} />
22
+ </StructuredListCell>
23
+ </StructuredListRow>
17
24
  ))
18
- : value.join(", ")
25
+ : `[ ${value.join(", ")} ]`
19
26
  : typeof value === "object" && value !== null
20
27
  ? Object.entries(value).map(([k, v], i) => (
21
- <div key={`${k}-${i}`}>
22
- {k}: <DisplayValue value={v} />
23
- </div>
28
+ <StructuredListRow key={`${k}-${i}`}>
29
+ <StructuredListCell className={styles.smallListCell}>
30
+ {k}
31
+ </StructuredListCell>
32
+ <StructuredListCell className={styles.smallListCell}>
33
+ <DisplayValue value={v} />
34
+ </StructuredListCell>
35
+ </StructuredListRow>
24
36
  ))
25
37
  : typeof value === "string" || typeof value === "number"
26
38
  ? value
27
39
  : value == null
28
40
  ? "none"
29
41
  : JSON.stringify(value)}
30
- </>
42
+ </div>
31
43
  );
32
44
  }
@@ -3,7 +3,8 @@ import isEqual from "lodash-es/isEqual";
3
3
  import unset from "lodash-es/unset";
4
4
  import cloneDeep from "lodash-es/cloneDeep";
5
5
  import Reset16 from "@carbon/icons-react/es/reset/16";
6
- import styles from "./editable-value.styles.css";
6
+ import Edit16 from "@carbon/icons-react/es/edit/16";
7
+ import styles from "./editable-value.styles.scss";
7
8
  import { Button } from "carbon-components-react";
8
9
  import {
9
10
  ConfigValue,
@@ -15,6 +16,7 @@ import {
15
16
  import { ValueEditor, CustomValueType } from "./value-editor";
16
17
  import { implementerToolsStore, ImplementerToolsStore } from "../store";
17
18
  import { DisplayValue } from "./display-value";
19
+ import { useTranslation } from "react-i18next";
18
20
 
19
21
  export interface EditableValueProps {
20
22
  path: Array<string>;
@@ -41,6 +43,7 @@ export default function EditableValue({
41
43
  const [editing, setEditing] = useState(false);
42
44
  const [error, setError] = useState<string | null>(null);
43
45
  const activeConfigRef = useRef<HTMLButtonElement>(null);
46
+ const { t } = useTranslation();
44
47
 
45
48
  const closeEditor = () => {
46
49
  setEditing(false);
@@ -114,25 +117,26 @@ export default function EditableValue({
114
117
  />
115
118
  </>
116
119
  ) : (
117
- <>
118
- <button
119
- className={`${styles.secretButton} ${
120
- element._source == "temporary config"
121
- ? styles.overriddenValue
122
- : ""
123
- }`}
120
+ <div className={styles.elementValue}>
121
+ <DisplayValue value={element._value} />
122
+ <Button
123
+ kind="ghost"
124
+ size="sm"
125
+ iconDescription={t("editValueButtonText", "Edit")}
124
126
  onClick={() => setEditing(true)}
125
127
  ref={activeConfigRef}
126
- >
127
- <DisplayValue value={element._value} />
128
- </button>
128
+ renderIcon={Edit16}
129
+ hasIconOnly
130
+ />
129
131
  {element._source == "temporary config" ? (
130
132
  <Button
131
- style={{ marginLeft: "1em" }}
132
133
  renderIcon={Reset16}
133
134
  size="sm"
134
- kind="tertiary"
135
- iconDescription="Reset to default"
135
+ kind="ghost"
136
+ iconDescription={t(
137
+ "resetToDefaultValueButtonText",
138
+ "Reset to default"
139
+ )}
136
140
  hasIconOnly
137
141
  onClick={() => {
138
142
  temporaryConfigStore.setState(
@@ -144,7 +148,7 @@ export default function EditableValue({
144
148
  }}
145
149
  />
146
150
  ) : null}
147
- </>
151
+ </div>
148
152
  )}
149
153
  {error && <div className={styles.error}>{error}</div>}
150
154
  </div>
@@ -1,3 +1,5 @@
1
+ @import "../implementer-tools.styles.scss";
2
+
1
3
  .secretButton {
2
4
  background: none;
3
5
  color: #474b6b;
@@ -15,3 +17,12 @@
15
17
  .overriddenValue {
16
18
  color: #2020a0;
17
19
  }
20
+
21
+ .elementValue {
22
+ display: flex;
23
+ align-items: flex-start;
24
+ }
25
+
26
+ .editValueButton {
27
+ margin-left: $spacing-05;
28
+ }
@@ -1,11 +1,9 @@
1
- import React, { useEffect, useMemo, useState } from "react";
1
+ import React from "react";
2
2
  import EditableValue from "./editable-value.component";
3
3
  import isEqual from "lodash-es/isEqual";
4
4
  import {
5
- extensionStore,
6
5
  ExtensionSlotConfigureValueObject,
7
- useExtensionStore,
8
- ExtensionStore,
6
+ useAssignedExtensions,
9
7
  } from "@openmrs/esm-framework";
10
8
  import { ExtensionConfigureTree } from "./extension-configure-tree";
11
9
  import { Subtree } from "./layout/subtree.component";
@@ -43,7 +41,7 @@ export function ExtensionSlotsConfigTree({
43
41
  <ExtensionSlotConfigTree
44
42
  key={slotName}
45
43
  config={extensionsConfig?.[slotName]}
46
- path={[moduleName, "extensions", slotName]}
44
+ path={[moduleName, "extensionSlots", slotName]}
47
45
  />
48
46
  ))}
49
47
  </Subtree>
@@ -56,28 +54,16 @@ interface ExtensionSlotConfigProps {
56
54
  }
57
55
 
58
56
  function ExtensionSlotConfigTree({ config, path }: ExtensionSlotConfigProps) {
59
- const [assignedExtensions, setAssignedExtensions] = useState<Array<string>>(
60
- []
61
- );
62
57
  const moduleName = path[0];
63
58
  const slotName = path[2];
64
-
65
- useEffect(() => {
66
- function update(state) {
67
- setAssignedExtensions(
68
- state.slots[slotName]?.instances?.[moduleName]?.assignedIds
69
- );
70
- }
71
- update(extensionStore.getState());
72
- return extensionStore.subscribe(update);
73
- }, []);
59
+ const assignedExtensions = useAssignedExtensions(slotName);
74
60
 
75
61
  function setActiveExtensionSlotOnMouseEnter(moduleName, slotName) {
76
62
  if (!implementerToolsStore.getState().configPathBeingEdited) {
77
63
  implementerToolsStore.setState({
78
64
  activeItemDescription: {
79
65
  path: [moduleName, slotName],
80
- value: assignedExtensions,
66
+ value: assignedExtensions.map((e) => e.id),
81
67
  },
82
68
  });
83
69
  }
@@ -5,7 +5,8 @@ import { Button } from "carbon-components-react";
5
5
  import { Type } from "@openmrs/esm-framework";
6
6
  import { ConfigValueDescriptor } from "./editable-value.component";
7
7
  import { ValueEditorField } from "./value-editors/value-editor-field";
8
- import styles from "./configuration.styles.css";
8
+ import styles from "./configuration.styles.scss";
9
+ import { useTranslation } from "react-i18next";
9
10
 
10
11
  export type CustomValueType = "add" | "remove" | "order" | "configure";
11
12
 
@@ -28,6 +29,7 @@ export function ValueEditor({
28
29
  }: ValueEditorProps) {
29
30
  const ref = useRef<HTMLDivElement>(null);
30
31
  const [tmpValue, setTmpValue] = useState(element._value);
32
+ const { t } = useTranslation();
31
33
 
32
34
  const valueType = customType ?? element._type;
33
35
 
@@ -58,20 +60,14 @@ export function ValueEditor({
58
60
  <div className={styles.valueEditorButtons}>
59
61
  <Button
60
62
  renderIcon={Save16}
61
- size="sm"
62
63
  kind="primary"
63
- iconDescription="Save"
64
- hasIconOnly
65
64
  onClick={() => handleSave(JSON.stringify(tmpValue))}
66
- />
67
- <Button
68
- renderIcon={Close16}
69
- size="sm"
70
- kind="secondary"
71
- iconDescription="Cancel"
72
- hasIconOnly
73
- onClick={handleClose}
74
- />
65
+ >
66
+ {t("saveValueButtonText", "Save")}
67
+ </Button>
68
+ <Button renderIcon={Close16} kind="secondary" onClick={handleClose}>
69
+ {t("CancelButtonText", "Cancel")}
70
+ </Button>
75
71
  </div>
76
72
  </div>
77
73
  );
@@ -67,8 +67,7 @@ export function ArrayEditor({
67
67
  </StructuredListRow>
68
68
  ))}
69
69
  <StructuredListRow>
70
- <StructuredListCell></StructuredListCell>
71
- <StructuredListCell className={styles.buttonCell}>
70
+ <StructuredListCell>
72
71
  <Button
73
72
  renderIcon={Add16}
74
73
  size="sm"
@@ -1,3 +1,13 @@
1
+ @import "../../implementer-tools.styles.scss";
2
+
3
+ .activeConceptUuid {
4
+ @include carbon--type-style("body-short-01");
5
+ color: $ui-05;
6
+ margin-bottom: $spacing-03;
7
+ padding-bottom: $spacing-03;
8
+ border-bottom: 1px solid $ui-03;
9
+ }
10
+
1
11
  .autocomplete,
2
12
  .autocomplete > [role="combobox"] {
3
13
  display: flex;
@@ -5,17 +5,26 @@ import {
5
5
  fetchConceptByUuid,
6
6
  performConceptSearch,
7
7
  } from "./concept-search.resource";
8
- import styles from "./concept-search.styles.css";
8
+ import styles from "./uuid-search.scss";
9
+ import {
10
+ Search,
11
+ StructuredListCell,
12
+ StructuredListRow,
13
+ StructuredListWrapper,
14
+ } from "carbon-components-react";
15
+ import { useTranslation } from "react-i18next";
9
16
 
10
17
  interface ConceptSearchBoxProps {
18
+ value: string;
11
19
  setConcept: (concept) => void;
12
20
  }
13
21
 
14
- export function ConceptSearchBox({ setConcept }: ConceptSearchBoxProps) {
22
+ export function ConceptSearchBox({ setConcept, value }: ConceptSearchBoxProps) {
15
23
  const [searchTerm, setSearchTerm] = useState("");
16
24
  const [searchResults, setSearchResults] = useState([]);
17
- const [activeConceptUuid, setActiveConceptUuid] = useState<any>("");
25
+ const [activeConceptUuid, setActiveConceptUuid] = useState<any>(value);
18
26
  const searchTimeoutInMs = 300;
27
+ const { t } = useTranslation();
19
28
 
20
29
  const id = useMemo(() => uniqueId(), []);
21
30
 
@@ -48,42 +57,55 @@ export function ConceptSearchBox({ setConcept }: ConceptSearchBoxProps) {
48
57
  }, [searchTerm]);
49
58
 
50
59
  return (
51
- <div className={styles.autocomplete}>
52
- <input
53
- type="text"
54
- autoComplete="off"
55
- autoCapitalize="off"
56
- aria-autocomplete="list"
57
- role="combobox"
58
- aria-label="Look up concept by name"
59
- aria-controls={`searchbox-${id}`}
60
- aria-expanded={searchResults.length > 0}
61
- placeholder="Look up concept by name"
62
- autoFocus
63
- onChange={($event) => {
64
- handleSearchTermChange($event.target.value);
65
- }}
66
- />
67
- <div id={`searchbox-${id}`}>
68
- <ul role="listbox">
69
- {!!searchResults.length &&
70
- searchResults.map((concept: any) => (
71
- <li
60
+ <div>
61
+ {activeConceptUuid && (
62
+ <p className={styles.activeUuid}>{activeConceptUuid}</p>
63
+ )}
64
+ <div className={styles.autocomplete}>
65
+ <Search
66
+ id={`searchbox-${id}`}
67
+ labelText=""
68
+ type="text"
69
+ size="sm"
70
+ autoComplete="off"
71
+ autoCapitalize="off"
72
+ aria-autocomplete="list"
73
+ role="combobox"
74
+ aria-label={t("searchConceptHelperText", "Concept Name")}
75
+ aria-controls={`searchbox-${id}`}
76
+ aria-expanded={searchResults.length > 0}
77
+ placeholder={t("searchConceptHelperText", "Concept Name")}
78
+ onChange={($event) => {
79
+ handleSearchTermChange($event.target.value);
80
+ }}
81
+ />
82
+ {!!searchResults.length && (
83
+ <StructuredListWrapper
84
+ selection
85
+ id={`searchbox-${id}`}
86
+ className={styles.listbox}
87
+ >
88
+ {searchResults.map((concept: any) => (
89
+ <StructuredListRow
72
90
  key={concept.uuid}
73
91
  role="option"
74
- style={{ padding: "5px" }}
75
92
  onClick={() => {
76
93
  handleUuidChange(concept);
77
94
  }}
78
95
  aria-selected="true"
79
96
  >
80
- {concept.display}
81
- </li>
97
+ <StructuredListCell className={styles.smallListCell}>
98
+ {concept.display}
99
+ </StructuredListCell>
100
+ </StructuredListRow>
82
101
  ))}
83
- {searchTerm && searchResults && !searchResults.length && (
84
- <li>No matching results found.</li>
85
- )}
86
- </ul>
102
+ </StructuredListWrapper>
103
+ )}
104
+ {searchTerm && searchResults && !searchResults.length && (
105
+ <p className={styles.bodyShort01}>
106
+ {t("noConceptsFoundText", "No matching results found")}
107
+ </p>
108
+ )}
87
109
  </div>
88
110
  </div>
89
111
  );
@@ -1,6 +1,11 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import { MultiSelect } from "carbon-components-react";
3
- import { ExtensionStore, extensionStore } from "@openmrs/esm-framework";
3
+ import {
4
+ ExtensionInternalStore,
5
+ getExtensionInternalStore,
6
+ } from "@openmrs/esm-framework";
7
+
8
+ const extensionInternalStore = getExtensionInternalStore();
4
9
 
5
10
  export function ExtensionSlotAdd({ value, setValue }) {
6
11
  const [availableExtensions, setAvailableExtensions] = useState<Array<string>>(
@@ -8,11 +13,11 @@ export function ExtensionSlotAdd({ value, setValue }) {
8
13
  );
9
14
 
10
15
  useEffect(() => {
11
- function update(state: ExtensionStore) {
16
+ function update(state: ExtensionInternalStore) {
12
17
  setAvailableExtensions(Object.keys(state.extensions));
13
18
  }
14
- update(extensionStore.getState());
15
- return extensionStore.subscribe(update);
19
+ update(extensionInternalStore.getState());
20
+ return extensionInternalStore.subscribe(update);
16
21
  }, []);
17
22
 
18
23
  return (
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { MultiSelect } from "carbon-components-react";
3
- import { useAssignedExtensionIds } from "@openmrs/esm-framework";
3
+ import { useAssignedExtensions } from "@openmrs/esm-framework";
4
4
 
5
5
  export function ExtensionSlotRemove({
6
6
  slotName,
@@ -8,7 +8,7 @@ export function ExtensionSlotRemove({
8
8
  value,
9
9
  setValue,
10
10
  }) {
11
- const assignedIds = useAssignedExtensionIds(slotName);
11
+ const assignedIds = useAssignedExtensions(slotName).map((e) => e.id);
12
12
 
13
13
  return (
14
14
  <MultiSelect.Filterable
@@ -0,0 +1,18 @@
1
+ import { openmrsFetch } from "@openmrs/esm-framework";
2
+
3
+ export function fetchPersonAttributeTypeByUuid(
4
+ personAttributeTypeUuid: string
5
+ ) {
6
+ return openmrsFetch(
7
+ `/ws/rest/v1/personattributetype/${personAttributeTypeUuid}`,
8
+ {
9
+ method: "GET",
10
+ }
11
+ );
12
+ }
13
+
14
+ export function performPersonAttributeTypeSearch(query: string) {
15
+ return openmrsFetch(`/ws/rest/v1/personattributetype/?q=${query}`, {
16
+ method: "GET",
17
+ });
18
+ }
@@ -0,0 +1,123 @@
1
+ import React, { useState, useEffect, useMemo } from "react";
2
+ import debounce from "lodash-es/debounce";
3
+ import uniqueId from "lodash-es/uniqueId";
4
+ import { performPersonAttributeTypeSearch } from "./person-attribute-search.resource";
5
+ import styles from "./uuid-search.scss";
6
+ import { useTranslation } from "react-i18next";
7
+ import {
8
+ Search,
9
+ StructuredListCell,
10
+ StructuredListRow,
11
+ StructuredListWrapper,
12
+ } from "carbon-components-react";
13
+
14
+ interface PersonAttributeTypeSearchBoxProps {
15
+ value: string;
16
+ setPersonAttributeUuid: (personAttributeType) => void;
17
+ }
18
+
19
+ export function PersonAttributeTypeSearchBox({
20
+ setPersonAttributeUuid,
21
+ value,
22
+ }: PersonAttributeTypeSearchBoxProps) {
23
+ const [searchTerm, setSearchTerm] = useState("");
24
+ const [searchResults, setSearchResults] = useState([]);
25
+ const [activePersonAttributeUuid, setActivePersonAttributeUuid] =
26
+ useState<any>(value);
27
+ const { t } = useTranslation();
28
+ const searchTimeoutInMs = 300;
29
+
30
+ const id = useMemo(() => uniqueId(), []);
31
+
32
+ const handleUuidChange = (personAttributeType) => {
33
+ setActivePersonAttributeUuid(personAttributeType.uuid);
34
+ resetSearch();
35
+ setPersonAttributeUuid(personAttributeType.uuid);
36
+ };
37
+
38
+ const resetSearch = () => {
39
+ setSearchTerm("");
40
+ setSearchResults([]);
41
+ };
42
+
43
+ const handleSearchTermChange = debounce((searchTerm) => {
44
+ setSearchTerm(searchTerm);
45
+ }, searchTimeoutInMs);
46
+
47
+ useEffect(() => {
48
+ const ac = new AbortController();
49
+
50
+ if (searchTerm && searchTerm.length >= 2) {
51
+ performPersonAttributeTypeSearch(searchTerm).then(
52
+ ({ data: { results } }) => {
53
+ setSearchResults(results.slice(0, 9));
54
+ }
55
+ );
56
+ } else {
57
+ setSearchResults([]);
58
+ }
59
+ return () => ac.abort();
60
+ }, [searchTerm]);
61
+
62
+ return (
63
+ <div>
64
+ {activePersonAttributeUuid && (
65
+ <p className={styles.activeUuid}>{activePersonAttributeUuid}</p>
66
+ )}
67
+ <div className={styles.autocomplete}>
68
+ <Search
69
+ id={`search-input-${id}`}
70
+ labelText=""
71
+ type="text"
72
+ size="sm"
73
+ autoComplete="off"
74
+ autoCapitalize="off"
75
+ aria-autocomplete="list"
76
+ role="combobox"
77
+ aria-label={t(
78
+ "searchPersonAttributeHelperText",
79
+ "Person attribute type name"
80
+ )}
81
+ aria-controls={`searchbox-${id}`}
82
+ aria-expanded={searchResults.length > 0}
83
+ placeholder={t(
84
+ "searchPersonAttributeHelperText",
85
+ "Person attribute type name"
86
+ )}
87
+ onChange={($event) => {
88
+ handleSearchTermChange($event.target.value);
89
+ }}
90
+ />
91
+
92
+ {!!searchResults.length && (
93
+ <StructuredListWrapper
94
+ selection
95
+ className={styles.listbox}
96
+ id={`searchbox-${id}`}
97
+ >
98
+ {searchResults.map((personAttributeType: any) => (
99
+ <StructuredListRow
100
+ key={personAttributeType.uuid}
101
+ role="option"
102
+ onClick={() => {
103
+ handleUuidChange(personAttributeType);
104
+ }}
105
+ aria-selected="true"
106
+ >
107
+ <StructuredListCell className={styles.smallListCell}>
108
+ {personAttributeType.display}
109
+ </StructuredListCell>
110
+ </StructuredListRow>
111
+ ))}
112
+ </StructuredListWrapper>
113
+ )}
114
+
115
+ {searchTerm && searchResults && !searchResults.length && (
116
+ <p className={styles.bodyShort01}>
117
+ {t("noPersonAttributeFoundText", "No matching results found")}
118
+ </p>
119
+ )}
120
+ </div>
121
+ </div>
122
+ );
123
+ }
@@ -0,0 +1,23 @@
1
+ @import "../../implementer-tools.styles.scss";
2
+
3
+ .activeUuid {
4
+ @include carbon--type-style("body-short-01");
5
+ color: $ui-05;
6
+ margin-bottom: $spacing-03;
7
+ padding-bottom: $spacing-03;
8
+ border-bottom: 1px solid $ui-03;
9
+ }
10
+
11
+ .autocomplete,
12
+ .autocomplete > [role="combobox"] {
13
+ position: relative;
14
+ width: 20em;
15
+ }
16
+
17
+ .listbox {
18
+ box-shadow: 0 0 $spacing-03;
19
+ }
20
+
21
+ .smallListCell {
22
+ padding: $spacing-03;
23
+ }