@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,64 @@
1
+ import React from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { Button } from "@carbon/react";
4
+ import { Edit } from "@carbon/react/icons";
5
+ import ValueEditor from "./value-editor.component";
6
+ import styles from "./editable-value.scss";
7
+
8
+ type EditableValueProps = {
9
+ elementType?: "schema" | "page" | "section";
10
+ id: string;
11
+ value: string;
12
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
13
+ onSave: (value: string) => void;
14
+ };
15
+
16
+ const EditableValue: React.FC<EditableValueProps> = ({
17
+ elementType,
18
+ id,
19
+ value,
20
+ onChange,
21
+ onSave,
22
+ }) => {
23
+ const { t } = useTranslation();
24
+ const [editing, setEditing] = React.useState(false);
25
+
26
+ const closeEditor = () => {
27
+ setEditing(false);
28
+ };
29
+
30
+ return (
31
+ <>
32
+ {editing ? (
33
+ <ValueEditor
34
+ handleCancel={closeEditor}
35
+ handleSave={(val) => {
36
+ onSave(val);
37
+ closeEditor();
38
+ }}
39
+ onChange={onChange}
40
+ id={id}
41
+ value={value}
42
+ />
43
+ ) : (
44
+ <>
45
+ <h1 className={styles[`${elementType}` + "Label"]}>{value}</h1>
46
+
47
+ <Button
48
+ kind="ghost"
49
+ size="sm"
50
+ enterDelayMs={200}
51
+ iconDescription={t("editButton", "Edit {elementType}", {
52
+ elementType: elementType,
53
+ })}
54
+ onClick={() => setEditing(true)}
55
+ renderIcon={(props) => <Edit size={16} {...props} />}
56
+ hasIconOnly
57
+ />
58
+ </>
59
+ )}
60
+ </>
61
+ );
62
+ };
63
+
64
+ export default EditableValue;
@@ -0,0 +1,23 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ .schemaLabel {
5
+ @include type.type-style('heading-03');
6
+ }
7
+
8
+ .pageLabel {
9
+ @include type.type-style('heading-02');
10
+ margin: 0.5rem 0rem;
11
+
12
+ &:after {
13
+ content: "";
14
+ display: block;
15
+ width: 2rem;
16
+ padding-top: 0.188rem;
17
+ border-bottom: 0.375rem solid var(--brand-03);
18
+ }
19
+ }
20
+
21
+ .sectionLabel {
22
+ @include type.type-style('heading-02');
23
+ }
@@ -0,0 +1,569 @@
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, Replicate, TrashCan } from "@carbon/react/icons";
5
+ import { useParams } from "react-router-dom";
6
+ import { showToast, showNotification } from "@openmrs/esm-framework";
7
+ import { OHRIFormSchema } from "@ohri/openmrs-ohri-form-engine-lib";
8
+
9
+ import { RouteParams, Schema } from "../../types";
10
+ import ActionButtons from "../action-buttons/action-buttons.component";
11
+ import AddQuestionModal from "./add-question-modal.component";
12
+ import DeleteSectionModal from "./delete-section-modal.component";
13
+ import DeletePageModal from "./delete-page-modal.component";
14
+ import DeleteQuestionModal from "./delete-question-modal.component";
15
+ import EditQuestionModal from "./edit-question-modal.component";
16
+ import EditableValue from "./editable-value.component";
17
+ import NewFormModal from "./new-form-modal.component";
18
+ import PageModal from "./page-modal.component";
19
+ import SectionModal from "./section-modal.component";
20
+ import styles from "./interactive-builder.scss";
21
+
22
+ type InteractiveBuilderProps = {
23
+ isLoading: boolean;
24
+ onSchemaChange: (schema: Schema) => void;
25
+ schema: Schema;
26
+ };
27
+
28
+ const InteractiveBuilder: React.FC<InteractiveBuilderProps> = ({
29
+ isLoading,
30
+ onSchemaChange,
31
+ schema,
32
+ }) => {
33
+ const { t } = useTranslation();
34
+ const { formUuid } = useParams<RouteParams>();
35
+ const isEditingExistingForm = !!formUuid;
36
+ const [formName, setFormName] = useState(schema ? schema.name : "");
37
+ const [pageName, setPageName] = useState("");
38
+ const [sectionName, setSectionName] = useState("");
39
+ const [pageIndex, setPageIndex] = useState(0);
40
+ const [sectionIndex, setSectionIndex] = useState(0);
41
+ const [questionIndex, setQuestionIndex] = useState(0);
42
+ const [questionToEdit, setQuestionToEdit] = useState(null);
43
+ const [showNewFormModal, setShowNewFormModal] = useState(false);
44
+ const [showAddPageModal, setShowAddPageModal] = useState(false);
45
+ const [showAddQuestionModal, setShowAddQuestionModal] = useState(false);
46
+ const [showEditQuestionModal, setShowEditQuestionModal] = useState(false);
47
+ const [showAddSectionModal, setShowAddSectionModal] = useState(false);
48
+ const [showDeletePageModal, setShowDeletePageModal] = useState(false);
49
+ const [showDeleteSectionModal, setShowDeleteSectionModal] = useState(false);
50
+ const [showDeleteQuestionModal, setShowDeleteQuestionModal] = useState(false);
51
+
52
+ const initializeSchema = () => {
53
+ const dummySchema: OHRIFormSchema = {
54
+ name: null,
55
+ pages: [],
56
+ processor: "EncounterFormProcessor",
57
+ encounterType: "",
58
+ referencedForms: [],
59
+ uuid: "",
60
+ };
61
+
62
+ if (!schema) {
63
+ onSchemaChange({ ...dummySchema });
64
+ }
65
+ };
66
+
67
+ const launchNewFormModal = () => {
68
+ initializeSchema();
69
+ setShowNewFormModal(true);
70
+ };
71
+
72
+ const resetIndices = () => {
73
+ setPageIndex(0);
74
+ setSectionIndex(0);
75
+ setQuestionIndex(0);
76
+ };
77
+
78
+ const addPage = () => {
79
+ setShowAddPageModal(true);
80
+ };
81
+
82
+ const addSection = () => {
83
+ setShowAddSectionModal(true);
84
+ };
85
+
86
+ const addQuestion = () => {
87
+ setShowAddQuestionModal(true);
88
+ };
89
+
90
+ const editQuestion = () => {
91
+ setShowEditQuestionModal(true);
92
+ };
93
+
94
+ const renameSchema = (value) => {
95
+ try {
96
+ if (value) {
97
+ schema.name = value;
98
+ } else {
99
+ schema.name = formName;
100
+ }
101
+
102
+ onSchemaChange({ ...schema });
103
+
104
+ showToast({
105
+ title: t("success", "Success!"),
106
+ kind: "success",
107
+ critical: true,
108
+ description: t("formRenamed", "Form renamed"),
109
+ });
110
+ } catch (error) {
111
+ showNotification({
112
+ title: t("errorRenamingForm", "Error renaming form"),
113
+ kind: "error",
114
+ critical: true,
115
+ description: error?.message,
116
+ });
117
+ }
118
+ };
119
+
120
+ const renamePage = (name, pageIndex) => {
121
+ try {
122
+ if (name) {
123
+ schema.pages[pageIndex].label = name;
124
+ } else if (pageName) {
125
+ schema.pages[pageIndex].label = pageName;
126
+ }
127
+
128
+ onSchemaChange({ ...schema });
129
+
130
+ showToast({
131
+ title: t("success", "Success!"),
132
+ kind: "success",
133
+ critical: true,
134
+ description: t("pageRenamed", "Page renamed"),
135
+ });
136
+ } catch (error) {
137
+ showNotification({
138
+ title: t("errorRenamingPage", "Error renaming page"),
139
+ kind: "error",
140
+ critical: true,
141
+ description: error?.message,
142
+ });
143
+ }
144
+ };
145
+
146
+ const renameSection = (name, pageIndex, sectionIndex) => {
147
+ try {
148
+ if (name) {
149
+ schema.pages[pageIndex].sections[sectionIndex].label = name;
150
+ }
151
+ onSchemaChange({ ...schema });
152
+
153
+ resetIndices();
154
+
155
+ showToast({
156
+ title: t("success", "Success!"),
157
+ kind: "success",
158
+ critical: true,
159
+ description: t("sectionRenamed", "Section renamed"),
160
+ });
161
+ } catch (error) {
162
+ showNotification({
163
+ title: t("errorRenamingSection", "Error renaming section"),
164
+ kind: "error",
165
+ critical: true,
166
+ description: error?.message,
167
+ });
168
+ }
169
+ };
170
+
171
+ const duplicateQuestion = (question) => {
172
+ try {
173
+ const questionToDuplicate = JSON.parse(JSON.stringify(question));
174
+ questionToDuplicate.id = questionToDuplicate.id + "Duplicate";
175
+
176
+ schema.pages[pageIndex].sections[sectionIndex].questions.push(
177
+ questionToDuplicate
178
+ );
179
+
180
+ onSchemaChange({ ...schema });
181
+ resetIndices();
182
+
183
+ showToast({
184
+ title: t("success", "Success!"),
185
+ kind: "success",
186
+ critical: true,
187
+ description: t("questionDuplicated", "Question duplicated"),
188
+ });
189
+ } catch (error) {
190
+ showNotification({
191
+ title: t("errorDuplicatingQuestion", "Error duplicating question"),
192
+ kind: "error",
193
+ critical: true,
194
+ description: error?.message,
195
+ });
196
+ }
197
+ };
198
+
199
+ return (
200
+ <div className={styles.container}>
201
+ {isLoading ? (
202
+ <InlineLoading
203
+ description={t("loadingSchema", "Loading schema") + "..."}
204
+ />
205
+ ) : null}
206
+
207
+ <ActionButtons schema={schema} t={t} />
208
+
209
+ {showNewFormModal ? (
210
+ <NewFormModal
211
+ schema={schema}
212
+ onSchemaChange={onSchemaChange}
213
+ showModal={showNewFormModal}
214
+ onModalChange={setShowNewFormModal}
215
+ />
216
+ ) : null}
217
+
218
+ {showAddPageModal ? (
219
+ <PageModal
220
+ schema={schema}
221
+ onSchemaChange={onSchemaChange}
222
+ showModal={showAddPageModal}
223
+ onModalChange={setShowAddPageModal}
224
+ />
225
+ ) : null}
226
+
227
+ {showAddSectionModal ? (
228
+ <SectionModal
229
+ schema={schema}
230
+ onSchemaChange={onSchemaChange}
231
+ pageIndex={pageIndex}
232
+ resetIndices={resetIndices}
233
+ showModal={showAddSectionModal}
234
+ onModalChange={setShowAddSectionModal}
235
+ />
236
+ ) : null}
237
+
238
+ {showAddQuestionModal ? (
239
+ <AddQuestionModal
240
+ onModalChange={setShowAddQuestionModal}
241
+ onQuestionEdit={setQuestionToEdit}
242
+ onSchemaChange={onSchemaChange}
243
+ pageIndex={pageIndex}
244
+ sectionIndex={sectionIndex}
245
+ questionIndex={questionIndex}
246
+ questionToEdit={questionToEdit}
247
+ resetIndices={resetIndices}
248
+ schema={schema}
249
+ showModal={showAddQuestionModal}
250
+ />
251
+ ) : null}
252
+
253
+ {showEditQuestionModal ? (
254
+ <EditQuestionModal
255
+ onModalChange={setShowEditQuestionModal}
256
+ onQuestionEdit={setQuestionToEdit}
257
+ onSchemaChange={onSchemaChange}
258
+ pageIndex={pageIndex}
259
+ questionIndex={questionIndex}
260
+ questionToEdit={questionToEdit}
261
+ resetIndices={resetIndices}
262
+ schema={schema}
263
+ sectionIndex={sectionIndex}
264
+ showModal={showEditQuestionModal}
265
+ />
266
+ ) : null}
267
+
268
+ {showDeletePageModal ? (
269
+ <DeletePageModal
270
+ onModalChange={setShowDeletePageModal}
271
+ onSchemaChange={onSchemaChange}
272
+ resetIndices={resetIndices}
273
+ pageIndex={pageIndex}
274
+ schema={schema}
275
+ showModal={showDeletePageModal}
276
+ />
277
+ ) : null}
278
+
279
+ {showDeleteSectionModal ? (
280
+ <DeleteSectionModal
281
+ onModalChange={setShowDeleteSectionModal}
282
+ onSchemaChange={onSchemaChange}
283
+ resetIndices={resetIndices}
284
+ pageIndex={pageIndex}
285
+ sectionIndex={sectionIndex}
286
+ schema={schema}
287
+ showModal={showDeleteSectionModal}
288
+ />
289
+ ) : null}
290
+
291
+ {showDeleteQuestionModal ? (
292
+ <DeleteQuestionModal
293
+ onModalChange={setShowDeleteQuestionModal}
294
+ onSchemaChange={onSchemaChange}
295
+ resetIndices={resetIndices}
296
+ pageIndex={pageIndex}
297
+ sectionIndex={sectionIndex}
298
+ questionIndex={questionIndex}
299
+ schema={schema}
300
+ showModal={showDeleteQuestionModal}
301
+ />
302
+ ) : null}
303
+
304
+ {schema?.name && (
305
+ <>
306
+ <div className={styles.header}>
307
+ <div className={styles.explainer}>
308
+ <p>
309
+ {t(
310
+ "welcomeHeading",
311
+ "Welcome to the Interactive Schema builder"
312
+ )}
313
+ </p>
314
+ <p>
315
+ {t(
316
+ "welcomeExplainer",
317
+ "Add pages, sections and questions to your form. The Preview tab automatically updates as you build your form."
318
+ )}
319
+ </p>
320
+ </div>
321
+ <Button
322
+ kind="primary"
323
+ renderIcon={Add}
324
+ onClick={addPage}
325
+ iconDescription={t("addPage", "Add Page")}
326
+ >
327
+ {t("addPage", "Add Page")}
328
+ </Button>
329
+ </div>
330
+ <div className={styles.editorContainer}>
331
+ <EditableValue
332
+ elementType="schema"
333
+ id="formNameInput"
334
+ value={schema?.name}
335
+ onChange={(event) => setFormName(event.target.value)}
336
+ onSave={(name) => renameSchema(name)}
337
+ />
338
+ </div>
339
+ </>
340
+ )}
341
+
342
+ {!isEditingExistingForm && !schema?.name && (
343
+ <div className={styles.header}>
344
+ <p className={styles.explainer}>
345
+ {t(
346
+ "interactiveBuilderHelperText",
347
+ "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."
348
+ )}
349
+ </p>
350
+
351
+ <Button
352
+ onClick={launchNewFormModal}
353
+ className={styles.startButton}
354
+ kind="primary"
355
+ >
356
+ {t("startBuilding", "Start building")}
357
+ </Button>
358
+ </div>
359
+ )}
360
+
361
+ {schema?.pages?.length
362
+ ? schema.pages.map((page, pageIndex) => (
363
+ <div className={styles.editableFieldsContainer}>
364
+ <div style={{ display: "flex", alignItems: "center" }}>
365
+ <div className={styles.editorContainer}>
366
+ <EditableValue
367
+ elementType="page"
368
+ id="pageNameInput"
369
+ value={schema.pages[pageIndex].label}
370
+ onChange={(event) => setPageName(event.target.value)}
371
+ onSave={(name) => renamePage(name, pageIndex)}
372
+ />
373
+ </div>
374
+ <Button
375
+ hasIconOnly
376
+ enterDelayMs={200}
377
+ iconDescription={t("deletePage", "Delete page")}
378
+ kind="ghost"
379
+ onClick={() => {
380
+ setPageIndex(pageIndex);
381
+ setShowDeletePageModal(true);
382
+ }}
383
+ renderIcon={(props) => <TrashCan size={16} {...props} />}
384
+ size="sm"
385
+ />
386
+ </div>
387
+ <div>
388
+ {page?.sections?.length ? (
389
+ <p className={styles.sectionExplainer}>
390
+ {t(
391
+ "expandSectionExplainer",
392
+ "Below are the sections linked to this page. Expand each section to add questions to it."
393
+ )}
394
+ </p>
395
+ ) : null}
396
+ {page?.sections?.length ? (
397
+ page.sections?.map((section, sectionIndex) => (
398
+ <Accordion>
399
+ <AccordionItem title={section.label}>
400
+ <>
401
+ <div
402
+ style={{ display: "flex", alignItems: "center" }}
403
+ >
404
+ <div className={styles.editorContainer}>
405
+ <EditableValue
406
+ elementType="section"
407
+ id="sectionNameInput"
408
+ value={section.label}
409
+ onChange={(event) =>
410
+ setSectionName(event.target.value)
411
+ }
412
+ onSave={(name) =>
413
+ renameSection(name, pageIndex, sectionIndex)
414
+ }
415
+ />
416
+ </div>
417
+ <Button
418
+ hasIconOnly
419
+ enterDelayMs={200}
420
+ iconDescription={t(
421
+ "deleteSection",
422
+ "Delete section"
423
+ )}
424
+ kind="ghost"
425
+ onClick={() => {
426
+ setPageIndex(pageIndex);
427
+ setSectionIndex(sectionIndex);
428
+ setShowDeleteSectionModal(true);
429
+ }}
430
+ renderIcon={(props) => (
431
+ <TrashCan size={16} {...props} />
432
+ )}
433
+ size="sm"
434
+ />
435
+ </div>
436
+ <div>
437
+ {section.questions?.length ? (
438
+ section.questions.map(
439
+ (question, questionIndex) => (
440
+ <div
441
+ style={{
442
+ display: "flex",
443
+ alignItems: "center",
444
+ }}
445
+ >
446
+ <div className={styles.editorContainer}>
447
+ <p className={styles.questionLabel}>
448
+ {question.label}
449
+ </p>
450
+ <div className={styles.buttonContainer}>
451
+ <Button
452
+ kind="ghost"
453
+ size="sm"
454
+ enterDelayMs={200}
455
+ iconDescription={t(
456
+ "duplicateQuestion",
457
+ "Duplicate question"
458
+ )}
459
+ onClick={() => {
460
+ duplicateQuestion(question);
461
+ setPageIndex(pageIndex);
462
+ setSectionIndex(sectionIndex);
463
+ setQuestionIndex(questionIndex);
464
+ }}
465
+ renderIcon={(props) => (
466
+ <Replicate size={16} {...props} />
467
+ )}
468
+ hasIconOnly
469
+ />
470
+ <Button
471
+ kind="ghost"
472
+ size="sm"
473
+ enterDelayMs={200}
474
+ iconDescription={t(
475
+ "editQuestion",
476
+ "Edit question"
477
+ )}
478
+ onClick={() => {
479
+ editQuestion();
480
+ setPageIndex(pageIndex);
481
+ setSectionIndex(sectionIndex);
482
+ setQuestionIndex(questionIndex);
483
+ setQuestionToEdit(question);
484
+ }}
485
+ renderIcon={(props) => (
486
+ <Edit size={16} {...props} />
487
+ )}
488
+ hasIconOnly
489
+ />
490
+ </div>
491
+ </div>
492
+ <Button
493
+ hasIconOnly
494
+ enterDelayMs={200}
495
+ iconDescription={t(
496
+ "deleteQuestion",
497
+ "Delete question"
498
+ )}
499
+ kind="ghost"
500
+ onClick={() => {
501
+ setPageIndex(pageIndex);
502
+ setSectionIndex(sectionIndex);
503
+ setQuestionIndex(questionIndex);
504
+ setShowDeleteQuestionModal(true);
505
+ }}
506
+ renderIcon={(props) => (
507
+ <TrashCan size={16} {...props} />
508
+ )}
509
+ size="sm"
510
+ />
511
+ </div>
512
+ )
513
+ )
514
+ ) : (
515
+ <p className={styles.explainer}>
516
+ {t(
517
+ "sectionExplainer",
518
+ "A section will typically contain one or more questions. Click the button below to add a question to this section."
519
+ )}
520
+ </p>
521
+ )}
522
+ <Button
523
+ className={styles.addQuestionButton}
524
+ kind="primary"
525
+ renderIcon={Add}
526
+ onClick={() => {
527
+ addQuestion();
528
+ setQuestionIndex(questionIndex);
529
+ setPageIndex(pageIndex);
530
+ setSectionIndex(sectionIndex);
531
+ }}
532
+ iconDescription={t("addQuestion", "Add Question")}
533
+ >
534
+ {t("addQuestion", "Add Question")}
535
+ </Button>
536
+ </div>
537
+ </>
538
+ </AccordionItem>
539
+ </Accordion>
540
+ ))
541
+ ) : (
542
+ <p className={styles.explainer}>
543
+ {t(
544
+ "pageExplainer",
545
+ "Pages typically have one or more sections. Click the button below to add a section to your page."
546
+ )}
547
+ </p>
548
+ )}
549
+ </div>
550
+ <Button
551
+ className={styles.addSectionButton}
552
+ kind="primary"
553
+ renderIcon={Add}
554
+ onClick={() => {
555
+ addSection();
556
+ setPageIndex(pageIndex);
557
+ }}
558
+ iconDescription={t("addSection", "Add Section")}
559
+ >
560
+ {t("addSection", "Add Section")}
561
+ </Button>
562
+ </div>
563
+ ))
564
+ : null}
565
+ </div>
566
+ );
567
+ };
568
+
569
+ export default InteractiveBuilder;