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.
- package/out/404.html +1 -1
- package/out/_next/static/chunks/{18539003-4c4fdc1584f243b4.js → 18539003-afe9213a677bd9f0.js} +3 -3
- package/out/_next/static/chunks/212-b6a2b8d9d16ef3c5.js +1 -0
- package/out/_next/static/chunks/{210-565d01ee9777d1a1.js → 326-2d6d04d623de16c6.js} +2 -2
- package/out/_next/static/chunks/448-ae7b5e576a84b47d.js +1 -0
- package/out/_next/static/chunks/465-d5dd75edd8e6bb65.js +1 -0
- package/out/_next/static/chunks/574-a23212bd78e563de.js +1 -0
- package/out/_next/static/chunks/59b4e022-8e544a511d670317.js +1 -0
- package/out/_next/static/chunks/{756-0b198fb40a3e6ae2.js → 695-bfe7255199983590.js} +6 -4
- package/out/_next/static/chunks/816-17afd84d2642f4c4.js +1 -0
- package/out/_next/static/chunks/f36c6662-1f3a854183168b10.js +1 -0
- package/out/_next/static/chunks/pages/_app-07a2573662865a6b.js +547 -0
- package/out/_next/static/chunks/pages/changelog-1d15a7ef498f499a.js +1 -0
- package/out/_next/static/chunks/pages/changes-0af2a1a5244418f9.js +1 -0
- package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-a6ff1618238b3510.js → [customTypeId]-b714ad9814898d23.js} +1 -1
- package/out/_next/static/chunks/pages/index-2610e550d79c5bb1.js +1 -0
- package/out/_next/static/chunks/pages/labs-240ba57bfdabe998.js +1 -0
- package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-c300f963db847451.js → [pageTypeId]-aa881786f6e5aee2.js} +1 -1
- package/out/_next/static/chunks/pages/settings-039476a9a63d25a1.js +1 -0
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-cfc19e3e799745bb.js +1 -0
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-58e6cb0f278ea03c.js +1 -0
- package/out/_next/static/chunks/pages/slices-9894ce968d74baf6.js +1 -0
- package/out/_next/static/css/447984631bacb88c.css +1 -0
- package/out/_next/static/yi8ItcVBmEtxCSvLChc0b/_buildManifest.js +1 -0
- package/out/changelog.html +1 -1
- package/out/changes.html +1 -1
- package/out/custom-types/[customTypeId].html +1 -1
- package/out/custom-types.html +1 -1
- package/out/index.html +1 -1
- package/out/labs.html +1 -1
- package/out/page-types/[pageTypeId].html +1 -1
- package/out/settings.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
- package/out/slices.html +1 -1
- package/package.json +9 -9
- package/src/domain/fields.ts +223 -0
- package/src/features/builder/AddFieldDropdown.tsx +122 -0
- package/src/features/onboarding/OnboardingGuide.tsx +45 -0
- package/src/features/onboarding/OnboardingProgressStepper.tsx +71 -0
- package/src/features/onboarding/OnboardingProvider.tsx +82 -0
- package/src/features/onboarding/OnboardingStepDialog/OnboardingStepDialog.tsx +46 -0
- package/src/features/onboarding/OnboardingStepDialog/OnboardingStepDialogContent.tsx +38 -0
- package/src/features/onboarding/OnboardingStepDialog/index.ts +1 -0
- package/src/features/onboarding/content.tsx +99 -0
- package/src/features/onboarding/types.ts +32 -0
- package/src/features/onboarding/useOnboardingExperiment.ts +6 -0
- package/src/hooks/usePersistedState.ts +20 -3
- package/src/legacy/components/Navigation/index.tsx +2 -0
- package/src/legacy/lib/builders/CustomTypeBuilder/TabZone/index.tsx +9 -9
- package/src/legacy/lib/builders/SliceBuilder/FieldZones/index.tsx +7 -9
- package/src/legacy/lib/builders/SliceBuilder/VariationsList/index.tsx +1 -0
- package/src/legacy/lib/builders/common/Zone/components/ZoneEmptyState/ZoneEmptyState.tsx +4 -13
- package/src/legacy/lib/builders/common/Zone/index.jsx +26 -31
- package/src/legacy/lib/models/common/widgets/Boolean/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/Color/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/ContentRelationship/index.ts +0 -2
- package/src/legacy/lib/models/common/widgets/Date/index.ts +0 -2
- package/src/legacy/lib/models/common/widgets/Embed/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/GeoPoint/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/Group/ListItem/index.jsx +25 -19
- package/src/legacy/lib/models/common/widgets/Group/createGroupWidget.ts +2 -4
- package/src/legacy/lib/models/common/widgets/Group/index.tsx +8 -8
- package/src/legacy/lib/models/common/widgets/Image/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/Link/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/LinkToMedia/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/NestedGroup/index.tsx +7 -7
- package/src/legacy/lib/models/common/widgets/Number/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/Select/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/StructuredText/index.ts +0 -2
- package/src/legacy/lib/models/common/widgets/Text/index.ts +0 -2
- package/src/legacy/lib/models/common/widgets/Timestamp/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/UID/index.ts +2 -4
- package/src/legacy/lib/models/common/widgets/Widget.ts +0 -2
- package/out/_next/static/8ioxR-A_Nfflu3QxG8paB/_buildManifest.js +0 -1
- package/out/_next/static/chunks/212-58a99ab312f8a03b.js +0 -1
- package/out/_next/static/chunks/448-7a6843206737d199.js +0 -1
- package/out/_next/static/chunks/465-695ac5fedef986eb.js +0 -1
- package/out/_next/static/chunks/574-dfeb0e6aa3079da1.js +0 -1
- package/out/_next/static/chunks/59b4e022-7a3b5554b806349f.js +0 -1
- package/out/_next/static/chunks/816-96fc9a79308019c7.js +0 -1
- package/out/_next/static/chunks/f36c6662-03bb5e9d9aa0b0aa.js +0 -1
- package/out/_next/static/chunks/pages/_app-0f7eec1bd89de90c.js +0 -536
- package/out/_next/static/chunks/pages/changelog-f0cd26a9ae095e9e.js +0 -1
- package/out/_next/static/chunks/pages/changes-1a57f90433a0b5e2.js +0 -1
- package/out/_next/static/chunks/pages/index-a0530bd55c7e1872.js +0 -1
- package/out/_next/static/chunks/pages/labs-7b0abeef69494aec.js +0 -1
- package/out/_next/static/chunks/pages/settings-f82286dde10f624c.js +0 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-453f946e8879c829.js +0 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-b530a5e0ac9d0391.js +0 -1
- package/out/_next/static/chunks/pages/slices-7411822d74f1f6c1.js +0 -1
- package/out/_next/static/css/ebf74ed654df9202.css +0 -1
- package/src/legacy/lib/builders/common/SelectFieldTypeModal/FieldTypeCard.jsx +0 -51
- package/src/legacy/lib/builders/common/SelectFieldTypeModal/index.jsx +0 -84
- /package/out/_next/static/{8ioxR-A_Nfflu3QxG8paB → yi8ItcVBmEtxCSvLChc0b}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { IconName } from "@prismicio/editor-ui";
|
|
2
|
+
import {
|
|
3
|
+
FieldType,
|
|
4
|
+
NestableFieldTypes,
|
|
5
|
+
} from "@prismicio/types-internal/lib/customtypes";
|
|
6
|
+
|
|
7
|
+
interface BaseField {
|
|
8
|
+
description: string;
|
|
9
|
+
icon: IconName;
|
|
10
|
+
name: string;
|
|
11
|
+
thumbnail: string;
|
|
12
|
+
type: FieldType;
|
|
13
|
+
variant?: FieldVariant;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type NestableFieldVariant = "ContentRelationship" | "LinkToMedia";
|
|
17
|
+
export type GroupFieldVariant = "NestedGroup";
|
|
18
|
+
export type FieldVariant = NestableFieldVariant | GroupFieldVariant;
|
|
19
|
+
|
|
20
|
+
export interface NestableField extends BaseField {
|
|
21
|
+
type: NestableFieldTypes;
|
|
22
|
+
variant?: NestableFieldVariant;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UIDField extends BaseField {
|
|
26
|
+
type: "UID";
|
|
27
|
+
variant?: never;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GroupField extends BaseField {
|
|
31
|
+
type: "Group";
|
|
32
|
+
variant?: "NestedGroup";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type Field = NestableField | UIDField | GroupField;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Nestable fields
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export const booleanField: NestableField = {
|
|
42
|
+
name: "Boolean",
|
|
43
|
+
description: "A true or false toggle.",
|
|
44
|
+
icon: "toggleOff",
|
|
45
|
+
thumbnail:
|
|
46
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295526/DEV_TOOLS/SM_FIELDS/Type_Boolean_cbwg8k.png",
|
|
47
|
+
type: "Boolean",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const colorField: NestableField = {
|
|
51
|
+
name: "Color",
|
|
52
|
+
description: "A color in hex format.",
|
|
53
|
+
icon: "colorLens",
|
|
54
|
+
thumbnail:
|
|
55
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295521/DEV_TOOLS/SM_FIELDS/Type_Color_nyoeit.png",
|
|
56
|
+
type: "Color",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const dateField: NestableField = {
|
|
60
|
+
name: "Date",
|
|
61
|
+
description: "A date without time.",
|
|
62
|
+
icon: "dateRange",
|
|
63
|
+
thumbnail:
|
|
64
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295525/DEV_TOOLS/SM_FIELDS/Type_Date_dsuaeq.png",
|
|
65
|
+
type: "Date",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const embedField: NestableField = {
|
|
69
|
+
name: "Embed",
|
|
70
|
+
description: "A video, song, or oEmbed link.",
|
|
71
|
+
icon: "code",
|
|
72
|
+
thumbnail:
|
|
73
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295523/DEV_TOOLS/SM_FIELDS/Type_Embed_wwssvd.png",
|
|
74
|
+
type: "Embed",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const geoPointField: NestableField = {
|
|
78
|
+
name: "Geopoint",
|
|
79
|
+
description: "A geographical coordinate.",
|
|
80
|
+
icon: "place",
|
|
81
|
+
thumbnail:
|
|
82
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295522/DEV_TOOLS/SM_FIELDS/Type_GeoPoint_lpq0sm.png",
|
|
83
|
+
type: "GeoPoint",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const imageField: NestableField = {
|
|
87
|
+
name: "Image",
|
|
88
|
+
description: "A responsive image.",
|
|
89
|
+
icon: "image",
|
|
90
|
+
thumbnail:
|
|
91
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295520/DEV_TOOLS/SM_FIELDS/Type_Image_zyatxy.png",
|
|
92
|
+
type: "Image",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const LinkField: NestableField = {
|
|
96
|
+
name: "Link",
|
|
97
|
+
description: "A link to a website, asset, or document.",
|
|
98
|
+
icon: "link",
|
|
99
|
+
thumbnail:
|
|
100
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295528/DEV_TOOLS/SM_FIELDS/Type_Link_bgslgy.png",
|
|
101
|
+
type: "Link",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const contentRelationshipField: NestableField = {
|
|
105
|
+
name: "Content Relationship",
|
|
106
|
+
description: "A reference to a Prismic document.",
|
|
107
|
+
icon: "settingsEthernet",
|
|
108
|
+
thumbnail:
|
|
109
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295527/DEV_TOOLS/SM_FIELDS/Type_Content_RelationShip_s4z4nh.png",
|
|
110
|
+
type: "Link",
|
|
111
|
+
variant: "ContentRelationship",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const LinkToMediaField: NestableField = {
|
|
115
|
+
name: "Link To Media",
|
|
116
|
+
description: "A link to a media asset.",
|
|
117
|
+
icon: "attachFile",
|
|
118
|
+
thumbnail:
|
|
119
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295519/DEV_TOOLS/SM_FIELDS/Type_LinkToMedia_m2p0ce.png",
|
|
120
|
+
type: "Link",
|
|
121
|
+
variant: "LinkToMedia",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const numberField: NestableField = {
|
|
125
|
+
name: "Number",
|
|
126
|
+
description: "An integer or float.",
|
|
127
|
+
icon: "pin",
|
|
128
|
+
thumbnail:
|
|
129
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295522/DEV_TOOLS/SM_FIELDS/Type_Number_o3olop.png",
|
|
130
|
+
type: "Number",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const selectField: NestableField = {
|
|
134
|
+
name: "Select",
|
|
135
|
+
description: "A dropdown of options.",
|
|
136
|
+
icon: "arrowDropDownCircle",
|
|
137
|
+
thumbnail:
|
|
138
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295527/DEV_TOOLS/SM_FIELDS/Type_Select_bedjei.png",
|
|
139
|
+
type: "Select",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const richTextField: NestableField = {
|
|
143
|
+
name: "Rich Text",
|
|
144
|
+
description: "Text with formatting options.",
|
|
145
|
+
icon: "textFields",
|
|
146
|
+
thumbnail:
|
|
147
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295530/DEV_TOOLS/SM_FIELDS/Type_Rich_Text_fxdyar.png",
|
|
148
|
+
type: "StructuredText",
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const textField: NestableField = {
|
|
152
|
+
name: "Text",
|
|
153
|
+
description: "A text string without formatting.",
|
|
154
|
+
icon: "title",
|
|
155
|
+
thumbnail:
|
|
156
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295529/DEV_TOOLS/SM_FIELDS/Type_Key_Text_xrqf45.png",
|
|
157
|
+
type: "Text",
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const timestampField: NestableField = {
|
|
161
|
+
name: "Timestamp",
|
|
162
|
+
description: "A date and time.",
|
|
163
|
+
icon: "schedule",
|
|
164
|
+
thumbnail:
|
|
165
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295524/DEV_TOOLS/SM_FIELDS/Type_Timestamp_fkr5iw.png",
|
|
166
|
+
type: "Timestamp",
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const nestableFields: NestableField[] = [
|
|
170
|
+
imageField,
|
|
171
|
+
textField,
|
|
172
|
+
richTextField,
|
|
173
|
+
booleanField,
|
|
174
|
+
numberField,
|
|
175
|
+
selectField,
|
|
176
|
+
LinkField,
|
|
177
|
+
LinkToMediaField,
|
|
178
|
+
colorField,
|
|
179
|
+
dateField,
|
|
180
|
+
timestampField,
|
|
181
|
+
embedField,
|
|
182
|
+
geoPointField,
|
|
183
|
+
contentRelationshipField,
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* UID
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
export const UIDField: UIDField = {
|
|
191
|
+
name: "UID",
|
|
192
|
+
description: "Unique Identifier",
|
|
193
|
+
icon: "tag",
|
|
194
|
+
thumbnail:
|
|
195
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721295519/DEV_TOOLS/SM_FIELDS/Type_UID_hrwzug.png",
|
|
196
|
+
type: "UID",
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Group
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
export const groupField: GroupField = {
|
|
204
|
+
name: "Repeatable Group",
|
|
205
|
+
description: "A set of fields that editors can repeat.",
|
|
206
|
+
icon: "createNewFolder",
|
|
207
|
+
thumbnail:
|
|
208
|
+
"https://res.cloudinary.com/dmtf1daqp/image/upload/v1721314577/DEV_TOOLS/SM_FIELDS/Type_Group_uwwco0.png",
|
|
209
|
+
type: "Group",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const nestedGroupField: GroupField = {
|
|
213
|
+
...groupField,
|
|
214
|
+
variant: "NestedGroup",
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export const groupFields: GroupField[] = [groupField, nestedGroupField];
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* All fields
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
export const fields = [...nestableFields, UIDField, ...groupFields];
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
DropdownMenu,
|
|
4
|
+
DropdownMenuContent,
|
|
5
|
+
DropdownMenuItem,
|
|
6
|
+
DropdownMenuLabel,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
Icon,
|
|
9
|
+
Image,
|
|
10
|
+
Text,
|
|
11
|
+
Tooltip,
|
|
12
|
+
} from "@prismicio/editor-ui";
|
|
13
|
+
import { FieldType } from "@prismicio/types-internal/lib/customtypes";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
Field,
|
|
17
|
+
FieldVariant,
|
|
18
|
+
groupField,
|
|
19
|
+
nestableFields,
|
|
20
|
+
UIDField,
|
|
21
|
+
} from "@/domain/fields";
|
|
22
|
+
|
|
23
|
+
interface AddFieldDropdownProps {
|
|
24
|
+
disabled: boolean;
|
|
25
|
+
onSelectField: (fieldType: FieldType | FieldVariant) => void;
|
|
26
|
+
fields: Field[];
|
|
27
|
+
triggerDataTestId?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AddFieldDropdown(props: AddFieldDropdownProps) {
|
|
31
|
+
const {
|
|
32
|
+
disabled,
|
|
33
|
+
onSelectField,
|
|
34
|
+
fields,
|
|
35
|
+
triggerDataTestId = "add-field",
|
|
36
|
+
} = props;
|
|
37
|
+
|
|
38
|
+
const singleFieldsToRender = fields.filter(
|
|
39
|
+
(field) =>
|
|
40
|
+
nestableFields.some(
|
|
41
|
+
(nestableField) => nestableField.name === field.name,
|
|
42
|
+
) || field.name === UIDField.name,
|
|
43
|
+
);
|
|
44
|
+
const groupFieldToRender = fields.find(
|
|
45
|
+
(field) => field.name === groupField.name,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<DropdownMenu>
|
|
50
|
+
<DropdownMenuTrigger disabled={disabled}>
|
|
51
|
+
<Button startIcon="add" color="grey" data-testid={triggerDataTestId}>
|
|
52
|
+
Add a field
|
|
53
|
+
</Button>
|
|
54
|
+
</DropdownMenuTrigger>
|
|
55
|
+
<DropdownMenuContent align="end" maxHeight={400} collisionPadding={8}>
|
|
56
|
+
<DropdownMenuLabel>Single fields</DropdownMenuLabel>
|
|
57
|
+
{singleFieldsToRender.map((field) => (
|
|
58
|
+
<AddFieldDropdownItem field={field} onSelectField={onSelectField} />
|
|
59
|
+
))}
|
|
60
|
+
|
|
61
|
+
{groupFieldToRender && (
|
|
62
|
+
<>
|
|
63
|
+
<DropdownMenuLabel>Set of fields</DropdownMenuLabel>
|
|
64
|
+
{
|
|
65
|
+
<AddFieldDropdownItem
|
|
66
|
+
field={groupFieldToRender}
|
|
67
|
+
onSelectField={onSelectField}
|
|
68
|
+
/>
|
|
69
|
+
}
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
</DropdownMenuContent>
|
|
73
|
+
</DropdownMenu>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface AddFieldDropdownItemProps {
|
|
78
|
+
onSelectField: AddFieldDropdownProps["onSelectField"];
|
|
79
|
+
field: Field;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function AddFieldDropdownItem(props: AddFieldDropdownItemProps) {
|
|
83
|
+
const { field, onSelectField } = props;
|
|
84
|
+
const { type, variant, name, icon, description, thumbnail } = field;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<DropdownMenuItem
|
|
88
|
+
key={`${type}${variant ? `-${variant}` : ""}`}
|
|
89
|
+
startIcon={<Icon name={icon} size="large" />}
|
|
90
|
+
description={description}
|
|
91
|
+
onSelect={() => onSelectField(variant ?? type)}
|
|
92
|
+
renderTooltip={(trigger) => (
|
|
93
|
+
<Tooltip
|
|
94
|
+
visible
|
|
95
|
+
variant="custom"
|
|
96
|
+
content={
|
|
97
|
+
<>
|
|
98
|
+
<Image
|
|
99
|
+
src={thumbnail}
|
|
100
|
+
sx={{
|
|
101
|
+
height: 170,
|
|
102
|
+
width: 278,
|
|
103
|
+
borderRadius: 4,
|
|
104
|
+
}}
|
|
105
|
+
animateOnLoad={false}
|
|
106
|
+
/>
|
|
107
|
+
<Text color="white">How it will look for content editors.</Text>
|
|
108
|
+
</>
|
|
109
|
+
}
|
|
110
|
+
align="start"
|
|
111
|
+
side="left"
|
|
112
|
+
sideOffset={4}
|
|
113
|
+
animation="none"
|
|
114
|
+
>
|
|
115
|
+
{trigger}
|
|
116
|
+
</Tooltip>
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
{name}
|
|
120
|
+
</DropdownMenuItem>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Card, CardContent, Text } from "@prismicio/editor-ui";
|
|
2
|
+
|
|
3
|
+
import { OnboardingProgressStepper } from "@/features/onboarding/OnboardingProgressStepper";
|
|
4
|
+
import {
|
|
5
|
+
OnboardingProvider,
|
|
6
|
+
useOnboardingContext,
|
|
7
|
+
} from "@/features/onboarding/OnboardingProvider";
|
|
8
|
+
import { useOnboardingExperiment } from "@/features/onboarding/useOnboardingExperiment";
|
|
9
|
+
|
|
10
|
+
const OnboardingGuideContent = () => {
|
|
11
|
+
const { steps, completedStepCount, isComplete } = useOnboardingContext();
|
|
12
|
+
|
|
13
|
+
if (isComplete) return null;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Card color="grey2" variant="outlined" paddingBlock={16}>
|
|
17
|
+
<CardContent>
|
|
18
|
+
<div>
|
|
19
|
+
<Text variant="bold" color="grey12">
|
|
20
|
+
Welcome
|
|
21
|
+
</Text>
|
|
22
|
+
<Text color="grey11" variant="small">
|
|
23
|
+
Get started in {steps.length} steps
|
|
24
|
+
</Text>
|
|
25
|
+
</div>
|
|
26
|
+
<Text color="grey11" variant="small" align="end">
|
|
27
|
+
{completedStepCount}/{steps.length}
|
|
28
|
+
</Text>
|
|
29
|
+
<OnboardingProgressStepper />
|
|
30
|
+
</CardContent>
|
|
31
|
+
</Card>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const OnboardingGuide = () => {
|
|
36
|
+
const { eligible } = useOnboardingExperiment();
|
|
37
|
+
|
|
38
|
+
if (!eligible) return null;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<OnboardingProvider>
|
|
42
|
+
<OnboardingGuideContent />
|
|
43
|
+
</OnboardingProvider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
DropdownMenu,
|
|
4
|
+
DropdownMenuContent,
|
|
5
|
+
DropdownMenuItem,
|
|
6
|
+
DropdownMenuLabel,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
Icon,
|
|
9
|
+
} from "@prismicio/editor-ui";
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
|
|
12
|
+
import { useOnboardingContext } from "@/features/onboarding/OnboardingProvider";
|
|
13
|
+
import { OnboardingStepDialog } from "@/features/onboarding/OnboardingStepDialog";
|
|
14
|
+
import type { OnboardingStep } from "@/features/onboarding/types";
|
|
15
|
+
|
|
16
|
+
const EndCtaIcon = () => <Icon name="playCircle" size="small" color="grey11" />;
|
|
17
|
+
|
|
18
|
+
export const OnboardingProgressStepper = () => {
|
|
19
|
+
const { completedStepCount, steps, isStepComplete } = useOnboardingContext();
|
|
20
|
+
|
|
21
|
+
const [isDialogOpen, setDialogOpen] = useState(false);
|
|
22
|
+
const [activeStep, setActiveStep] = useState(steps[0]);
|
|
23
|
+
|
|
24
|
+
const showStep = (step: OnboardingStep) => {
|
|
25
|
+
setActiveStep(step);
|
|
26
|
+
setDialogOpen(true);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<OnboardingStepDialog
|
|
32
|
+
step={activeStep}
|
|
33
|
+
isOpen={isDialogOpen}
|
|
34
|
+
onClose={() => setDialogOpen(false)}
|
|
35
|
+
/>
|
|
36
|
+
<DropdownMenu>
|
|
37
|
+
<DropdownMenuTrigger>
|
|
38
|
+
<Button
|
|
39
|
+
color="grey"
|
|
40
|
+
sx={{ width: "100%" }}
|
|
41
|
+
renderEndIcon={EndCtaIcon}
|
|
42
|
+
>
|
|
43
|
+
{completedStepCount > 0 ? "Continue" : "Start"}
|
|
44
|
+
</Button>
|
|
45
|
+
</DropdownMenuTrigger>
|
|
46
|
+
<DropdownMenuContent align="start">
|
|
47
|
+
<DropdownMenuLabel>Progress</DropdownMenuLabel>
|
|
48
|
+
{steps.map((step) => {
|
|
49
|
+
const isCompleted = isStepComplete(step.id);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<DropdownMenuItem
|
|
53
|
+
key={step.id}
|
|
54
|
+
onSelect={() => showStep(step)}
|
|
55
|
+
startIcon={
|
|
56
|
+
<Icon
|
|
57
|
+
color={isCompleted ? "green10" : "currentColor"}
|
|
58
|
+
name={isCompleted ? "checkBox" : "checkBoxOutlinedBlank"}
|
|
59
|
+
/>
|
|
60
|
+
}
|
|
61
|
+
description={step.description}
|
|
62
|
+
>
|
|
63
|
+
{step.title}
|
|
64
|
+
</DropdownMenuItem>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</DropdownMenuContent>
|
|
68
|
+
</DropdownMenu>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createContext, ReactNode, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import { onboardingSteps as steps } from "@/features/onboarding/content";
|
|
4
|
+
import {
|
|
5
|
+
type OnboardingStep,
|
|
6
|
+
type OnboardingStepStatuses,
|
|
7
|
+
onboardingStepStatusesSchema,
|
|
8
|
+
type OnboardingStepType,
|
|
9
|
+
} from "@/features/onboarding/types";
|
|
10
|
+
import { usePersistedState } from "@/hooks/usePersistedState";
|
|
11
|
+
|
|
12
|
+
type OnboardingContext = {
|
|
13
|
+
steps: OnboardingStep[];
|
|
14
|
+
completedStepCount: number;
|
|
15
|
+
toggleStepComplete: (step: OnboardingStepType) => void;
|
|
16
|
+
getStepIndex: (stepId: OnboardingStepType) => number;
|
|
17
|
+
isStepComplete: (stepId: OnboardingStepType) => boolean;
|
|
18
|
+
isComplete: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const OnboardingContext = createContext<OnboardingContext | undefined>(
|
|
22
|
+
undefined,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const getInitialState = (): OnboardingStepStatuses => {
|
|
26
|
+
// if the old guide was dismissed, all steps start as complete
|
|
27
|
+
const wasOldGuideDismissed =
|
|
28
|
+
localStorage.getItem("slice-machine_isInAppGuideOpen") === "false";
|
|
29
|
+
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
steps.map((step) => [step.id, wasOldGuideDismissed]),
|
|
32
|
+
) as OnboardingStepStatuses;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const OnboardingProvider = ({ children }: { children: ReactNode }) => {
|
|
36
|
+
const [stepStatus, setStepStatus] = usePersistedState(
|
|
37
|
+
"onboardingSteps",
|
|
38
|
+
getInitialState(),
|
|
39
|
+
{ schema: onboardingStepStatusesSchema },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const toggleStepComplete = (step: OnboardingStepType) => {
|
|
43
|
+
setStepStatus((current) => ({ ...current, [step]: !current[step] }));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getStepIndex = (stepId: OnboardingStepType) => {
|
|
47
|
+
return steps.findIndex(({ id }) => id === stepId);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const isStepComplete = (stepId: OnboardingStepType) => {
|
|
51
|
+
return stepStatus[stepId] ?? false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const completedStepCount = Object.values(stepStatus).filter(Boolean).length;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<OnboardingContext.Provider
|
|
58
|
+
value={{
|
|
59
|
+
steps,
|
|
60
|
+
toggleStepComplete,
|
|
61
|
+
isStepComplete,
|
|
62
|
+
getStepIndex,
|
|
63
|
+
completedStepCount,
|
|
64
|
+
isComplete: completedStepCount === steps.length,
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</OnboardingContext.Provider>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const useOnboardingContext = () => {
|
|
73
|
+
const context = useContext(OnboardingContext);
|
|
74
|
+
|
|
75
|
+
if (context == null) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"useOnboardingContext must be used within an OnboardingProvider",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return context;
|
|
82
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
DialogActions,
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogHeader,
|
|
6
|
+
} from "@prismicio/editor-ui";
|
|
7
|
+
|
|
8
|
+
import { useOnboardingContext } from "@/features/onboarding/OnboardingProvider";
|
|
9
|
+
import { OnboardingStepDialogContent } from "@/features/onboarding/OnboardingStepDialog/OnboardingStepDialogContent";
|
|
10
|
+
import type { OnboardingStep } from "@/features/onboarding/types";
|
|
11
|
+
|
|
12
|
+
type OnboardingStepDialogProps = {
|
|
13
|
+
step: OnboardingStep;
|
|
14
|
+
isOpen: boolean;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const OnboardingStepDialog = ({
|
|
19
|
+
step,
|
|
20
|
+
isOpen,
|
|
21
|
+
onClose,
|
|
22
|
+
}: OnboardingStepDialogProps) => {
|
|
23
|
+
const { toggleStepComplete, isStepComplete } = useOnboardingContext();
|
|
24
|
+
|
|
25
|
+
const markAsDone = () => {
|
|
26
|
+
if (!isOpen) return;
|
|
27
|
+
toggleStepComplete(step.id);
|
|
28
|
+
onClose();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
33
|
+
<DialogHeader title="Learn" />
|
|
34
|
+
<DialogContent>
|
|
35
|
+
<OnboardingStepDialogContent step={step} />
|
|
36
|
+
<DialogActions
|
|
37
|
+
ok={{
|
|
38
|
+
text: isStepComplete(step.id) ? "Undo step" : "Mark as done",
|
|
39
|
+
onClick: markAsDone,
|
|
40
|
+
}}
|
|
41
|
+
cancel={{ text: "Close" }}
|
|
42
|
+
/>
|
|
43
|
+
</DialogContent>
|
|
44
|
+
</Dialog>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Box, ScrollArea, Text, Video } from "@prismicio/editor-ui";
|
|
2
|
+
|
|
3
|
+
import { getOnboardingStepsContent } from "@/features/onboarding/content";
|
|
4
|
+
import { useOnboardingContext } from "@/features/onboarding/OnboardingProvider";
|
|
5
|
+
import { OnboardingStep } from "@/features/onboarding/types";
|
|
6
|
+
import { useRepositoryInformation } from "@/hooks/useRepositoryInformation";
|
|
7
|
+
|
|
8
|
+
type OnboardingStepDialogContentProps = {
|
|
9
|
+
step: OnboardingStep;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const OnboardingStepDialogContent = ({
|
|
13
|
+
step,
|
|
14
|
+
}: OnboardingStepDialogContentProps) => {
|
|
15
|
+
const { getStepIndex } = useOnboardingContext();
|
|
16
|
+
const { repositoryUrl } = useRepositoryInformation();
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
content: Content,
|
|
20
|
+
videoUrl,
|
|
21
|
+
title = step.title,
|
|
22
|
+
} = getOnboardingStepsContent({ repositoryUrl })[step.id];
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ScrollArea>
|
|
26
|
+
<Box as="article" flexDirection="column" padding={16} gap={16}>
|
|
27
|
+
<section>
|
|
28
|
+
<Text sx={{ marginBottom: 4 }} color="purple9" variant="bold">
|
|
29
|
+
Step {getStepIndex(step.id) + 1}
|
|
30
|
+
</Text>
|
|
31
|
+
<Text variant="h3">{title}</Text>
|
|
32
|
+
<Content />
|
|
33
|
+
</section>
|
|
34
|
+
<Video src={videoUrl} sizing="contain" autoPlay loop />
|
|
35
|
+
</Box>
|
|
36
|
+
</ScrollArea>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OnboardingStepDialog } from "./OnboardingStepDialog";
|