@openmrs/esm-form-builder-app 1.0.1-pre.126

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 (60) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +33 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.husky/pre-push +6 -0
  5. package/.prettierignore +14 -0
  6. package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
  7. package/.yarn/plugins/@yarnpkg/plugin-version.cjs +550 -0
  8. package/.yarn/versions/7d315ef1.yml +0 -0
  9. package/LICENSE +401 -0
  10. package/README.md +14 -0
  11. package/__mocks__/react-i18next.js +56 -0
  12. package/dist/openmrs-esm-form-builder-app.js +1 -0
  13. package/i18next-parser.config.js +89 -0
  14. package/jest.config.json +19 -0
  15. package/package.json +102 -0
  16. package/src/components/dashboard/dashboard.component.tsx +310 -0
  17. package/src/components/dashboard/dashboard.scss +112 -0
  18. package/src/components/empty-state/empty-data-illustration.component.tsx +51 -0
  19. package/src/components/empty-state/empty-state.component.tsx +41 -0
  20. package/src/components/empty-state/empty-state.scss +55 -0
  21. package/src/components/error-state/error-state.component.tsx +37 -0
  22. package/src/components/error-state/error-state.scss +49 -0
  23. package/src/components/form-editor/form-editor.component.tsx +297 -0
  24. package/src/components/form-editor/form-editor.scss +50 -0
  25. package/src/components/form-renderer/form-renderer.component.tsx +82 -0
  26. package/src/components/form-renderer/form-renderer.scss +31 -0
  27. package/src/components/interactive-builder/add-question-modal.component.tsx +494 -0
  28. package/src/components/interactive-builder/edit-question-modal.component.tsx +447 -0
  29. package/src/components/interactive-builder/editable-value.component.tsx +60 -0
  30. package/src/components/interactive-builder/editable-value.scss +23 -0
  31. package/src/components/interactive-builder/interactive-builder.component.tsx +403 -0
  32. package/src/components/interactive-builder/interactive-builder.scss +83 -0
  33. package/src/components/interactive-builder/new-form-modal.component.tsx +86 -0
  34. package/src/components/interactive-builder/page-modal.component.tsx +91 -0
  35. package/src/components/interactive-builder/question-modal.scss +35 -0
  36. package/src/components/interactive-builder/section-modal.component.tsx +94 -0
  37. package/src/components/interactive-builder/value-editor.component.tsx +55 -0
  38. package/src/components/interactive-builder/value-editor.scss +10 -0
  39. package/src/components/modals/save-form.component.tsx +310 -0
  40. package/src/components/modals/save-form.scss +5 -0
  41. package/src/components/schema-editor/schema-editor.component.tsx +190 -0
  42. package/src/components/schema-editor/schema-editor.scss +30 -0
  43. package/src/config-schema.ts +47 -0
  44. package/src/constants.ts +3 -0
  45. package/src/declarations.d.tsx +2 -0
  46. package/src/form-builder-app-menu-link.component.tsx +13 -0
  47. package/src/forms.resource.ts +178 -0
  48. package/src/hooks/useClobdata.ts +20 -0
  49. package/src/hooks/useConceptLookup.ts +18 -0
  50. package/src/hooks/useEncounterTypes.ts +18 -0
  51. package/src/hooks/useForm.ts +18 -0
  52. package/src/hooks/useForms.ts +20 -0
  53. package/src/index.ts +70 -0
  54. package/src/root.component.tsx +19 -0
  55. package/src/setup-tests.ts +1 -0
  56. package/src/types.ts +132 -0
  57. package/translations/en.json +110 -0
  58. package/tsconfig.json +23 -0
  59. package/turbo.json +26 -0
  60. package/webpack.config.js +19 -0
@@ -0,0 +1,403 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { Accordion, AccordionItem, Button, InlineLoading } from "@carbon/react";
4
+ import { Add, Edit } from "@carbon/react/icons";
5
+ import { useParams } from "react-router-dom";
6
+ import { OHRIFormSchema } from "@ohri/openmrs-ohri-form-engine-lib";
7
+ import { showToast, showNotification } from "@openmrs/esm-framework";
8
+ import { RouteParams, Schema } from "../../types";
9
+ import AddQuestionModal from "./add-question-modal.component";
10
+ import EditQuestionModal from "./edit-question-modal.component";
11
+ import EditableValue from "./editable-value.component";
12
+ import NewFormModal from "./new-form-modal.component";
13
+ import PageModal from "./page-modal.component";
14
+ import SectionModal from "./section-modal.component";
15
+ import styles from "./interactive-builder.scss";
16
+
17
+ type InteractiveBuilderProps = {
18
+ isLoading: boolean;
19
+ onSchemaChange: (schema: Schema) => void;
20
+ schema: Schema;
21
+ };
22
+
23
+ const InteractiveBuilder: React.FC<InteractiveBuilderProps> = ({
24
+ isLoading,
25
+ onSchemaChange,
26
+ schema,
27
+ }) => {
28
+ const { t } = useTranslation();
29
+ const { formUuid } = useParams<RouteParams>();
30
+ const isEditingExistingForm = !!formUuid;
31
+ const [formName, setFormName] = useState(schema ? schema.name : "");
32
+ const [pageName, setPageName] = useState("");
33
+ const [sectionName, setSectionName] = useState("");
34
+ const [pageIndex, setPageIndex] = useState(0);
35
+ const [sectionIndex, setSectionIndex] = useState(0);
36
+ const [questionIndex, setQuestionIndex] = useState(0);
37
+ const [showNewFormModal, setShowNewFormModal] = useState(false);
38
+ const [showAddPageModal, setShowAddPageModal] = useState(false);
39
+ const [showAddQuestionModal, setShowAddQuestionModal] = useState(false);
40
+ const [showEditQuestionModal, setShowEditQuestionModal] = useState(false);
41
+ const [showAddSectionModal, setShowAddSectionModal] = useState(false);
42
+ const [questionToEdit, setQuestionToEdit] = useState(null);
43
+
44
+ const initializeSchema = () => {
45
+ const dummySchema: OHRIFormSchema = {
46
+ name: null,
47
+ pages: [],
48
+ processor: "EncounterFormProcessor",
49
+ encounterType: "",
50
+ referencedForms: [],
51
+ uuid: "",
52
+ };
53
+
54
+ if (!schema) {
55
+ onSchemaChange({ ...dummySchema });
56
+ }
57
+ };
58
+
59
+ const launchNewFormModal = () => {
60
+ initializeSchema();
61
+ setShowNewFormModal(true);
62
+ };
63
+
64
+ const resetIndices = () => {
65
+ setPageIndex(0);
66
+ setSectionIndex(0);
67
+ setQuestionIndex(0);
68
+ };
69
+
70
+ const addPage = () => {
71
+ setShowAddPageModal(true);
72
+ };
73
+
74
+ const addSection = () => {
75
+ setShowAddSectionModal(true);
76
+ };
77
+
78
+ const addQuestion = () => {
79
+ setShowAddQuestionModal(true);
80
+ };
81
+
82
+ const editQuestion = () => {
83
+ setShowEditQuestionModal(true);
84
+ };
85
+
86
+ const renameSchema = (value) => {
87
+ try {
88
+ if (value) {
89
+ schema.name = value;
90
+ } else {
91
+ schema.name = formName;
92
+ }
93
+
94
+ onSchemaChange({ ...schema });
95
+
96
+ showToast({
97
+ title: t("success", "Success!"),
98
+ kind: "success",
99
+ critical: true,
100
+ description: t("formRenamed", "Form renamed"),
101
+ });
102
+ } catch (error) {
103
+ showNotification({
104
+ title: t("errorRenamingForm", "Error renaming form"),
105
+ kind: "error",
106
+ critical: true,
107
+ description: error?.message,
108
+ });
109
+ }
110
+ };
111
+
112
+ const renamePage = (name, pageIndex) => {
113
+ try {
114
+ if (name) {
115
+ schema.pages[pageIndex].label = name;
116
+ } else if (pageName) {
117
+ schema.pages[pageIndex].label = pageName;
118
+ }
119
+
120
+ onSchemaChange({ ...schema });
121
+
122
+ showToast({
123
+ title: t("success", "Success!"),
124
+ kind: "success",
125
+ critical: true,
126
+ description: t("pageRenamed", "Page renamed"),
127
+ });
128
+ } catch (error) {
129
+ showNotification({
130
+ title: t("errorRenamingPage", "Error renaming page"),
131
+ kind: "error",
132
+ critical: true,
133
+ description: error?.message,
134
+ });
135
+ }
136
+ };
137
+
138
+ const renameSection = (name, pageIndex, sectionIndex) => {
139
+ try {
140
+ if (name) {
141
+ schema.pages[pageIndex].sections[sectionIndex].label = name;
142
+ }
143
+ onSchemaChange({ ...schema });
144
+
145
+ resetIndices();
146
+
147
+ showToast({
148
+ title: t("success", "Success!"),
149
+ kind: "success",
150
+ critical: true,
151
+ description: t("sectionRenamed", "Section renamed"),
152
+ });
153
+ } catch (error) {
154
+ showNotification({
155
+ title: t("errorRenamingSection", "Error renaming section"),
156
+ kind: "error",
157
+ critical: true,
158
+ description: error?.message,
159
+ });
160
+ }
161
+ };
162
+
163
+ return (
164
+ <div className={styles.container}>
165
+ {isLoading ? (
166
+ <InlineLoading
167
+ description={t("loadingSchema", "Loading schema") + "..."}
168
+ />
169
+ ) : null}
170
+
171
+ {showNewFormModal ? (
172
+ <NewFormModal
173
+ schema={schema}
174
+ onSchemaChange={onSchemaChange}
175
+ showModal={showNewFormModal}
176
+ onModalChange={setShowNewFormModal}
177
+ />
178
+ ) : null}
179
+
180
+ {showAddPageModal ? (
181
+ <PageModal
182
+ schema={schema}
183
+ onSchemaChange={onSchemaChange}
184
+ showModal={showAddPageModal}
185
+ onModalChange={setShowAddPageModal}
186
+ />
187
+ ) : null}
188
+
189
+ {showAddSectionModal ? (
190
+ <SectionModal
191
+ schema={schema}
192
+ onSchemaChange={onSchemaChange}
193
+ pageIndex={pageIndex}
194
+ resetIndices={resetIndices}
195
+ showModal={showAddSectionModal}
196
+ onModalChange={setShowAddSectionModal}
197
+ />
198
+ ) : null}
199
+
200
+ {showAddQuestionModal ? (
201
+ <AddQuestionModal
202
+ onModalChange={setShowAddQuestionModal}
203
+ onQuestionEdit={setQuestionToEdit}
204
+ onSchemaChange={onSchemaChange}
205
+ pageIndex={pageIndex}
206
+ sectionIndex={sectionIndex}
207
+ questionIndex={questionIndex}
208
+ questionToEdit={questionToEdit}
209
+ resetIndices={resetIndices}
210
+ schema={schema}
211
+ showModal={showAddQuestionModal}
212
+ />
213
+ ) : null}
214
+
215
+ {showEditQuestionModal ? (
216
+ <EditQuestionModal
217
+ onModalChange={setShowEditQuestionModal}
218
+ onQuestionEdit={setQuestionToEdit}
219
+ onSchemaChange={onSchemaChange}
220
+ pageIndex={pageIndex}
221
+ questionIndex={questionIndex}
222
+ questionToEdit={questionToEdit}
223
+ resetIndices={resetIndices}
224
+ schema={schema}
225
+ sectionIndex={sectionIndex}
226
+ showModal={showEditQuestionModal}
227
+ />
228
+ ) : null}
229
+
230
+ {schema?.name && (
231
+ <>
232
+ <div className={styles.header}>
233
+ <p className={styles.explainer}>
234
+ {t(
235
+ "welcomeText",
236
+ "Welcome to the Interactive Schema builder! Add pages, sections and questions to your form. The Preview on the right automatically updates as you build your form. When done, click Save Form to save your form."
237
+ )}
238
+ </p>
239
+ <Button
240
+ kind="primary"
241
+ renderIcon={Add}
242
+ onClick={addPage}
243
+ iconDescription={t("addPage", "Add Page")}
244
+ >
245
+ {t("addPage", "Add Page")}
246
+ </Button>
247
+ </div>
248
+ <div className={styles.editorContainer}>
249
+ <EditableValue
250
+ elementType="schema"
251
+ id="formNameInput"
252
+ value={schema?.name}
253
+ onChange={(event) => setFormName(event.target.value)}
254
+ onSave={(name) => renameSchema(name)}
255
+ />
256
+ </div>
257
+ </>
258
+ )}
259
+
260
+ {!isEditingExistingForm && !schema?.name && (
261
+ <div className={styles.header}>
262
+ <p className={styles.explainer}>
263
+ {t(
264
+ "interactiveBuilderHelperText",
265
+ "The Interactive Builder lets you build your form schema without writing JSON code. The Preview on the right automatically updates as you build your form. When done, click Save Form to save your form."
266
+ )}
267
+ </p>
268
+
269
+ <Button
270
+ onClick={launchNewFormModal}
271
+ className={styles.startButton}
272
+ kind="primary"
273
+ >
274
+ {t("startBuilding", "Start building")}
275
+ </Button>
276
+ </div>
277
+ )}
278
+
279
+ {schema?.pages?.length
280
+ ? schema.pages.map((page, pageIndex) => (
281
+ <div className={styles.editableFieldsContainer}>
282
+ <div className={styles.editorContainer}>
283
+ <EditableValue
284
+ elementType="page"
285
+ id="pageNameInput"
286
+ value={schema.pages[pageIndex].label}
287
+ onChange={(event) => setPageName(event.target.value)}
288
+ onSave={(name) => renamePage(name, pageIndex)}
289
+ />
290
+ </div>
291
+ <div>
292
+ {page?.sections?.length ? (
293
+ <p className={styles.explainer}>
294
+ {t(
295
+ "expandSectionExplainer",
296
+ "Below are the sections linked to this page. Expand each section to add questions to it."
297
+ )}
298
+ </p>
299
+ ) : null}
300
+ {page?.sections?.length ? (
301
+ page.sections?.map((section, sectionIndex) => (
302
+ <Accordion>
303
+ <AccordionItem title={section.label}>
304
+ <>
305
+ <div className={styles.editorContainer}>
306
+ <EditableValue
307
+ elementType="section"
308
+ id="sectionNameInput"
309
+ value={section.label}
310
+ onChange={(event) =>
311
+ setSectionName(event.target.value)
312
+ }
313
+ onSave={(name) =>
314
+ renameSection(name, pageIndex, sectionIndex)
315
+ }
316
+ />
317
+ </div>
318
+ <div>
319
+ {section.questions?.length ? (
320
+ section.questions.map(
321
+ (question, questionIndex) => (
322
+ <div className={styles.editorContainer}>
323
+ <p className={styles.questionLabel}>
324
+ {question.label}
325
+ </p>
326
+ <Button
327
+ kind="ghost"
328
+ size="sm"
329
+ iconDescription={t(
330
+ "editNameButton",
331
+ "Edit"
332
+ )}
333
+ onClick={() => {
334
+ editQuestion();
335
+ setPageIndex(pageIndex);
336
+ setSectionIndex(sectionIndex);
337
+ setQuestionIndex(questionIndex);
338
+ setQuestionToEdit(question);
339
+ }}
340
+ renderIcon={(props) => (
341
+ <Edit size={16} {...props} />
342
+ )}
343
+ hasIconOnly
344
+ />
345
+ </div>
346
+ )
347
+ )
348
+ ) : (
349
+ <p className={styles.explainer}>
350
+ {t(
351
+ "sectionExplainer",
352
+ "A section will typically contain one or more questions. Click the button below to add a question to this section."
353
+ )}
354
+ </p>
355
+ )}
356
+ <Button
357
+ className={styles.addQuestionButton}
358
+ kind="primary"
359
+ renderIcon={Add}
360
+ onClick={() => {
361
+ addQuestion();
362
+ setQuestionIndex(questionIndex);
363
+ setPageIndex(pageIndex);
364
+ setSectionIndex(sectionIndex);
365
+ }}
366
+ iconDescription={t("addQuestion", "Add Question")}
367
+ >
368
+ {t("addQuestion", "Add Question")}
369
+ </Button>
370
+ </div>
371
+ </>
372
+ </AccordionItem>
373
+ </Accordion>
374
+ ))
375
+ ) : (
376
+ <p className={styles.explainer}>
377
+ {t(
378
+ "pageExplainer",
379
+ "Pages typically have one or more sections. Click the button below to add a section to your page."
380
+ )}
381
+ </p>
382
+ )}
383
+ </div>
384
+ <Button
385
+ className={styles.addSectionButton}
386
+ kind="primary"
387
+ renderIcon={Add}
388
+ onClick={() => {
389
+ addSection();
390
+ setPageIndex(pageIndex);
391
+ }}
392
+ iconDescription={t("addSection", "Add Section")}
393
+ >
394
+ {t("addSection", "Add Section")}
395
+ </Button>
396
+ </div>
397
+ ))
398
+ : null}
399
+ </div>
400
+ );
401
+ };
402
+
403
+ export default InteractiveBuilder;
@@ -0,0 +1,83 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ .container {
5
+ :global(.cds--modal-content:focus) {
6
+ outline: none;
7
+ }
8
+
9
+ :global(.cds--tooltip) {
10
+ z-index: 1;
11
+ }
12
+ }
13
+
14
+ .helperText {
15
+ @include type.type-style('body-compact-01');
16
+ margin: 1rem 0rem;
17
+ }
18
+
19
+ .heading {
20
+ @include type.type-style('heading-03');
21
+ margin: 1rem 0rem 0.5rem;
22
+ }
23
+
24
+ .styledHeading {
25
+ @include type.type-style('heading-02');
26
+ margin: 0.5rem 0rem;
27
+
28
+ &:after {
29
+ content: "";
30
+ display: block;
31
+ width: 2rem;
32
+ padding-top: 0.188rem;
33
+ border-bottom: 0.375rem solid var(--brand-03);
34
+ }
35
+ }
36
+
37
+ .subheading {
38
+ @extend .heading;
39
+ @include type.type-style('heading-02');
40
+ }
41
+
42
+ .name {
43
+ @extend .heading;
44
+ @include type.type-style('heading-02');
45
+ }
46
+
47
+ .editableFieldsContainer {
48
+ margin: 1rem 0;
49
+ border-bottom: 1px solid $ui-03;
50
+ }
51
+
52
+ .addQuestionButton {
53
+ margin: 0.75rem 0;
54
+ }
55
+
56
+ .header {
57
+ margin-top: 5rem;
58
+ margin-bottom: 1rem;
59
+ width: 80%;
60
+ }
61
+
62
+ .explainer {
63
+ margin: 1.5rem 0;
64
+ }
65
+
66
+ .startButton {
67
+ margin: 1rem 0;
68
+ }
69
+
70
+ .editorContainer {
71
+ display: flex;
72
+ justify-content: space-between;
73
+ margin: 0.25rem 0;
74
+ align-items: center
75
+ }
76
+
77
+ .questionLabel {
78
+ @include type.type-style('body-01');
79
+ }
80
+
81
+ .addSectionButton {
82
+ margin: 1.5rem 0;
83
+ }
@@ -0,0 +1,86 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ Form,
7
+ FormGroup,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ TextInput,
12
+ } from "@carbon/react";
13
+ import { showToast, showNotification } from "@openmrs/esm-framework";
14
+ import { Schema } from "../../types";
15
+
16
+ type NewFormModalProps = {
17
+ schema: Schema;
18
+ onSchemaChange: (schema: Schema) => void;
19
+ showModal: boolean;
20
+ onModalChange: (showModal: boolean) => void;
21
+ };
22
+
23
+ const NewFormModal: React.FC<NewFormModalProps> = ({
24
+ schema,
25
+ onSchemaChange,
26
+ showModal,
27
+ onModalChange,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const [formName, setFormName] = useState("");
31
+
32
+ const updateFormName = () => {
33
+ try {
34
+ schema.name = formName;
35
+ onSchemaChange({ ...schema });
36
+
37
+ showToast({
38
+ title: t("success", "Success!"),
39
+ kind: "success",
40
+ critical: true,
41
+ description: t("formCreated", "New form created"),
42
+ });
43
+ } catch (error) {
44
+ showNotification({
45
+ title: t("errorCreatingForm", "Error creating form"),
46
+ kind: "error",
47
+ critical: true,
48
+ description: error?.message,
49
+ });
50
+ }
51
+ };
52
+
53
+ return (
54
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
55
+ <ModalHeader title={t("createNewForm", "Create a new form")} />
56
+ <Form onSubmit={(event) => event.preventDefault()}>
57
+ <ModalBody>
58
+ <FormGroup legendText={""}>
59
+ <TextInput
60
+ id="formName"
61
+ labelText={t("formName", "Form name")}
62
+ value={formName}
63
+ onChange={(event) => setFormName(event.target.value)}
64
+ />
65
+ </FormGroup>
66
+ </ModalBody>
67
+ </Form>
68
+ <ModalFooter>
69
+ <Button kind="secondary" onClick={() => onModalChange(false)}>
70
+ {t("cancel", "Cancel")}
71
+ </Button>
72
+ <Button
73
+ disabled={!formName}
74
+ onClick={() => {
75
+ updateFormName();
76
+ onModalChange(false);
77
+ }}
78
+ >
79
+ <span>{t("createForm", "Create Form")}</span>
80
+ </Button>
81
+ </ModalFooter>
82
+ </ComposedModal>
83
+ );
84
+ };
85
+
86
+ export default NewFormModal;
@@ -0,0 +1,91 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ Form,
7
+ FormGroup,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ TextInput,
12
+ } from "@carbon/react";
13
+ import { showToast, showNotification } from "@openmrs/esm-framework";
14
+ import { Schema } from "../../types";
15
+
16
+ type PageModalProps = {
17
+ schema: Schema;
18
+ onSchemaChange: (schema: Schema) => void;
19
+ showModal: boolean;
20
+ onModalChange: (showModal: boolean) => void;
21
+ };
22
+
23
+ const PageModal: React.FC<PageModalProps> = ({
24
+ schema,
25
+ onSchemaChange,
26
+ showModal,
27
+ onModalChange,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const [pageTitle, setPageTitle] = useState("");
31
+
32
+ const handleUpdatePageTitle = () => {
33
+ updatePages();
34
+ onModalChange(false);
35
+ };
36
+
37
+ const updatePages = () => {
38
+ try {
39
+ if (pageTitle) {
40
+ schema.pages.push({
41
+ label: pageTitle,
42
+ sections: [],
43
+ });
44
+
45
+ onSchemaChange({ ...schema });
46
+ setPageTitle("");
47
+ }
48
+ showToast({
49
+ title: t("success", "Success!"),
50
+ kind: "success",
51
+ critical: true,
52
+ description: t("pageCreated", "New page created"),
53
+ });
54
+ } catch (error) {
55
+ showNotification({
56
+ title: t("errorCreatingPage", "Error creating page"),
57
+ kind: "error",
58
+ critical: true,
59
+ description: error?.message,
60
+ });
61
+ }
62
+ };
63
+
64
+ return (
65
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
66
+ <ModalHeader title={t("createNewPage", "Create a new page")} />
67
+ <Form onSubmit={(event) => event.preventDefault()}>
68
+ <ModalBody>
69
+ <FormGroup legendText={""}>
70
+ <TextInput
71
+ id="pageTitle"
72
+ labelText={t("enterPageTitle", "Enter a title for your new page")}
73
+ value={pageTitle}
74
+ onChange={(event) => setPageTitle(event.target.value)}
75
+ />
76
+ </FormGroup>
77
+ </ModalBody>
78
+ </Form>
79
+ <ModalFooter>
80
+ <Button onClick={() => onModalChange(false)} kind="secondary">
81
+ {t("cancel", "Cancel")}
82
+ </Button>
83
+ <Button disabled={!pageTitle} onClick={handleUpdatePageTitle}>
84
+ <span>{t("save", "Save")}</span>
85
+ </Button>
86
+ </ModalFooter>
87
+ </ComposedModal>
88
+ );
89
+ };
90
+
91
+ export default PageModal;
@@ -0,0 +1,35 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ .label {
5
+ margin-bottom: 0.5rem;
6
+ }
7
+
8
+ .loader {
9
+ padding: 1rem 0.5rem;
10
+ }
11
+
12
+ .tag {
13
+ margin-right: 0.5rem;
14
+ }
15
+
16
+ .conceptList {
17
+ max-height: 14rem;
18
+ overflow-y: auto;
19
+ border: 1px solid $ui-03;
20
+ border-top: none;
21
+ }
22
+
23
+ .conceptList li:hover {
24
+ background-color: $ui-03;
25
+ }
26
+
27
+ .concept {
28
+ padding: 0.75rem;
29
+ }
30
+
31
+ .emptyResults {
32
+ @include type.type-style("body-compact-01");
33
+ color: $text-02;
34
+ min-height: 1rem;
35
+ }