slice-machine-ui 2.2.2-beta.8 → 2.3.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 (95) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{18539003-4c4fdc1584f243b4.js → 18539003-afe9213a677bd9f0.js} +3 -3
  3. package/out/_next/static/chunks/212-b6a2b8d9d16ef3c5.js +1 -0
  4. package/out/_next/static/chunks/{210-565d01ee9777d1a1.js → 326-2d6d04d623de16c6.js} +2 -2
  5. package/out/_next/static/chunks/448-ae7b5e576a84b47d.js +1 -0
  6. package/out/_next/static/chunks/465-d5dd75edd8e6bb65.js +1 -0
  7. package/out/_next/static/chunks/574-a23212bd78e563de.js +1 -0
  8. package/out/_next/static/chunks/59b4e022-8e544a511d670317.js +1 -0
  9. package/out/_next/static/chunks/{756-0b198fb40a3e6ae2.js → 695-bfe7255199983590.js} +6 -4
  10. package/out/_next/static/chunks/816-17afd84d2642f4c4.js +1 -0
  11. package/out/_next/static/chunks/f36c6662-1f3a854183168b10.js +1 -0
  12. package/out/_next/static/chunks/pages/_app-07a2573662865a6b.js +547 -0
  13. package/out/_next/static/chunks/pages/changelog-1d15a7ef498f499a.js +1 -0
  14. package/out/_next/static/chunks/pages/changes-0af2a1a5244418f9.js +1 -0
  15. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-a6ff1618238b3510.js → [customTypeId]-b714ad9814898d23.js} +1 -1
  16. package/out/_next/static/chunks/pages/index-2610e550d79c5bb1.js +1 -0
  17. package/out/_next/static/chunks/pages/labs-240ba57bfdabe998.js +1 -0
  18. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-c300f963db847451.js → [pageTypeId]-aa881786f6e5aee2.js} +1 -1
  19. package/out/_next/static/chunks/pages/settings-039476a9a63d25a1.js +1 -0
  20. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-cfc19e3e799745bb.js +1 -0
  21. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-58e6cb0f278ea03c.js +1 -0
  22. package/out/_next/static/chunks/pages/slices-9894ce968d74baf6.js +1 -0
  23. package/out/_next/static/css/447984631bacb88c.css +1 -0
  24. package/out/_next/static/yi8ItcVBmEtxCSvLChc0b/_buildManifest.js +1 -0
  25. package/out/changelog.html +1 -1
  26. package/out/changes.html +1 -1
  27. package/out/custom-types/[customTypeId].html +1 -1
  28. package/out/custom-types.html +1 -1
  29. package/out/index.html +1 -1
  30. package/out/labs.html +1 -1
  31. package/out/page-types/[pageTypeId].html +1 -1
  32. package/out/settings.html +1 -1
  33. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  34. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  35. package/out/slices.html +1 -1
  36. package/package.json +9 -9
  37. package/src/domain/fields.ts +223 -0
  38. package/src/features/builder/AddFieldDropdown.tsx +122 -0
  39. package/src/features/onboarding/OnboardingGuide.tsx +45 -0
  40. package/src/features/onboarding/OnboardingProgressStepper.tsx +71 -0
  41. package/src/features/onboarding/OnboardingProvider.tsx +82 -0
  42. package/src/features/onboarding/OnboardingStepDialog/OnboardingStepDialog.tsx +46 -0
  43. package/src/features/onboarding/OnboardingStepDialog/OnboardingStepDialogContent.tsx +38 -0
  44. package/src/features/onboarding/OnboardingStepDialog/index.ts +1 -0
  45. package/src/features/onboarding/content.tsx +99 -0
  46. package/src/features/onboarding/types.ts +32 -0
  47. package/src/features/onboarding/useOnboardingExperiment.ts +6 -0
  48. package/src/hooks/usePersistedState.ts +20 -3
  49. package/src/legacy/components/Navigation/index.tsx +2 -0
  50. package/src/legacy/lib/builders/CustomTypeBuilder/TabZone/index.tsx +9 -9
  51. package/src/legacy/lib/builders/SliceBuilder/FieldZones/index.tsx +7 -9
  52. package/src/legacy/lib/builders/SliceBuilder/VariationsList/index.tsx +1 -0
  53. package/src/legacy/lib/builders/common/Zone/components/ZoneEmptyState/ZoneEmptyState.tsx +4 -13
  54. package/src/legacy/lib/builders/common/Zone/index.jsx +26 -31
  55. package/src/legacy/lib/models/common/widgets/Boolean/index.ts +2 -4
  56. package/src/legacy/lib/models/common/widgets/Color/index.ts +2 -4
  57. package/src/legacy/lib/models/common/widgets/ContentRelationship/index.ts +0 -2
  58. package/src/legacy/lib/models/common/widgets/Date/index.ts +0 -2
  59. package/src/legacy/lib/models/common/widgets/Embed/index.ts +2 -4
  60. package/src/legacy/lib/models/common/widgets/GeoPoint/index.ts +2 -4
  61. package/src/legacy/lib/models/common/widgets/Group/ListItem/index.jsx +25 -19
  62. package/src/legacy/lib/models/common/widgets/Group/createGroupWidget.ts +2 -4
  63. package/src/legacy/lib/models/common/widgets/Group/index.tsx +8 -8
  64. package/src/legacy/lib/models/common/widgets/Image/index.ts +2 -4
  65. package/src/legacy/lib/models/common/widgets/Link/index.ts +2 -4
  66. package/src/legacy/lib/models/common/widgets/LinkToMedia/index.ts +2 -4
  67. package/src/legacy/lib/models/common/widgets/NestedGroup/index.tsx +7 -7
  68. package/src/legacy/lib/models/common/widgets/Number/index.ts +2 -4
  69. package/src/legacy/lib/models/common/widgets/Select/index.ts +2 -4
  70. package/src/legacy/lib/models/common/widgets/StructuredText/index.ts +0 -2
  71. package/src/legacy/lib/models/common/widgets/Text/index.ts +0 -2
  72. package/src/legacy/lib/models/common/widgets/Timestamp/index.ts +2 -4
  73. package/src/legacy/lib/models/common/widgets/UID/index.ts +2 -4
  74. package/src/legacy/lib/models/common/widgets/Widget.ts +0 -2
  75. package/out/_next/static/8ioxR-A_Nfflu3QxG8paB/_buildManifest.js +0 -1
  76. package/out/_next/static/chunks/212-58a99ab312f8a03b.js +0 -1
  77. package/out/_next/static/chunks/448-7a6843206737d199.js +0 -1
  78. package/out/_next/static/chunks/465-695ac5fedef986eb.js +0 -1
  79. package/out/_next/static/chunks/574-dfeb0e6aa3079da1.js +0 -1
  80. package/out/_next/static/chunks/59b4e022-7a3b5554b806349f.js +0 -1
  81. package/out/_next/static/chunks/816-96fc9a79308019c7.js +0 -1
  82. package/out/_next/static/chunks/f36c6662-03bb5e9d9aa0b0aa.js +0 -1
  83. package/out/_next/static/chunks/pages/_app-0f7eec1bd89de90c.js +0 -536
  84. package/out/_next/static/chunks/pages/changelog-f0cd26a9ae095e9e.js +0 -1
  85. package/out/_next/static/chunks/pages/changes-1a57f90433a0b5e2.js +0 -1
  86. package/out/_next/static/chunks/pages/index-a0530bd55c7e1872.js +0 -1
  87. package/out/_next/static/chunks/pages/labs-7b0abeef69494aec.js +0 -1
  88. package/out/_next/static/chunks/pages/settings-f82286dde10f624c.js +0 -1
  89. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-453f946e8879c829.js +0 -1
  90. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-b530a5e0ac9d0391.js +0 -1
  91. package/out/_next/static/chunks/pages/slices-7411822d74f1f6c1.js +0 -1
  92. package/out/_next/static/css/ebf74ed654df9202.css +0 -1
  93. package/src/legacy/lib/builders/common/SelectFieldTypeModal/FieldTypeCard.jsx +0 -51
  94. package/src/legacy/lib/builders/common/SelectFieldTypeModal/index.jsx +0 -84
  95. /package/out/_next/static/{8ioxR-A_Nfflu3QxG8paB → yi8ItcVBmEtxCSvLChc0b}/_ssgManifest.js +0 -0
@@ -0,0 +1,99 @@
1
+ import { Text } from "@prismicio/editor-ui";
2
+
3
+ import type {
4
+ OnboardingStep,
5
+ OnboardingStepContentDefinition,
6
+ OnboardingStepType,
7
+ } from "@/features/onboarding/types";
8
+
9
+ export const onboardingSteps: OnboardingStep[] = [
10
+ {
11
+ id: "addSlice",
12
+ title: "Add Slices",
13
+ description: "Add slices to your page type.",
14
+ },
15
+ {
16
+ id: "codePage",
17
+ title: "Code your page",
18
+ description: "Build your page's structure",
19
+ },
20
+ {
21
+ id: "pushModels",
22
+ title: "Push Your Page",
23
+ description: "Push to your Page Builder.",
24
+ },
25
+ {
26
+ id: "createContent",
27
+ title: "Create content",
28
+ description: "Create engaging content.",
29
+ },
30
+ {
31
+ id: "renderPage",
32
+ title: "Render your page",
33
+ description: "Render your page on localhost.",
34
+ },
35
+ ];
36
+
37
+ type GetOnboardingStepsContentProps = {
38
+ repositoryUrl: string;
39
+ };
40
+
41
+ export const getOnboardingStepsContent = ({
42
+ repositoryUrl,
43
+ }: GetOnboardingStepsContentProps): Record<
44
+ OnboardingStepType,
45
+ OnboardingStepContentDefinition
46
+ > => ({
47
+ addSlice: {
48
+ videoUrl:
49
+ "https://res.cloudinary.com/dmtf1daqp/video/upload/v1700213517/IN-APP-GUIDE-SM/ADD_SLICE.mp4",
50
+ content: () => (
51
+ <Text>Use slice templates and add them to your page type.</Text>
52
+ ),
53
+ },
54
+ codePage: {
55
+ videoUrl:
56
+ "https://res.cloudinary.com/dmtf1daqp/video/upload/v1700213517/IN-APP-GUIDE-SM/CODE_SNIP.mp4",
57
+ content: () => (
58
+ <Text>
59
+ If you don't already have a page component, copy-paste the page snippets
60
+ provided in your page type to create one.
61
+ </Text>
62
+ ),
63
+ },
64
+ pushModels: {
65
+ videoUrl:
66
+ "https://res.cloudinary.com/dmtf1daqp/video/upload/v1700213517/IN-APP-GUIDE-SM/PUSH.mp4",
67
+ content: () => (
68
+ <Text>
69
+ You have just created some models, but you can't use them yet. First,
70
+ you must push them to the Page Builder. The Page Builder is where you
71
+ create content. Go head — push your models.
72
+ </Text>
73
+ ),
74
+ },
75
+ createContent: {
76
+ videoUrl:
77
+ "https://res.cloudinary.com/dmtf1daqp/video/upload/v1700213517/IN-APP-GUIDE-SM/PREVIEW.mp4",
78
+ content: () => (
79
+ <Text>
80
+ Open your{" "}
81
+ <Text href={repositoryUrl} underline>
82
+ Page Builder
83
+ </Text>
84
+ , create a page, add slices, save, and publish. Then, come back here.
85
+ </Text>
86
+ ),
87
+ },
88
+ renderPage: {
89
+ videoUrl:
90
+ "https://res.cloudinary.com/dmtf1daqp/video/upload/v1700213517/IN-APP-GUIDE-SM/WRITE.mp4",
91
+ content: () => (
92
+ <Text>
93
+ To render the page, run your project in your terminal and visit the page
94
+ on localhost (e.g.{" "}
95
+ <Text component="code">localhost:3000/example-page</Text>).
96
+ </Text>
97
+ ),
98
+ },
99
+ });
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+
3
+ export const onboardingStepStatusesSchema = z.object({
4
+ addSlice: z.boolean(),
5
+ codePage: z.boolean(),
6
+ pushModels: z.boolean(),
7
+ createContent: z.boolean(),
8
+ renderPage: z.boolean(),
9
+ });
10
+
11
+ export type OnboardingStepStatuses = z.infer<
12
+ typeof onboardingStepStatusesSchema
13
+ >;
14
+
15
+ export type OnboardingStepType = keyof OnboardingStepStatuses;
16
+
17
+ export type OnboardingStep = {
18
+ id: OnboardingStepType;
19
+ title: string;
20
+ description: string;
21
+ };
22
+
23
+ export type OnboardingStepContentDefinition = {
24
+ title?: string;
25
+ content: () => JSX.Element;
26
+ videoUrl: string;
27
+ };
28
+
29
+ export type OnboardingStepContent = Record<
30
+ OnboardingStepType,
31
+ OnboardingStepContentDefinition
32
+ >;
@@ -0,0 +1,6 @@
1
+ import { useExperimentVariant } from "@/hooks/useExperimentVariant";
2
+
3
+ export const useOnboardingExperiment = () => {
4
+ const variant = useExperimentVariant("slice-machine-onboarding");
5
+ return { eligible: variant?.value === "on" };
6
+ };
@@ -1,20 +1,37 @@
1
1
  import { Dispatch, SetStateAction, useEffect, useState } from "react";
2
+ import { ZodType, ZodTypeDef } from "zod";
2
3
 
3
4
  type PersistedState<T> = [T, Dispatch<SetStateAction<T>>];
4
5
 
5
6
  const SLICE_MACHINE_STORAGE_PREFIX = "slice-machine";
6
7
 
8
+ type UsePersistedStateOptions<T> = {
9
+ schema?: ZodType<T, ZodTypeDef, unknown>;
10
+ };
11
+
12
+ export function usePersistedState<T>(
13
+ key: string,
14
+ ): PersistedState<T | undefined>;
7
15
  export function usePersistedState<T>(
8
16
  key: string,
9
17
  defaultValue: T,
10
- ): PersistedState<T> {
18
+ options?: UsePersistedStateOptions<T>,
19
+ ): PersistedState<T>;
20
+ export function usePersistedState<T>(
21
+ key: string,
22
+ defaultValue?: T,
23
+ options?: UsePersistedStateOptions<T>,
24
+ ): PersistedState<T | undefined> {
25
+ const { schema } = options ?? {};
11
26
  const computedKey = `${SLICE_MACHINE_STORAGE_PREFIX}_${key}`;
12
27
 
13
- const [value, setValue] = useState<T>(() => {
28
+ const [value, setValue] = useState<T | undefined>(() => {
14
29
  try {
15
30
  const value = localStorage.getItem(computedKey);
16
31
 
17
- return value !== null ? (JSON.parse(value) as T) : defaultValue;
32
+ if (value == null) return defaultValue;
33
+ if (!schema) return JSON.parse(value) as T;
34
+ return schema.parse(JSON.parse(value));
18
35
  } catch (error) {
19
36
  console.warn(`Error reading localStorage key “${computedKey}”:`, error);
20
37
 
@@ -17,6 +17,7 @@ import { ErrorBoundary } from "@/ErrorBoundary";
17
17
  import { CUSTOM_TYPES_CONFIG } from "@/features/customTypes/customTypesConfig";
18
18
  import { CUSTOM_TYPES_MESSAGES } from "@/features/customTypes/customTypesMessages";
19
19
  import { MasterSliceLibraryPreviewModal } from "@/features/masterSliceLibrary/SliceLibraryPreviewModal";
20
+ import { OnboardingGuide } from "@/features/onboarding/OnboardingGuide";
20
21
  import { useGitIntegrationExperiment } from "@/features/settings/git/useGitIntegrationExperiment";
21
22
  import { useMarketingContent } from "@/hooks/useMarketingContent";
22
23
  import { useRepositoryInformation } from "@/hooks/useRepositoryInformation";
@@ -123,6 +124,7 @@ const Navigation: FC = () => {
123
124
  </ErrorBoundary>
124
125
 
125
126
  <SideNavList position="bottom">
127
+ <OnboardingGuide />
126
128
  {masterSliceLibrary !== undefined && (
127
129
  <SideNavListItem>
128
130
  <MasterSliceLibraryPreviewModal
@@ -45,21 +45,22 @@ import SliceZone from "../SliceZone";
45
45
 
46
46
  const widgetsArray = [
47
47
  Widgets.UID,
48
- Widgets.Group,
49
- Widgets.StructuredText,
50
48
  Widgets.Image,
49
+ Widgets.Text,
50
+ Widgets.StructuredText,
51
51
  Widgets.Link,
52
- Widgets.LinkToMedia,
53
- Widgets.ContentRelationship,
54
52
  Widgets.Select,
55
53
  Widgets.Boolean,
54
+ Widgets.Number,
55
+ Widgets.Color,
56
56
  Widgets.Date,
57
- Widgets.Timestamp,
58
57
  Widgets.Embed,
59
- Widgets.Number,
58
+ Widgets.Timestamp,
60
59
  Widgets.GeoPoint,
61
- Widgets.Color,
62
- Widgets.Text,
60
+ Widgets.ContentRelationship,
61
+ Widgets.LinkToMedia,
62
+ Widgets.Group,
63
+ Widgets.NestedGroup,
63
64
  ];
64
65
 
65
66
  interface TabZoneProps {
@@ -260,7 +261,6 @@ const TabZone: FC<TabZoneProps> = ({ tabId }) => {
260
261
  }
261
262
  testId="static-zone-content"
262
263
  isRepeatableCustomType={customType.repeatable}
263
- emptyStateActionTestId="add-Static-field"
264
264
  />
265
265
  ) : undefined}
266
266
 
@@ -41,20 +41,20 @@ const dataTipText2 = `The repeatable zone is for a group<br/>
41
41
  indeterminate number of times, like FAQs`;
42
42
 
43
43
  const itemsWidgetsArray = [
44
- Widgets.StructuredText,
45
44
  Widgets.Image,
45
+ Widgets.Text,
46
+ Widgets.StructuredText,
46
47
  Widgets.Link,
47
- Widgets.LinkToMedia,
48
- Widgets.ContentRelationship,
49
48
  Widgets.Select,
50
49
  Widgets.Boolean,
50
+ Widgets.Number,
51
+ Widgets.Color,
51
52
  Widgets.Date,
52
- Widgets.Timestamp,
53
53
  Widgets.Embed,
54
- Widgets.Number,
54
+ Widgets.Timestamp,
55
55
  Widgets.GeoPoint,
56
- Widgets.Color,
57
- Widgets.Text,
56
+ Widgets.ContentRelationship,
57
+ Widgets.LinkToMedia,
58
58
  ];
59
59
 
60
60
  const primaryWidgetsArray = [Widgets.Group, ...itemsWidgetsArray];
@@ -223,7 +223,6 @@ const FieldZones: FC = () => {
223
223
  testId="static-zone-content"
224
224
  isRepeatableCustomType={undefined}
225
225
  emptyStateHeading={undefined}
226
- emptyStateActionTestId="add-Static-field"
227
226
  />
228
227
  {hasItems ? (
229
228
  <Zone
@@ -252,7 +251,6 @@ const FieldZones: FC = () => {
252
251
  testId="slice-repeatable-zone"
253
252
  isRepeatableCustomType={undefined}
254
253
  emptyStateHeading="No fields"
255
- emptyStateActionTestId="add-Repeatable-field"
256
254
  />
257
255
  ) : null}
258
256
  <Dialog
@@ -205,6 +205,7 @@ export const SharedSliceCards = (props: SharedSliceCardsProps) => {
205
205
  <>
206
206
  {slice.model.variations.map((v) => (
207
207
  <div
208
+ key={v.id}
208
209
  style={
209
210
  width !== undefined
210
211
  ? { ...defaultCardStyle, width }
@@ -1,4 +1,4 @@
1
- import { Button, Text } from "@prismicio/editor-ui";
1
+ import { Text } from "@prismicio/editor-ui";
2
2
  import { FC, ReactNode } from "react";
3
3
 
4
4
  import styles from "./ZoneEmptyState.module.css";
@@ -6,16 +6,14 @@ import styles from "./ZoneEmptyState.module.css";
6
6
  type ZoneEmptyStateProps = {
7
7
  zoneType: "custom type" | "page type" | "slice";
8
8
  heading?: ReactNode;
9
- onActionClick: () => void;
10
- actionTestId?: string;
9
+ action: ReactNode;
11
10
  };
12
11
 
13
12
  export const ZoneEmptyState: FC<ZoneEmptyStateProps> = (props) => {
14
13
  const {
15
14
  zoneType,
16
15
  heading = `Your ${zoneType} has no fields yet`,
17
- onActionClick,
18
- actionTestId,
16
+ action,
19
17
  } = props;
20
18
 
21
19
  return (
@@ -26,14 +24,7 @@ export const ZoneEmptyState: FC<ZoneEmptyStateProps> = (props) => {
26
24
  A field is an input for content (e.g. text, images, links).
27
25
  </Text>
28
26
  </div>
29
- <Button
30
- color="grey"
31
- onClick={() => onActionClick()}
32
- data-testid={actionTestId}
33
- startIcon="add"
34
- >
35
- Add a field
36
- </Button>
27
+ {action}
37
28
  </div>
38
29
  );
39
30
  };
@@ -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,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> = {
@@ -2,18 +2,19 @@ import { GroupFieldType } from "@prismicio/types-internal/lib/customtypes/widget
2
2
  import { Fragment, useState } from "react";
3
3
  import { DragDropContext, Droppable } from "react-beautiful-dnd";
4
4
  import { flushSync } from "react-dom";
5
- import { Box, Button } from "theme-ui";
5
+ import { Box } from "theme-ui";
6
6
 
7
7
  import { telemetry } from "@/apiClient";
8
+ import { fields as allFields } from "@/domain/fields";
8
9
  import {
9
10
  addFieldToGroup,
10
11
  deleteFieldFromGroup,
11
12
  reorderFieldInGroup,
12
13
  updateFieldInGroup,
13
14
  } from "@/domain/group";
15
+ import { AddFieldDropdown } from "@/features/builder/AddFieldDropdown";
14
16
  import ListItem from "@/legacy/components/ListItem";
15
17
  import EditModal from "@/legacy/lib/builders/common/EditModal";
16
- import SelectFieldTypeModal from "@/legacy/lib/builders/common/SelectFieldTypeModal";
17
18
  import Hint from "@/legacy/lib/builders/common/Zone/Card/components/Hints";
18
19
  import NewField from "@/legacy/lib/builders/common/Zone/Card/components/NewField";
19
20
  import { findWidgetByConfigOrType } from "@/legacy/lib/builders/utils";
@@ -39,14 +40,12 @@ export const CustomListItem = ({
39
40
  HintElement,
40
41
  ...rest
41
42
  }) => {
42
- const [selectMode, setSelectMode] = useState(false);
43
43
  const [newFieldData, setNewFieldData] = useState(null);
44
44
  const [editModalData, setEditModalData] = useState({ isOpen: false });
45
45
 
46
46
  const onSelectFieldType = (widgetTypeName) => {
47
47
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
48
48
  setNewFieldData({ widgetTypeName });
49
- setSelectMode(false);
50
49
  };
51
50
 
52
51
  const onCancelNewField = () => {
@@ -171,15 +170,28 @@ export const CustomListItem = ({
171
170
  {...rest}
172
171
  HintElement={HintElement}
173
172
  CustomEditElements={[
174
- <Button
175
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
176
- key={`custom-edit-element-${groupItem.key}`}
177
- mr={2}
178
- variant="buttons.darkSmall"
179
- onClick={() => setSelectMode(true)}
180
- >
181
- Add Field
182
- </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
+ />,
183
195
  ]}
184
196
  children={
185
197
  <Box sx={{ ml: 4 }}>
@@ -282,12 +294,6 @@ export const CustomListItem = ({
282
294
  </Box>
283
295
  }
284
296
  />
285
- <SelectFieldTypeModal
286
- data={{ isOpen: selectMode }}
287
- close={() => setSelectMode(false)}
288
- onSelect={onSelectFieldType}
289
- widgetsArray={widgetsArray}
290
- />
291
297
  <EditModal
292
298
  data={editModalData}
293
299
  close={closeEditModal}
@@ -1,4 +1,4 @@
1
- import { MdPlaylistAdd } from "react-icons/md";
1
+ import { MdOutlineCreateNewFolder } from "react-icons/md";
2
2
  import * as yup from "yup";
3
3
 
4
4
  import { TabField } from "@/legacy/lib/models/common/CustomType";
@@ -12,9 +12,7 @@ import { type GroupListItemProps } from ".";
12
12
  import Form, { FormFields } from "./Form";
13
13
 
14
14
  const Meta = {
15
- icon: MdPlaylistAdd,
16
- title: "Group",
17
- description: "A repeatable set of fields",
15
+ icon: MdOutlineCreateNewFolder,
18
16
  };
19
17
 
20
18
  const createSchema = (typeRegex: RegExp) =>