slice-machine-ui 2.2.2-beta.1 → 2.2.2-beta.10

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 (107) hide show
  1. package/next.config.mjs +11 -1
  2. package/out/404.html +1 -1
  3. package/out/_next/static/75dY8fp3JqJwdHRXfu9LV/_buildManifest.js +1 -0
  4. package/out/_next/static/chunks/18539003-7b839478e0b5720a.js +22 -0
  5. package/out/_next/static/chunks/{66-d37d30551288858b.js → 210-565d01ee9777d1a1.js} +2 -2
  6. package/out/_next/static/chunks/{212-cbaa35389c0aacf1.js → 212-b6a2b8d9d16ef3c5.js} +1 -1
  7. package/out/_next/static/chunks/448-a0de8b21d55d4da3.js +1 -0
  8. package/out/_next/static/chunks/{465-47a6d2a5f66f521e.js → 465-d5dd75edd8e6bb65.js} +1 -1
  9. package/out/_next/static/chunks/574-319271e279f042bc.js +1 -0
  10. package/out/_next/static/chunks/59b4e022-8e544a511d670317.js +1 -0
  11. package/out/_next/static/chunks/695-603bd96af31f4302.js +12 -0
  12. package/out/_next/static/chunks/{816-68d4db26c0adab2e.js → 816-17afd84d2642f4c4.js} +1 -1
  13. package/out/_next/static/chunks/f36c6662-1f3a854183168b10.js +1 -0
  14. package/out/_next/static/chunks/pages/_app-d25e4ad1db4fc327.js +544 -0
  15. package/out/_next/static/chunks/pages/{changelog-d309ea186a0e56bf.js → changelog-1d15a7ef498f499a.js} +1 -1
  16. package/out/_next/static/chunks/pages/{changes-1bb1a7b784a22151.js → changes-0af2a1a5244418f9.js} +1 -1
  17. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-74be2c0ac9c854c7.js → [customTypeId]-a6ff1618238b3510.js} +1 -1
  18. package/out/_next/static/chunks/pages/{index-785a0ab6e2a4fc27.js → index-2610e550d79c5bb1.js} +1 -1
  19. package/out/_next/static/chunks/pages/{labs-2e4f3a5f68e65b93.js → labs-240ba57bfdabe998.js} +1 -1
  20. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-d35c87b9aaec445a.js → [pageTypeId]-c300f963db847451.js} +1 -1
  21. package/out/_next/static/chunks/pages/{settings-25eb02893ed871b7.js → settings-039476a9a63d25a1.js} +1 -1
  22. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-b47a72d5e94bd946.js → simulator-cfc19e3e799745bb.js} +1 -1
  23. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-58e6cb0f278ea03c.js +1 -0
  24. package/out/_next/static/chunks/pages/{slices-cc44eeccd58d23a9.js → slices-9894ce968d74baf6.js} +1 -1
  25. package/out/_next/static/css/82c8c4f05b25d3af.css +1 -0
  26. package/out/changelog.html +1 -1
  27. package/out/changes.html +1 -1
  28. package/out/custom-types/[customTypeId].html +1 -1
  29. package/out/custom-types.html +1 -1
  30. package/out/index.html +1 -1
  31. package/out/labs.html +1 -1
  32. package/out/page-types/[pageTypeId].html +1 -1
  33. package/out/settings.html +1 -1
  34. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  35. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  36. package/out/slices.html +1 -1
  37. package/package.json +11 -10
  38. package/src/domain/fields.ts +223 -0
  39. package/src/features/builder/AddFieldDropdown.tsx +119 -0
  40. package/src/features/onboarding/OnboardingGuide.tsx +45 -0
  41. package/src/features/onboarding/OnboardingProgressStepper.tsx +71 -0
  42. package/src/features/onboarding/OnboardingProvider.tsx +82 -0
  43. package/src/features/onboarding/OnboardingStepDialog/OnboardingStepDialog.tsx +46 -0
  44. package/src/features/onboarding/OnboardingStepDialog/OnboardingStepDialogContent.tsx +38 -0
  45. package/src/features/onboarding/OnboardingStepDialog/index.ts +1 -0
  46. package/src/features/onboarding/content.tsx +115 -0
  47. package/src/features/onboarding/types.ts +32 -0
  48. package/src/features/onboarding/useOnboardingExperiment.ts +6 -0
  49. package/src/hooks/useNestedGroupExperiment.ts +8 -0
  50. package/src/hooks/usePersistedState.ts +20 -3
  51. package/src/legacy/components/ListItem/index.tsx +1 -1
  52. package/src/legacy/components/Navigation/index.tsx +2 -0
  53. package/src/legacy/lib/builders/CustomTypeBuilder/TabZone/index.tsx +22 -4
  54. package/src/legacy/lib/builders/SliceBuilder/FieldZones/index.tsx +23 -8
  55. package/src/legacy/lib/builders/SliceBuilder/VariationsList/index.tsx +5 -2
  56. package/src/legacy/lib/builders/SliceBuilder/index.tsx +1 -1
  57. package/src/legacy/lib/builders/common/EditModal/index.jsx +1 -1
  58. package/src/legacy/lib/builders/common/Zone/Card/components/Hints/index.tsx +8 -1
  59. package/src/legacy/lib/builders/common/Zone/Card/components/NewField.tsx +1 -1
  60. package/src/legacy/lib/builders/common/Zone/Card/index.jsx +3 -1
  61. package/src/legacy/lib/builders/common/Zone/components/ZoneEmptyState/ZoneEmptyState.tsx +4 -13
  62. package/src/legacy/lib/builders/common/Zone/index.jsx +26 -31
  63. package/src/legacy/lib/builders/utils/index.ts +8 -0
  64. package/src/legacy/lib/models/common/Group.ts +51 -28
  65. package/src/legacy/lib/models/common/widgets/Boolean/index.ts +2 -4
  66. package/src/legacy/lib/models/common/widgets/Color/index.ts +2 -4
  67. package/src/legacy/lib/models/common/widgets/ContentRelationship/index.ts +0 -2
  68. package/src/legacy/lib/models/common/widgets/Date/index.ts +0 -2
  69. package/src/legacy/lib/models/common/widgets/Embed/index.ts +2 -4
  70. package/src/legacy/lib/models/common/widgets/GeoPoint/index.ts +2 -4
  71. package/src/legacy/lib/models/common/widgets/Group/ListItem/index.jsx +46 -26
  72. package/src/legacy/lib/models/common/widgets/Group/createGroupWidget.ts +81 -0
  73. package/src/legacy/lib/models/common/widgets/Group/index.tsx +72 -0
  74. package/src/legacy/lib/models/common/widgets/Image/index.ts +2 -4
  75. package/src/legacy/lib/models/common/widgets/Link/index.ts +2 -4
  76. package/src/legacy/lib/models/common/widgets/LinkToMedia/index.ts +2 -4
  77. package/src/legacy/lib/models/common/widgets/NestedGroup/index.tsx +40 -0
  78. package/src/legacy/lib/models/common/widgets/Number/index.ts +2 -4
  79. package/src/legacy/lib/models/common/widgets/Select/index.ts +2 -4
  80. package/src/legacy/lib/models/common/widgets/StructuredText/index.ts +0 -2
  81. package/src/legacy/lib/models/common/widgets/Text/index.ts +0 -2
  82. package/src/legacy/lib/models/common/widgets/Timestamp/index.ts +2 -4
  83. package/src/legacy/lib/models/common/widgets/UID/index.ts +2 -4
  84. package/src/legacy/lib/models/common/widgets/Widget.ts +10 -3
  85. package/src/legacy/lib/models/common/widgets/groupWidgets.ts +7 -0
  86. package/src/legacy/lib/models/common/widgets/index.ts +7 -31
  87. package/src/legacy/lib/models/common/widgets/nonGroupWidgets.ts +33 -0
  88. package/test/lib/builders/buildersUtils.test.ts +1 -1
  89. package/test/lib/models/common/widgets/index.test.ts +1 -1
  90. package/out/_next/static/JGphUg6eFq0-W11Pa7cMU/_buildManifest.js +0 -1
  91. package/out/_next/static/chunks/18539003-eda642ad962ef598.js +0 -22
  92. package/out/_next/static/chunks/264-4bb6f3fb14863ef3.js +0 -1
  93. package/out/_next/static/chunks/574-6039c48fbc93b4a8.js +0 -1
  94. package/out/_next/static/chunks/59b4e022-7a3b5554b806349f.js +0 -1
  95. package/out/_next/static/chunks/756-1e44fe84bedc262b.js +0 -10
  96. package/out/_next/static/chunks/f36c6662-03bb5e9d9aa0b0aa.js +0 -1
  97. package/out/_next/static/chunks/pages/_app-05ac13d573829b68.js +0 -532
  98. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-996e20b9b295a912.js +0 -1
  99. package/out/_next/static/css/b3bb731ce567d63f.css +0 -1
  100. package/src/legacy/lib/builders/common/SelectFieldTypeModal/FieldTypeCard.jsx +0 -51
  101. package/src/legacy/lib/builders/common/SelectFieldTypeModal/index.jsx +0 -84
  102. package/src/legacy/lib/models/common/widgets/Group/index.ts +0 -44
  103. package/src/legacy/lib/models/common/widgets/ctBuilderArray.ts +0 -20
  104. package/src/legacy/lib/models/common/widgets/groupBuilderArray.ts +0 -18
  105. package/src/legacy/lib/models/common/widgets/sliceBuilderArray.ts +0 -19
  106. package/src/legacy/lib/models/common/widgets/withGroup.ts +0 -2
  107. /package/out/_next/static/{JGphUg6eFq0-W11Pa7cMU → 75dY8fp3JqJwdHRXfu9LV}/_ssgManifest.js +0 -0
@@ -1,13 +1,14 @@
1
- import { Button, Switch, Text } from "@prismicio/editor-ui";
1
+ import { Switch, Text } from "@prismicio/editor-ui";
2
2
  import { array, arrayOf, bool, func, object, shape, string } from "prop-types";
3
3
  import { useState } from "react";
4
4
  import { BaseStyles } from "theme-ui";
5
5
 
6
6
  import { telemetry } from "@/apiClient";
7
7
  import { ListHeader } from "@/components/List";
8
+ import { fields as allFields } from "@/domain/fields";
9
+ import { AddFieldDropdown } from "@/features/builder/AddFieldDropdown";
8
10
  import { getContentTypeForTracking } from "@/utils/getContentTypeForTracking";
9
11
 
10
- import SelectFieldTypeModal from "../SelectFieldTypeModal";
11
12
  import Card from "./Card";
12
13
  import NewField from "./Card/components/NewField";
13
14
  import { ZoneEmptyState } from "./components/ZoneEmptyState";
@@ -32,7 +33,6 @@ const Zone = ({
32
33
  testId,
33
34
  isRepeatableCustomType,
34
35
  emptyStateHeading,
35
- emptyStateActionTestId,
36
36
  }) => {
37
37
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
38
38
  const widgetsArrayWithCondUid = (() => {
@@ -50,7 +50,6 @@ const Zone = ({
50
50
 
51
51
  const [showHints, setShowHints] = useState(false);
52
52
  const [editModalData, setEditModalData] = useState({ isOpen: false });
53
- const [selectModalData, setSelectModalData] = useState({ isOpen: false });
54
53
  const [newFieldData, setNewFieldData] = useState(null);
55
54
 
56
55
  /** @param {[string, import("@prismicio/types-internal/lib/customtypes").NestableWidget]} field */
@@ -68,22 +67,38 @@ const Zone = ({
68
67
  contentType: getContentTypeForTracking(window.location.pathname),
69
68
  });
70
69
  };
71
- const enterSelectMode = () => {
72
- setSelectModalData({ isOpen: true });
73
- };
74
70
 
75
71
  const closeEditModal = () => {
76
72
  setEditModalData({ isOpen: false });
77
73
  };
78
- const closeSelectModal = () => setSelectModalData({ isOpen: false });
79
74
 
80
75
  const onSelectFieldType = (widgetTypeName) => {
81
76
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment
82
77
  setNewFieldData({ widgetTypeName, fields });
83
- setSelectModalData({ isOpen: false });
84
78
  };
85
79
 
86
80
  const onCancelNewField = () => setNewFieldData(null);
81
+ const addFieldDropdown = (
82
+ <AddFieldDropdown
83
+ disabled={newFieldData !== null}
84
+ onSelectField={onSelectFieldType}
85
+ fields={
86
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
87
+ widgetsArrayWithCondUid.filter(Boolean).map((widget) => {
88
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
89
+ const { TYPE_NAME, CUSTOM_NAME } = widget;
90
+ return allFields.find(
91
+ (f) =>
92
+ f.type === TYPE_NAME &&
93
+ (CUSTOM_NAME === undefined || f.variant === CUSTOM_NAME),
94
+ );
95
+ })
96
+ }
97
+ triggerDataTestId={
98
+ Boolean(isRepeatable) ? "add-field-in-items" : undefined
99
+ }
100
+ />
101
+ );
87
102
 
88
103
  return (
89
104
  <>
@@ -103,18 +118,7 @@ const Zone = ({
103
118
  style={{ flexShrink: 0 }}
104
119
  data-testid="code-snippets-switch"
105
120
  />
106
- <Button
107
- data-testid={`add-${
108
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
109
- isRepeatable ? "Repeatable" : "Static"
110
- }-field`}
111
- onClick={enterSelectMode}
112
- startIcon="add"
113
- color="grey"
114
- disabled={newFieldData !== null}
115
- >
116
- Add a field
117
- </Button>
121
+ {addFieldDropdown}
118
122
  </>
119
123
  ) : undefined
120
124
  }
@@ -129,9 +133,8 @@ const Zone = ({
129
133
  zoneType={getResolvedZoneType(zoneType, zoneTypeFormat)}
130
134
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
131
135
  heading={emptyStateHeading}
132
- onActionClick={() => enterSelectMode()}
133
136
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
134
- actionTestId={emptyStateActionTestId}
137
+ action={addFieldDropdown}
135
138
  />
136
139
  ) : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/strict-boolean-expressions
137
140
  fields.length > 0 || newFieldData ? (
@@ -153,7 +156,6 @@ const Zone = ({
153
156
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment
154
157
  renderHintBase={renderHintBase}
155
158
  enterEditMode={enterEditMode}
156
- enterSelectMode={enterSelectMode}
157
159
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
158
160
  onDragEnd={onDragEnd}
159
161
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -194,13 +196,6 @@ const Zone = ({
194
196
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
195
197
  zoneType={zoneType}
196
198
  />
197
- <SelectFieldTypeModal
198
- data={selectModalData}
199
- close={closeSelectModal}
200
- onSelect={onSelectFieldType}
201
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
202
- widgetsArray={widgetsArrayWithCondUid}
203
- />
204
199
  </>
205
200
  );
206
201
  };
@@ -1,3 +1,5 @@
1
+ import { GroupFieldType } from "@prismicio/types-internal/lib/customtypes/widgets";
2
+
1
3
  import { TabField } from "@/legacy/lib/models/common/CustomType";
2
4
 
3
5
  interface Widgets {
@@ -21,6 +23,12 @@ export const findWidgetByConfigOrType = (
21
23
  return widgets.Link;
22
24
  }
23
25
  }
26
+ // The Group & NestedGroup widgets share the "Group" type. They are never
27
+ // in the same widget group, so this will use whichever is provided. For example,
28
+ // in the custom list item for Group, we always pass NestedGroup as the Group widget.
29
+ if (type === GroupFieldType) {
30
+ return widgets.Group ?? widgets.NestedGroup;
31
+ }
24
32
 
25
33
  return widgets[type];
26
34
  };
@@ -1,32 +1,49 @@
1
- import { Group } from "@prismicio/types-internal/lib/customtypes/widgets";
1
+ import { NestableWidget } from "@prismicio/types-internal/lib/customtypes";
2
+ import {
3
+ Group,
4
+ GroupFieldType,
5
+ } from "@prismicio/types-internal/lib/customtypes/widgets";
2
6
  import { StringOrNull } from "@prismicio/types-internal/lib/validators";
3
7
  import { getOrElseW } from "fp-ts/lib/Either";
4
8
  import * as t from "io-ts";
5
9
 
6
10
  import { FieldsSM } from "./Fields";
7
11
 
8
- export const GroupConfig = t.exact(
9
- t.partial({
10
- label: StringOrNull,
11
- repeat: t.boolean,
12
- fields: FieldsSM,
13
- }),
14
- );
15
- export type GroupConfig = t.TypeOf<typeof GroupConfig>;
16
-
17
- export const GroupSM = t.exact(
18
- t.intersection([
19
- t.type({
20
- type: t.literal("Group"),
21
- }),
12
+ const createGroupConfig = <TFields extends t.Mixed>(fields: TFields) =>
13
+ t.exact(
22
14
  t.partial({
23
- fieldset: StringOrNull,
24
- icon: t.string,
25
- description: t.string,
26
- config: GroupConfig,
15
+ label: StringOrNull,
16
+ repeat: t.boolean,
17
+ fields,
27
18
  }),
28
- ]),
19
+ );
20
+
21
+ const createGroupSM = <TFields extends t.Mixed>(fields: TFields) =>
22
+ t.exact(
23
+ t.intersection([
24
+ t.type({
25
+ type: t.literal("Group"),
26
+ }),
27
+ t.partial({
28
+ fieldset: StringOrNull,
29
+ icon: t.string,
30
+ description: t.string,
31
+ config: createGroupConfig(fields),
32
+ }),
33
+ ]),
34
+ );
35
+
36
+ export const NestedGroupSM = createGroupSM(FieldsSM);
37
+ export type NestedGroupSM = t.TypeOf<typeof NestedGroupSM>;
38
+
39
+ const GroupFieldsSM = t.array(
40
+ t.type({
41
+ key: t.string,
42
+ value: t.union([NestableWidget, NestedGroupSM]),
43
+ }),
29
44
  );
45
+
46
+ export const GroupSM = createGroupSM(GroupFieldsSM);
30
47
  export type GroupSM = t.TypeOf<typeof GroupSM>;
31
48
 
32
49
  export const Groups = {
@@ -34,10 +51,13 @@ export const Groups = {
34
51
  const fields = (() => {
35
52
  if (!group.config?.fields) return;
36
53
 
37
- return group.config.fields.reduce(
38
- (acc, { key, value }) => ({ ...acc, [key]: value }),
39
- {},
40
- );
54
+ return group.config.fields.reduce((acc, { key, value }) => {
55
+ if (value.type === GroupFieldType) {
56
+ return { ...acc, [key]: Groups.fromSM(value) };
57
+ } else {
58
+ return { ...acc, [key]: value };
59
+ }
60
+ }, {});
41
61
  })();
42
62
 
43
63
  return getOrElseW(() => {
@@ -58,10 +78,13 @@ export const Groups = {
58
78
  if (!group.config?.fields) return;
59
79
 
60
80
  // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
61
- return Object.entries(group.config?.fields || {}).map(([key, value]) => ({
62
- key,
63
- value,
64
- }));
81
+ return Object.entries(group.config?.fields || {}).map(([key, value]) => {
82
+ if (value.type === GroupFieldType) {
83
+ return { key, value: Groups.toSM(value) };
84
+ } else {
85
+ return { key, value };
86
+ }
87
+ });
65
88
  })();
66
89
 
67
90
  return getOrElseW(() => {
@@ -1,5 +1,5 @@
1
1
  import { BooleanField } from "@prismicio/types-internal/lib/customtypes/widgets/nestable";
2
- import { BsToggleOn } from "react-icons/bs";
2
+ import { MdOutlineToggleOff } from "react-icons/md";
3
3
  import * as yup from "yup";
4
4
 
5
5
  import { createValidationSchema } from "../../../../forms";
@@ -18,9 +18,7 @@ import { removeProp } from "../../../../utils";
18
18
  import { Widget } from "../Widget";
19
19
 
20
20
  const Meta = {
21
- icon: BsToggleOn,
22
- title: "Boolean",
23
- description: "An input that is either true or false",
21
+ icon: MdOutlineToggleOff,
24
22
  };
25
23
 
26
24
  const FormFields = {
@@ -1,5 +1,5 @@
1
1
  import { Color } from "@prismicio/types-internal/lib/customtypes/widgets/nestable";
2
- import { MdColorLens } from "react-icons/md";
2
+ import { MdOutlineColorLens } from "react-icons/md";
3
3
 
4
4
  import { createDefaultWidgetValues } from "../../../../utils";
5
5
  import { Widget } from "../Widget";
@@ -12,9 +12,7 @@ import { Widget } from "../Widget";
12
12
  } */
13
13
 
14
14
  const Meta = {
15
- icon: MdColorLens,
16
- title: "Color",
17
- description: "A color picker",
15
+ icon: MdOutlineColorLens,
18
16
  };
19
17
 
20
18
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -25,8 +25,6 @@ import Form, { FormFields } from "./Form";
25
25
 
26
26
  const Meta = {
27
27
  icon: MdSettingsEthernet,
28
- title: "Content Relationship",
29
- description: "Define content relations & internal links",
30
28
  };
31
29
 
32
30
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
@@ -17,8 +17,6 @@ const { FormFields, schema } = createDefaultWidgetValues("Date");
17
17
 
18
18
  const Meta = {
19
19
  icon: MdDateRange,
20
- title: "Date",
21
- description: "A calendar date picker",
22
20
  };
23
21
 
24
22
  export const DateWidget: Widget<Date, typeof schema> = {
@@ -1,5 +1,5 @@
1
1
  import { Embed } from "@prismicio/types-internal/lib/customtypes/widgets/nestable";
2
- import { FiCode } from "react-icons/fi";
2
+ import { MdCode } from "react-icons/md";
3
3
 
4
4
  import { createDefaultWidgetValues } from "../../../../utils";
5
5
  import { Widget } from "../Widget";
@@ -16,9 +16,7 @@ import { Widget } from "../Widget";
16
16
  const { FormFields, schema } = createDefaultWidgetValues("Embed");
17
17
 
18
18
  const Meta = {
19
- icon: FiCode,
20
- title: "Embed",
21
- description: "Embed videos, songs, tweets, slides, …",
19
+ icon: MdCode,
22
20
  };
23
21
 
24
22
  export const EmbedWidget: Widget<Embed, typeof schema> = {
@@ -1,5 +1,5 @@
1
1
  import { GeoPoint } from "@prismicio/types-internal/lib/customtypes/widgets/nestable";
2
- import { MdPlace } from "react-icons/md";
2
+ import { MdOutlinePlace } from "react-icons/md";
3
3
  import * as yup from "yup";
4
4
 
5
5
  import { DefaultFields } from "../../../../forms/defaults";
@@ -36,9 +36,7 @@ const schema = yup.object().shape({
36
36
  });
37
37
 
38
38
  const Meta = {
39
- icon: MdPlace,
40
- title: "GeoPoint",
41
- description: "A field for storing geo-coordinates",
39
+ icon: MdOutlinePlace,
42
40
  };
43
41
 
44
42
  export const GeoPointWidget: Widget<GeoPoint, typeof schema> = {
@@ -1,32 +1,35 @@
1
+ import { GroupFieldType } from "@prismicio/types-internal/lib/customtypes/widgets";
1
2
  import { Fragment, useState } from "react";
2
3
  import { DragDropContext, Droppable } from "react-beautiful-dnd";
3
4
  import { flushSync } from "react-dom";
4
- import { Box, Button } from "theme-ui";
5
+ import { Box } from "theme-ui";
5
6
 
6
7
  import { telemetry } from "@/apiClient";
8
+ import { fields as allFields } from "@/domain/fields";
7
9
  import {
8
10
  addFieldToGroup,
9
11
  deleteFieldFromGroup,
10
12
  reorderFieldInGroup,
11
13
  updateFieldInGroup,
12
14
  } from "@/domain/group";
15
+ import { AddFieldDropdown } from "@/features/builder/AddFieldDropdown";
13
16
  import ListItem from "@/legacy/components/ListItem";
14
17
  import EditModal from "@/legacy/lib/builders/common/EditModal";
15
- import SelectFieldTypeModal from "@/legacy/lib/builders/common/SelectFieldTypeModal";
16
18
  import Hint from "@/legacy/lib/builders/common/Zone/Card/components/Hints";
17
19
  import NewField from "@/legacy/lib/builders/common/Zone/Card/components/NewField";
18
20
  import { findWidgetByConfigOrType } from "@/legacy/lib/builders/utils";
19
21
  import { Groups } from "@/legacy/lib/models/common/Group";
20
- import * as Widgets from "@/legacy/lib/models/common/widgets";
21
- import groupBuilderWidgetsArray from "@/legacy/lib/models/common/widgets/groupBuilderArray";
22
22
  import { ensureDnDDestination } from "@/legacy/lib/utils";
23
23
  import { transformKeyAccessor } from "@/legacy/lib/utils/str";
24
24
  import { getContentTypeForTracking } from "@/utils/getContentTypeForTracking";
25
25
 
26
26
  /* eslint-disable */
27
- const CustomListItem = ({
27
+ export const CustomListItem = ({
28
28
  tabId,
29
29
  widget,
30
+ Widgets,
31
+ widgetsArray,
32
+ hintBase,
30
33
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
34
  parentSnapshot,
32
35
  showHints,
@@ -37,14 +40,12 @@ const CustomListItem = ({
37
40
  HintElement,
38
41
  ...rest
39
42
  }) => {
40
- const [selectMode, setSelectMode] = useState(false);
41
43
  const [newFieldData, setNewFieldData] = useState(null);
42
44
  const [editModalData, setEditModalData] = useState({ isOpen: false });
43
45
 
44
46
  const onSelectFieldType = (widgetTypeName) => {
45
47
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
46
48
  setNewFieldData({ widgetTypeName });
47
- setSelectMode(false);
48
49
  };
49
50
 
50
51
  const onCancelNewField = () => {
@@ -86,7 +87,7 @@ const CustomListItem = ({
86
87
  group: Groups.fromSM(groupItem.value),
87
88
  previousFieldId: previousKey,
88
89
  newFieldId: newKey,
89
- field: value,
90
+ field: value.type === GroupFieldType ? Groups.fromSM(value) : value,
90
91
  });
91
92
 
92
93
  saveItem({
@@ -169,15 +170,28 @@ const CustomListItem = ({
169
170
  {...rest}
170
171
  HintElement={HintElement}
171
172
  CustomEditElements={[
172
- <Button
173
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
174
- key={`custom-edit-element-${groupItem.key}`}
175
- mr={2}
176
- variant="buttons.darkSmall"
177
- onClick={() => setSelectMode(true)}
178
- >
179
- Add Field
180
- </Button>,
173
+ <AddFieldDropdown
174
+ key="add-field-dropdown"
175
+ disabled={newFieldData !== null}
176
+ onSelectField={onSelectFieldType}
177
+ fields={widgetsArray.filter(Boolean).map((widget) => {
178
+ const { TYPE_NAME, CUSTOM_NAME } = widget;
179
+
180
+ const field = allFields.find(
181
+ (f) =>
182
+ f.type === TYPE_NAME &&
183
+ (CUSTOM_NAME === undefined || f.variant === CUSTOM_NAME),
184
+ );
185
+
186
+ if (!field) {
187
+ throw new Error(
188
+ `Field not found for widget: ${TYPE_NAME} - ${CUSTOM_NAME}`,
189
+ );
190
+ }
191
+
192
+ return field;
193
+ })}
194
+ />,
181
195
  ]}
182
196
  children={
183
197
  <Box sx={{ ml: 4 }}>
@@ -215,6 +229,8 @@ const CustomListItem = ({
215
229
  key: item.key,
216
230
  enterEditMode,
217
231
  deleteItem: onDeleteItem,
232
+ saveItem: onSaveField,
233
+ showHints,
218
234
  renderFieldAccessor: (key) =>
219
235
  `item${transformKeyAccessor(item.key)}`,
220
236
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions
@@ -232,13 +248,25 @@ const CustomListItem = ({
232
248
  isRepeatable={isRepeatable}
233
249
  renderHintBase={({ item }) =>
234
250
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
235
- `item${transformKeyAccessor(item.key)}`
251
+ `${hintBase}${transformKeyAccessor(item.key)}`
236
252
  }
253
+ hintItemName={widget.hintItemName}
237
254
  Widgets={Widgets}
238
255
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
239
256
  typeName={widget.CUSTOM_NAME || widget.TYPE_NAME}
240
257
  />
241
258
  );
259
+
260
+ if (widget.CustomListItem) {
261
+ const { CustomListItem } = widget;
262
+ return (
263
+ <CustomListItem
264
+ {...props}
265
+ HintElement={HintElement}
266
+ />
267
+ );
268
+ }
269
+
242
270
  return (
243
271
  <ListItem {...props} HintElement={HintElement} />
244
272
  );
@@ -266,12 +294,6 @@ const CustomListItem = ({
266
294
  </Box>
267
295
  }
268
296
  />
269
- <SelectFieldTypeModal
270
- data={{ isOpen: selectMode }}
271
- close={() => setSelectMode(false)}
272
- onSelect={onSelectFieldType}
273
- widgetsArray={groupBuilderWidgetsArray}
274
- />
275
297
  <EditModal
276
298
  data={editModalData}
277
299
  close={closeEditModal}
@@ -282,6 +304,4 @@ const CustomListItem = ({
282
304
  </Fragment>
283
305
  );
284
306
  };
285
-
286
- export default CustomListItem;
287
307
  /* eslint-enable */
@@ -0,0 +1,81 @@
1
+ import { MdOutlineCreateNewFolder } from "react-icons/md";
2
+ import * as yup from "yup";
3
+
4
+ import { TabField } from "@/legacy/lib/models/common/CustomType";
5
+ import {
6
+ type GroupSM,
7
+ type NestedGroupSM,
8
+ } from "@/legacy/lib/models/common/Group";
9
+ import { type Widget } from "@/legacy/lib/models/common/widgets/Widget";
10
+
11
+ import { type GroupListItemProps } from ".";
12
+ import Form, { FormFields } from "./Form";
13
+
14
+ const Meta = {
15
+ icon: MdOutlineCreateNewFolder,
16
+ };
17
+
18
+ const createSchema = (typeRegex: RegExp) =>
19
+ yup.object().shape({
20
+ type: yup
21
+ .string()
22
+ .matches(typeRegex, { excludeEmptyString: true })
23
+ .required(),
24
+ config: yup.object().shape({
25
+ fields: yup.array(),
26
+ label: yup.string(),
27
+ placeholder: yup.string(),
28
+ repeat: yup.boolean().optional(),
29
+ }),
30
+ });
31
+ export type SchemaType = ReturnType<typeof createSchema>;
32
+
33
+ type CreateGroupWidgetArgsBase<T extends TabField> = {
34
+ schemaTypeRegex: RegExp;
35
+ customListItem: (props: GroupListItemProps<T>) => JSX.Element;
36
+ hintItemName: string;
37
+ customName?: string;
38
+ };
39
+ type CreateTopGroupWidgetArgs = CreateGroupWidgetArgsBase<GroupSM>;
40
+ type CreateNestedGroupWidgetArgs = CreateGroupWidgetArgsBase<NestedGroupSM> & {
41
+ customName: "NestedGroup";
42
+ };
43
+ type CreateGroupWidgetArgs =
44
+ | CreateTopGroupWidgetArgs
45
+ | CreateNestedGroupWidgetArgs;
46
+
47
+ export function createGroupWidget(
48
+ args: CreateTopGroupWidgetArgs,
49
+ ): Widget<GroupSM, SchemaType>;
50
+
51
+ export function createGroupWidget(
52
+ args: CreateNestedGroupWidgetArgs,
53
+ ): Widget<NestedGroupSM, SchemaType>;
54
+
55
+ export function createGroupWidget({
56
+ schemaTypeRegex,
57
+ customListItem,
58
+ customName,
59
+ hintItemName,
60
+ }: CreateGroupWidgetArgs):
61
+ | Widget<GroupSM, SchemaType>
62
+ | Widget<NestedGroupSM, SchemaType> {
63
+ return {
64
+ Meta,
65
+ Form,
66
+ FormFields,
67
+ schema: createSchema(schemaTypeRegex),
68
+ create: (label: string) => ({
69
+ type: "Group",
70
+ config: {
71
+ label,
72
+ repeat: true,
73
+ fields: [],
74
+ },
75
+ }),
76
+ CustomListItem: customListItem,
77
+ TYPE_NAME: "Group",
78
+ CUSTOM_NAME: customName,
79
+ hintItemName,
80
+ };
81
+ }
@@ -0,0 +1,72 @@
1
+ import { type DroppableStateSnapshot } from "react-beautiful-dnd";
2
+
3
+ import { useNestedGroupExperiment } from "@/hooks/useNestedGroupExperiment";
4
+ import { type Item } from "@/legacy/components/ListItem";
5
+ import { type TabField } from "@/legacy/lib/models/common/CustomType";
6
+ import { type GroupSM } from "@/legacy/lib/models/common/Group";
7
+ import { Widgets } from "@/legacy/lib/models/common/widgets/groupWidgets";
8
+ import { type Widget } from "@/legacy/lib/models/common/widgets/Widget";
9
+
10
+ import { createGroupWidget, type SchemaType } from "./createGroupWidget";
11
+ import { CustomListItem } from "./ListItem";
12
+
13
+ const widgetsArray = [
14
+ Widgets.Image,
15
+ Widgets.Text,
16
+ Widgets.StructuredText,
17
+ Widgets.Link,
18
+ Widgets.Select,
19
+ Widgets.Boolean,
20
+ Widgets.Number,
21
+ Widgets.Color,
22
+ Widgets.Date,
23
+ Widgets.Embed,
24
+ Widgets.Timestamp,
25
+ Widgets.GeoPoint,
26
+ Widgets.ContentRelationship,
27
+ Widgets.LinkToMedia,
28
+ Widgets.NestedGroup,
29
+ ];
30
+
31
+ export interface GroupListItemProps<F extends TabField> {
32
+ tabId: string;
33
+ widget: Widget<F, SchemaType>;
34
+ parentSnapshot: DroppableStateSnapshot;
35
+ showHints: boolean;
36
+ isRepeatable: boolean;
37
+ item: Item<F>;
38
+ draggableId: string;
39
+ saveItem: ({
40
+ apiId,
41
+ newKey,
42
+ value,
43
+ }: {
44
+ apiId: string;
45
+ newKey: string;
46
+ value: F;
47
+ }) => void;
48
+ HintElement: JSX.Element;
49
+ }
50
+
51
+ const hintItemName = "item";
52
+
53
+ const GroupListItem = (props: GroupListItemProps<GroupSM>): JSX.Element => {
54
+ const nestedGroupExperiment = useNestedGroupExperiment();
55
+ const maybeFilteredWidgetsArray = nestedGroupExperiment.eligible
56
+ ? widgetsArray
57
+ : widgetsArray.filter((widget) => widget.CUSTOM_NAME !== "NestedGroup");
58
+ return (
59
+ <CustomListItem
60
+ Widgets={Widgets}
61
+ widgetsArray={maybeFilteredWidgetsArray}
62
+ hintBase={hintItemName}
63
+ {...props}
64
+ />
65
+ );
66
+ };
67
+
68
+ export const GroupWidget = createGroupWidget({
69
+ schemaTypeRegex: /^Group$/,
70
+ customListItem: GroupListItem,
71
+ hintItemName,
72
+ });
@@ -1,5 +1,5 @@
1
1
  import { Image } from "@prismicio/types-internal/lib/customtypes/widgets/nestable";
2
- import { BsImage } from "react-icons/bs";
2
+ import { MdOutlineImage } from "react-icons/md";
3
3
  import * as yup from "yup";
4
4
 
5
5
  import { createValidationSchema } from "../../../../forms";
@@ -38,9 +38,7 @@ const schema = yup.object().shape({
38
38
  });
39
39
 
40
40
  const Meta = {
41
- icon: BsImage,
42
- title: "Image",
43
- description: "A responsive image field with constraints",
41
+ icon: MdOutlineImage,
44
42
  };
45
43
 
46
44
  export const ImageWidget: Widget<Image, typeof schema> = {