@openmrs/esm-form-builder-app 1.0.0

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 (52) hide show
  1. package/LICENSE +401 -0
  2. package/README.md +35 -0
  3. package/package.json +106 -0
  4. package/src/components/action-buttons/action-buttons.component.tsx +185 -0
  5. package/src/components/action-buttons/action-buttons.scss +16 -0
  6. package/src/components/dashboard/dashboard.component.tsx +309 -0
  7. package/src/components/dashboard/dashboard.scss +112 -0
  8. package/src/components/dashboard/dashboard.test.tsx +208 -0
  9. package/src/components/empty-state/empty-data-illustration.component.tsx +51 -0
  10. package/src/components/empty-state/empty-state.component.tsx +41 -0
  11. package/src/components/empty-state/empty-state.scss +55 -0
  12. package/src/components/error-state/error-state.component.tsx +37 -0
  13. package/src/components/error-state/error-state.scss +49 -0
  14. package/src/components/form-editor/form-editor.component.tsx +125 -0
  15. package/src/components/form-editor/form-editor.scss +33 -0
  16. package/src/components/form-renderer/form-renderer.component.tsx +123 -0
  17. package/src/components/form-renderer/form-renderer.scss +57 -0
  18. package/src/components/interactive-builder/add-question-modal.component.tsx +427 -0
  19. package/src/components/interactive-builder/delete-page-modal.component.tsx +89 -0
  20. package/src/components/interactive-builder/delete-question-modal.component.tsx +93 -0
  21. package/src/components/interactive-builder/delete-section-modal.component.tsx +91 -0
  22. package/src/components/interactive-builder/edit-question-modal.component.tsx +465 -0
  23. package/src/components/interactive-builder/editable-value.component.tsx +64 -0
  24. package/src/components/interactive-builder/editable-value.scss +23 -0
  25. package/src/components/interactive-builder/interactive-builder.component.tsx +569 -0
  26. package/src/components/interactive-builder/interactive-builder.scss +100 -0
  27. package/src/components/interactive-builder/new-form-modal.component.tsx +86 -0
  28. package/src/components/interactive-builder/page-modal.component.tsx +91 -0
  29. package/src/components/interactive-builder/question-modal.scss +35 -0
  30. package/src/components/interactive-builder/section-modal.component.tsx +94 -0
  31. package/src/components/interactive-builder/value-editor.component.tsx +55 -0
  32. package/src/components/interactive-builder/value-editor.scss +10 -0
  33. package/src/components/modals/save-form.component.tsx +310 -0
  34. package/src/components/modals/save-form.scss +5 -0
  35. package/src/components/schema-editor/schema-editor.component.tsx +191 -0
  36. package/src/components/schema-editor/schema-editor.scss +26 -0
  37. package/src/config-schema.ts +47 -0
  38. package/src/constants.ts +3 -0
  39. package/src/declarations.d.tsx +2 -0
  40. package/src/form-builder-app-menu-link.component.tsx +13 -0
  41. package/src/forms.resource.ts +178 -0
  42. package/src/hooks/useClobdata.ts +20 -0
  43. package/src/hooks/useConceptLookup.ts +18 -0
  44. package/src/hooks/useConceptName.ts +18 -0
  45. package/src/hooks/useEncounterTypes.ts +18 -0
  46. package/src/hooks/useForm.ts +18 -0
  47. package/src/hooks/useForms.ts +20 -0
  48. package/src/index.ts +70 -0
  49. package/src/root.component.tsx +19 -0
  50. package/src/setup-tests.ts +11 -0
  51. package/src/test-helpers.tsx +37 -0
  52. package/src/types.ts +132 -0
@@ -0,0 +1,208 @@
1
+ import React from "react";
2
+ import { screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { navigate, openmrsFetch } from "@openmrs/esm-framework";
5
+ import { renderWithSwr, waitForLoadingToFinish } from "../../test-helpers";
6
+ import Dashboard from "./dashboard.component";
7
+
8
+ const mockedOpenmrsFetch = openmrsFetch as jest.Mock;
9
+
10
+ const formsResponse = [
11
+ {
12
+ uuid: "2ddde996-b1c3-37f1-a53e-378dd1a4f6b5",
13
+ name: "Test Form 1",
14
+ encounterType: {
15
+ uuid: "dd528487-82a5-4082-9c72-ed246bd49591",
16
+ name: "Consultation",
17
+ },
18
+ version: "1",
19
+ published: true,
20
+ retired: false,
21
+ resources: [
22
+ {
23
+ uuid: "ea27fd4f-7a4d-4869-8855-5b890c8fed56",
24
+ name: "JSON schema",
25
+ dataType: "AmpathJsonSchema",
26
+ valueReference: "511efba8-f08f-4544-a6da-6a6fa2497b9e",
27
+ },
28
+ ],
29
+ },
30
+ ];
31
+
32
+ describe("Dashboard", () => {
33
+ it("renders an empty state view if no forms are available", async () => {
34
+ mockedOpenmrsFetch.mockReturnValueOnce({ data: { results: [] } });
35
+
36
+ renderDashboard();
37
+
38
+ await waitForLoadingToFinish();
39
+
40
+ expect(
41
+ screen.getByRole("heading", { name: /form builder/i })
42
+ ).toBeInTheDocument();
43
+ expect(screen.getByRole("heading", { name: /forms/i })).toBeInTheDocument();
44
+ expect(screen.getByTitle(/empty data illustration/i)).toBeInTheDocument();
45
+ expect(
46
+ screen.getByText(/there are no forms to display/i)
47
+ ).toBeInTheDocument();
48
+ expect(screen.getByText(/create a new form/i)).toBeInTheDocument();
49
+ });
50
+
51
+ it("renders a list of forms fetched from the server", async () => {
52
+ mockedOpenmrsFetch.mockReturnValueOnce({
53
+ data: {
54
+ results: formsResponse,
55
+ },
56
+ });
57
+
58
+ renderDashboard();
59
+
60
+ await waitForLoadingToFinish();
61
+
62
+ expect(
63
+ screen.getByRole("heading", { name: /form builder/i })
64
+ ).toBeInTheDocument();
65
+ expect(
66
+ screen.getByRole("button", { name: /filter by publish status/i })
67
+ ).toBeInTheDocument();
68
+ expect(
69
+ screen.getByRole("button", { name: /create a new form/i })
70
+ ).toBeInTheDocument();
71
+ expect(
72
+ screen.getByRole("button", { name: /edit schema/i })
73
+ ).toBeInTheDocument();
74
+ expect(
75
+ screen.getByRole("button", { name: /download schema/i })
76
+ ).toBeInTheDocument();
77
+ expect(
78
+ screen.getByRole("search", { name: /filter table/i })
79
+ ).toBeInTheDocument();
80
+ expect(screen.getByRole("table")).toBeInTheDocument();
81
+ expect(screen.getByText(/Test Form 1/i)).toBeInTheDocument();
82
+ });
83
+
84
+ it("searching for a form by name filters the list of forms", async () => {
85
+ const user = userEvent.setup();
86
+
87
+ mockedOpenmrsFetch.mockReturnValueOnce({
88
+ data: {
89
+ results: formsResponse,
90
+ },
91
+ });
92
+
93
+ renderDashboard();
94
+
95
+ await waitForLoadingToFinish();
96
+
97
+ expect(screen.getByText(/Test Form 1/i)).toBeInTheDocument();
98
+
99
+ const searchbox = screen.getByRole("searchbox");
100
+
101
+ await user.type(searchbox, "COVID");
102
+
103
+ expect(screen.queryByText(/Test Form 1/i)).not.toBeInTheDocument();
104
+ expect(
105
+ screen.getByText(/no matching forms to display/i)
106
+ ).toBeInTheDocument();
107
+ });
108
+
109
+ it('filters the list of forms by "published" status', async () => {
110
+ const user = userEvent.setup();
111
+
112
+ mockedOpenmrsFetch.mockReturnValueOnce({
113
+ data: {
114
+ results: formsResponse,
115
+ },
116
+ });
117
+
118
+ renderDashboard();
119
+
120
+ await waitForLoadingToFinish();
121
+
122
+ const publishStatusFilter = screen.getByRole("button", {
123
+ name: /filter by publish status/i,
124
+ });
125
+
126
+ await user.click(publishStatusFilter);
127
+ await user.click(screen.getByRole("option", { name: /unpublished/i }));
128
+
129
+ expect(screen.queryByText(/Test Form 1/i)).not.toBeInTheDocument();
130
+ expect(
131
+ screen.getByText(/no matching forms to display/i)
132
+ ).toBeInTheDocument();
133
+ });
134
+
135
+ it('clicking on "create a new form" button navigates to the "create form" page', async () => {
136
+ const user = userEvent.setup();
137
+
138
+ mockedOpenmrsFetch.mockReturnValueOnce({
139
+ data: {
140
+ results: formsResponse,
141
+ },
142
+ });
143
+
144
+ renderDashboard();
145
+
146
+ await waitForLoadingToFinish();
147
+
148
+ const createFormButton = screen.getByRole("button", {
149
+ name: /create a new form/i,
150
+ });
151
+
152
+ await user.click(createFormButton);
153
+
154
+ expect(navigate).toHaveBeenCalledWith({
155
+ to: expect.stringMatching(/form\-builder\/new/),
156
+ });
157
+ });
158
+
159
+ it('clicking on "edit schema" button navigates to the "edit schema" page', async () => {
160
+ const user = userEvent.setup();
161
+
162
+ mockedOpenmrsFetch.mockReturnValueOnce({
163
+ data: {
164
+ results: formsResponse,
165
+ },
166
+ });
167
+
168
+ renderDashboard();
169
+
170
+ await waitForLoadingToFinish();
171
+
172
+ const editSchemaButton = screen.getByRole("button", {
173
+ name: /edit schema/i,
174
+ });
175
+
176
+ await user.click(editSchemaButton);
177
+
178
+ expect(navigate).toHaveBeenCalledWith({
179
+ to: expect.stringMatching(/form\-builder\/edit/),
180
+ });
181
+ });
182
+
183
+ it('clicking on "download schema" button downloads the schema', async () => {
184
+ const user = userEvent.setup();
185
+
186
+ mockedOpenmrsFetch.mockReturnValueOnce({
187
+ data: {
188
+ results: formsResponse,
189
+ },
190
+ });
191
+
192
+ renderDashboard();
193
+
194
+ await waitForLoadingToFinish();
195
+
196
+ const downloadSchemaButton = screen.getByRole("button", {
197
+ name: /download schema/i,
198
+ });
199
+
200
+ await user.click(downloadSchemaButton);
201
+
202
+ expect(window.URL.createObjectURL).toHaveBeenCalled();
203
+ });
204
+ });
205
+
206
+ function renderDashboard() {
207
+ renderWithSwr(<Dashboard />);
208
+ }
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+
3
+ export const EmptyDataIllustration = ({ width = "64", height = "64" }) => {
4
+ return (
5
+ <svg width={width} height={height} viewBox="0 0 64 64">
6
+ <title>Empty data illustration</title>
7
+ <g fill="none" fillRule="evenodd">
8
+ <path
9
+ d="M38.133 13.186H21.947c-.768.001-1.39.623-1.39 1.391V50.55l-.186.057-3.97 1.216a.743.743 0 01-.927-.493L3.664 12.751a.742.742 0 01.492-.926l6.118-1.874 17.738-5.43 6.119-1.873a.741.741 0 01.926.492L38.076 13l.057.186z"
10
+ fill="#F4F4F4"
11
+ />
12
+ <path
13
+ d="M41.664 13L38.026 1.117A1.576 1.576 0 0036.056.07l-8.601 2.633-17.737 5.43-8.603 2.634a1.578 1.578 0 00-1.046 1.97l12.436 40.616a1.58 1.58 0 001.969 1.046l5.897-1.805.185-.057v-.194l-.185.057-5.952 1.822a1.393 1.393 0 01-1.737-.923L.247 12.682a1.39 1.39 0 01.923-1.738L9.772 8.31 27.51 2.881 36.112.247a1.393 1.393 0 011.737.923L41.47 13l.057.186h.193l-.057-.185z"
14
+ fill="#8D8D8D"
15
+ />
16
+ <path
17
+ d="M11.378 11.855a.836.836 0 01-.798-.59L9.385 7.361a.835.835 0 01.554-1.042l16.318-4.996a.836.836 0 011.042.554l1.195 3.902a.836.836 0 01-.554 1.043l-16.318 4.995a.831.831 0 01-.244.037z"
18
+ fill="#C6C6C6"
19
+ />
20
+ <circle fill="#C6C6C6" cx={17.636} cy={2.314} r={1.855} />
21
+ <circle
22
+ fill="#FFF"
23
+ fillRule="nonzero"
24
+ cx={17.636}
25
+ cy={2.314}
26
+ r={1.175}
27
+ />
28
+ <path
29
+ d="M55.893 53.995H24.544a.79.79 0 01-.788-.789V15.644a.79.79 0 01.788-.788h31.349a.79.79 0 01.788.788v37.562a.79.79 0 01-.788.789z"
30
+ fill="#F4F4F4"
31
+ />
32
+ <path
33
+ d="M41.47 13H21.948a1.579 1.579 0 00-1.576 1.577V52.4l.185-.057V14.577c.001-.768.623-1.39 1.391-1.39h19.581L41.471 13zm17.02 0H21.947a1.579 1.579 0 00-1.576 1.577v42.478c0 .87.706 1.576 1.576 1.577H58.49a1.579 1.579 0 001.576-1.577V14.577a1.579 1.579 0 00-1.576-1.576zm1.39 44.055c0 .768-.622 1.39-1.39 1.392H21.947c-.768-.001-1.39-.624-1.39-1.392V14.577c0-.768.622-1.39 1.39-1.39H58.49c.768 0 1.39.622 1.39 1.39v42.478z"
34
+ fill="#8D8D8D"
35
+ />
36
+ <path
37
+ d="M48.751 17.082H31.686a.836.836 0 01-.835-.835v-4.081c0-.46.374-.834.835-.835H48.75c.461 0 .834.374.835.835v4.08c0 .462-.374.835-.835.836z"
38
+ fill="#C6C6C6"
39
+ />
40
+ <circle fill="#C6C6C6" cx={40.218} cy={9.755} r={1.855} />
41
+ <circle
42
+ fill="#FFF"
43
+ fillRule="nonzero"
44
+ cx={40.218}
45
+ cy={9.755}
46
+ r={1.13}
47
+ />
48
+ </g>
49
+ </svg>
50
+ );
51
+ };
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { Layer, Link, Tile } from "@carbon/react";
3
+ import { useTranslation } from "react-i18next";
4
+ import { navigate, useLayoutType } from "@openmrs/esm-framework";
5
+
6
+ import styles from "./empty-state.scss";
7
+ import { EmptyDataIllustration } from "./empty-data-illustration.component";
8
+
9
+ function EmptyState() {
10
+ const { t } = useTranslation();
11
+ const isTablet = useLayoutType() === "tablet";
12
+
13
+ return (
14
+ <Layer>
15
+ <Tile className={styles.tile}>
16
+ <div
17
+ className={isTablet ? styles.tabletHeading : styles.desktopHeading}
18
+ >
19
+ <h4>{t("forms", "Forms")}</h4>
20
+ </div>
21
+ <EmptyDataIllustration />
22
+ <p className={styles.content}>
23
+ {t("noFormsToDisplay", "There are no forms to display.")}
24
+ </p>
25
+ <p className={styles.action}>
26
+ <Link
27
+ onClick={() =>
28
+ navigate({
29
+ to: `${window.spaBase}/form-builder/new`,
30
+ })
31
+ }
32
+ >
33
+ {t("createNewForm", "Create a new form")}
34
+ </Link>
35
+ </p>
36
+ </Tile>
37
+ </Layer>
38
+ );
39
+ }
40
+
41
+ export default EmptyState;
@@ -0,0 +1,55 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .action {
6
+ margin-bottom: spacing.$spacing-03;
7
+ }
8
+
9
+ .content {
10
+ @include type.type-style("heading-compact-01");
11
+ color: $text-02;
12
+ margin-top: spacing.$spacing-05;
13
+ margin-bottom: spacing.$spacing-03;
14
+ }
15
+
16
+ .desktopHeading {
17
+ h4 {
18
+ @include type.type-style('heading-compact-02');
19
+ color: $text-02;
20
+ }
21
+ }
22
+
23
+ .tabletHeading {
24
+ h4 {
25
+ @include type.type-style('heading-03');
26
+ color: $text-02;
27
+ }
28
+ }
29
+
30
+ .desktopHeading, .tabletHeading {
31
+ text-align: left;
32
+ text-transform: capitalize;
33
+ margin-bottom: spacing.$spacing-05;
34
+
35
+ h4:after {
36
+ content: "";
37
+ display: block;
38
+ width: 2rem;
39
+ padding-top: 0.188rem;
40
+ border-bottom: 0.375rem solid var(--brand-03);
41
+ }
42
+ }
43
+
44
+ .heading:after {
45
+ content: "";
46
+ display: block;
47
+ width: 2rem;
48
+ padding-top: 0.188rem;
49
+ border-bottom: 0.375rem solid var(--brand-03);
50
+ }
51
+
52
+ .tile {
53
+ text-align: center;
54
+ border: 1px solid $ui-03;
55
+ }
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { Layer, Tile } from "@carbon/react";
3
+ import { useTranslation } from "react-i18next";
4
+ import { useLayoutType } from "@openmrs/esm-framework";
5
+ import styles from "./error-state.scss";
6
+
7
+ interface ErrorStateProps {
8
+ error: Error;
9
+ }
10
+
11
+ const ErrorState: React.FC<ErrorStateProps> = ({ error }) => {
12
+ const { t } = useTranslation();
13
+ const isTablet = useLayoutType() === "tablet";
14
+
15
+ return (
16
+ <Layer>
17
+ <Tile className={styles.tile}>
18
+ <div
19
+ className={isTablet ? styles.tabletHeading : styles.desktopHeading}
20
+ >
21
+ <h4>{t("forms", "Forms")}</h4>
22
+ </div>
23
+ <p className={styles.errorMessage}>
24
+ {t("error", "Error")}: {error?.message}
25
+ </p>
26
+ <p className={styles.errorCopy}>
27
+ {t(
28
+ "errorCopy",
29
+ "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above."
30
+ )}
31
+ </p>
32
+ </Tile>
33
+ </Layer>
34
+ );
35
+ };
36
+
37
+ export default ErrorState;
@@ -0,0 +1,49 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .errorMessage {
6
+ @include type.type-style("heading-compact-02");
7
+
8
+ margin-top: 2.25rem;
9
+ margin-bottom: spacing.$spacing-03;
10
+ }
11
+
12
+ .errorCopy {
13
+ margin-bottom: spacing.$spacing-03;
14
+ @include type.type-style("body-01");
15
+ color: $text-02;
16
+ }
17
+
18
+ .desktopHeading {
19
+ h4 {
20
+ @include type.type-style('heading-compact-02');
21
+ color: $text-02;
22
+ }
23
+ }
24
+
25
+ .tabletHeading {
26
+ h4 {
27
+ @include type.type-style('heading-03');
28
+ color: $text-02;
29
+ }
30
+ }
31
+
32
+ .desktopHeading, .tabletHeading {
33
+ text-align: left;
34
+ text-transform: capitalize;
35
+ margin-bottom: spacing.$spacing-05;
36
+
37
+ h4:after {
38
+ content: "";
39
+ display: block;
40
+ width: 2rem;
41
+ padding-top: 0.188rem;
42
+ border-bottom: 0.375rem solid var(--brand-03);
43
+ }
44
+ }
45
+
46
+ .tile {
47
+ text-align: center;
48
+ border: 1px solid $ui-03;
49
+ }
@@ -0,0 +1,125 @@
1
+ import React, { useCallback, useEffect, useState } from "react";
2
+ import {
3
+ Column,
4
+ InlineNotification,
5
+ Grid,
6
+ Tabs,
7
+ Tab,
8
+ TabList,
9
+ TabPanels,
10
+ TabPanel,
11
+ } from "@carbon/react";
12
+ import { useParams } from "react-router-dom";
13
+ import { useTranslation } from "react-i18next";
14
+ import { ExtensionSlot } from "@openmrs/esm-framework";
15
+ import { Schema, RouteParams } from "../../types";
16
+ import { useClobdata } from "../../hooks/useClobdata";
17
+ import { useForm } from "../../hooks/useForm";
18
+ import FormRenderer from "../form-renderer/form-renderer.component";
19
+ import InteractiveBuilder from "../interactive-builder/interactive-builder.component";
20
+ import SchemaEditor from "../schema-editor/schema-editor.component";
21
+ import styles from "./form-editor.scss";
22
+
23
+ const Error = ({ error, title }) => {
24
+ return (
25
+ <InlineNotification
26
+ style={{
27
+ minWidth: "100%",
28
+ margin: "0rem",
29
+ padding: "0rem",
30
+ }}
31
+ kind={"error"}
32
+ lowContrast
33
+ subtitle={error?.message}
34
+ title={title}
35
+ />
36
+ );
37
+ };
38
+
39
+ const FormEditor: React.FC = () => {
40
+ const { t } = useTranslation();
41
+ const { formUuid } = useParams<RouteParams>();
42
+ const [schema, setSchema] = useState<Schema>();
43
+ const { form, formError, isLoadingForm } = useForm(formUuid);
44
+ const { clobdata, clobdataError, isLoadingClobdata } = useClobdata(form);
45
+ const isLoadingFormOrSchema =
46
+ formUuid && (isLoadingClobdata || isLoadingForm);
47
+
48
+ useEffect(() => {
49
+ if (!isLoadingClobdata && clobdata) {
50
+ setSchema(clobdata);
51
+ }
52
+ }, [clobdata, isLoadingClobdata, setSchema]);
53
+
54
+ const updateSchema = useCallback((updatedSchema) => {
55
+ setSchema(updatedSchema);
56
+ }, []);
57
+
58
+ return (
59
+ <>
60
+ <div className={styles.breadcrumbsContainer}>
61
+ <ExtensionSlot extensionSlotName="breadcrumbs-slot" />
62
+ </div>
63
+ <div className={styles.container}>
64
+ <Grid className={styles.grid}>
65
+ <Column lg={8} md={8} className={styles.column}>
66
+ <Tabs>
67
+ <TabList>
68
+ <Tab>{t("schemaEditor", "Schema Editor")}</Tab>
69
+ </TabList>
70
+ <TabPanels>
71
+ <TabPanel>
72
+ <>
73
+ {formError ? (
74
+ <Error
75
+ error={formError}
76
+ title={t("formError", "Error loading form metadata")}
77
+ />
78
+ ) : null}
79
+ {clobdataError ? (
80
+ <Error
81
+ error={clobdataError}
82
+ title={t("schemaLoadError", "Error loading schema")}
83
+ />
84
+ ) : null}
85
+ <SchemaEditor
86
+ schema={schema}
87
+ onSchemaChange={updateSchema}
88
+ isLoading={isLoadingFormOrSchema}
89
+ />
90
+ </>
91
+ </TabPanel>
92
+ </TabPanels>
93
+ </Tabs>
94
+ </Column>
95
+ <Column lg={8} md={8} className={styles.column}>
96
+ <Tabs>
97
+ <TabList>
98
+ <Tab>{t("preview", "Preview")}</Tab>
99
+ <Tab>{t("interactiveBuilder", "Interactive Builder")}</Tab>
100
+ </TabList>
101
+ <TabPanels>
102
+ <TabPanel>
103
+ <FormRenderer
104
+ schema={schema}
105
+ onSchemaChange={updateSchema}
106
+ isLoading={isLoadingFormOrSchema}
107
+ />
108
+ </TabPanel>
109
+ <TabPanel>
110
+ <InteractiveBuilder
111
+ schema={schema}
112
+ onSchemaChange={updateSchema}
113
+ isLoading={isLoadingFormOrSchema}
114
+ />
115
+ </TabPanel>
116
+ </TabPanels>
117
+ </Tabs>
118
+ </Column>
119
+ </Grid>
120
+ </div>
121
+ </>
122
+ );
123
+ };
124
+
125
+ export default FormEditor;
@@ -0,0 +1,33 @@
1
+ @use "@carbon/styles/scss/spacing";
2
+ @use "@carbon/styles/scss/type";
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .container {
6
+ padding: 2rem;
7
+ display: flex;
8
+ flex-direction: column;
9
+ }
10
+
11
+ .breadcrumbsContainer {
12
+ nav {
13
+ background-color: $ui-02;
14
+ }
15
+ }
16
+
17
+ .grid {
18
+ margin-left: 0;
19
+ margin-right: 0;
20
+ padding-left: 0;
21
+ padding-right: 0;
22
+ max-width: 100%;
23
+
24
+ :global(.cds--tabs__nav-item--selected) {
25
+ border-bottom: 2px solid var(--cds-border-interactive, #005d5d);
26
+ outline: none !important;
27
+ }
28
+ }
29
+
30
+ .column {
31
+ margin-left: 0;
32
+ margin-right: 0;
33
+ }