@asheeui/puck 0.0.0-stage → 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/LICENSE +201 -0
- package/NOTICE +10 -0
- package/README.md +126 -3
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/index.native.d.ts +22 -0
- package/dist/index.native.d.ts.map +1 -0
- package/dist/index.native.js +22 -0
- package/dist/index.native.js.map +1 -0
- package/dist/native/blocks/content.d.ts +19 -0
- package/dist/native/blocks/content.d.ts.map +1 -0
- package/dist/native/blocks/content.js +29 -0
- package/dist/native/blocks/content.js.map +1 -0
- package/dist/native/blocks/layout.d.ts +42 -0
- package/dist/native/blocks/layout.d.ts.map +1 -0
- package/dist/native/blocks/layout.js +33 -0
- package/dist/native/blocks/layout.js.map +1 -0
- package/dist/native/blocks/navigation.d.ts +19 -0
- package/dist/native/blocks/navigation.d.ts.map +1 -0
- package/dist/native/blocks/navigation.js +38 -0
- package/dist/native/blocks/navigation.js.map +1 -0
- package/dist/native/blocks/sections.d.ts +30 -0
- package/dist/native/blocks/sections.d.ts.map +1 -0
- package/dist/native/blocks/sections.js +60 -0
- package/dist/native/blocks/sections.js.map +1 -0
- package/dist/native/blocks/utility.d.ts +13 -0
- package/dist/native/blocks/utility.d.ts.map +1 -0
- package/dist/native/blocks/utility.js +18 -0
- package/dist/native/blocks/utility.js.map +1 -0
- package/dist/native/config.d.ts +19 -0
- package/dist/native/config.d.ts.map +1 -0
- package/dist/native/config.js +45 -0
- package/dist/native/config.js.map +1 -0
- package/dist/native/index.d.ts +18 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +18 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/render.d.ts +39 -0
- package/dist/native/render.d.ts.map +1 -0
- package/dist/native/render.js +71 -0
- package/dist/native/render.js.map +1 -0
- package/dist/native/root.d.ts +21 -0
- package/dist/native/root.d.ts.map +1 -0
- package/dist/native/root.js +28 -0
- package/dist/native/root.js.map +1 -0
- package/dist/shared/adapters.d.ts +52 -0
- package/dist/shared/adapters.d.ts.map +1 -0
- package/dist/shared/adapters.js +57 -0
- package/dist/shared/adapters.js.map +1 -0
- package/dist/shared/blocks/content.d.ts +42 -0
- package/dist/shared/blocks/content.d.ts.map +1 -0
- package/dist/shared/blocks/content.js +44 -0
- package/dist/shared/blocks/content.js.map +1 -0
- package/dist/shared/blocks/index.d.ts +42 -0
- package/dist/shared/blocks/index.d.ts.map +1 -0
- package/dist/shared/blocks/index.js +81 -0
- package/dist/shared/blocks/index.js.map +1 -0
- package/dist/shared/blocks/layout.d.ts +52 -0
- package/dist/shared/blocks/layout.d.ts.map +1 -0
- package/dist/shared/blocks/layout.js +51 -0
- package/dist/shared/blocks/layout.js.map +1 -0
- package/dist/shared/blocks/navigation.d.ts +74 -0
- package/dist/shared/blocks/navigation.d.ts.map +1 -0
- package/dist/shared/blocks/navigation.js +86 -0
- package/dist/shared/blocks/navigation.js.map +1 -0
- package/dist/shared/blocks/sections.d.ts +142 -0
- package/dist/shared/blocks/sections.d.ts.map +1 -0
- package/dist/shared/blocks/sections.js +166 -0
- package/dist/shared/blocks/sections.js.map +1 -0
- package/dist/shared/blocks/utility.d.ts +31 -0
- package/dist/shared/blocks/utility.d.ts.map +1 -0
- package/dist/shared/blocks/utility.js +36 -0
- package/dist/shared/blocks/utility.js.map +1 -0
- package/dist/shared/fields.d.ts +103 -0
- package/dist/shared/fields.d.ts.map +1 -0
- package/dist/shared/fields.js +162 -0
- package/dist/shared/fields.js.map +1 -0
- package/dist/shared/index.d.ts +14 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +14 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/types.d.ts +235 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +20 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/blocks/content.d.ts +10 -0
- package/dist/web/blocks/content.d.ts.map +1 -0
- package/dist/web/blocks/content.js +28 -0
- package/dist/web/blocks/content.js.map +1 -0
- package/dist/web/blocks/layout.d.ts +27 -0
- package/dist/web/blocks/layout.d.ts.map +1 -0
- package/dist/web/blocks/layout.js +28 -0
- package/dist/web/blocks/layout.js.map +1 -0
- package/dist/web/blocks/navigation.d.ts +10 -0
- package/dist/web/blocks/navigation.d.ts.map +1 -0
- package/dist/web/blocks/navigation.js +38 -0
- package/dist/web/blocks/navigation.js.map +1 -0
- package/dist/web/blocks/sections.d.ts +22 -0
- package/dist/web/blocks/sections.d.ts.map +1 -0
- package/dist/web/blocks/sections.js +60 -0
- package/dist/web/blocks/sections.js.map +1 -0
- package/dist/web/blocks/utility.d.ts +6 -0
- package/dist/web/blocks/utility.d.ts.map +1 -0
- package/dist/web/blocks/utility.js +19 -0
- package/dist/web/blocks/utility.js.map +1 -0
- package/dist/web/config.d.ts +65 -0
- package/dist/web/config.d.ts.map +1 -0
- package/dist/web/config.js +35 -0
- package/dist/web/config.js.map +1 -0
- package/dist/web/index.d.ts +19 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +20 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/page.d.ts +24 -0
- package/dist/web/page.d.ts.map +1 -0
- package/dist/web/page.js +27 -0
- package/dist/web/page.js.map +1 -0
- package/dist/web/root.d.ts +9 -0
- package/dist/web/root.d.ts.map +1 -0
- package/dist/web/root.js +26 -0
- package/dist/web/root.js.map +1 -0
- package/package.json +119 -3
- package/src/index.native.ts +22 -0
- package/src/index.ts +24 -0
- package/src/native/blocks/content.tsx +55 -0
- package/src/native/blocks/layout.tsx +72 -0
- package/src/native/blocks/navigation.tsx +71 -0
- package/src/native/blocks/sections.tsx +174 -0
- package/src/native/blocks/utility.tsx +27 -0
- package/src/native/config.tsx +66 -0
- package/src/native/index.ts +18 -0
- package/src/native/render.tsx +113 -0
- package/src/native/root.tsx +35 -0
- package/src/shared/adapters.ts +82 -0
- package/src/shared/blocks/content.ts +80 -0
- package/src/shared/blocks/index.ts +100 -0
- package/src/shared/blocks/layout.ts +82 -0
- package/src/shared/blocks/navigation.ts +168 -0
- package/src/shared/blocks/sections.ts +322 -0
- package/src/shared/blocks/utility.ts +65 -0
- package/src/shared/fields.ts +199 -0
- package/src/shared/index.ts +14 -0
- package/src/shared/types.ts +278 -0
- package/src/web/blocks/content.tsx +55 -0
- package/src/web/blocks/layout.tsx +73 -0
- package/src/web/blocks/navigation.tsx +72 -0
- package/src/web/blocks/sections.tsx +177 -0
- package/src/web/blocks/utility.tsx +31 -0
- package/src/web/config.tsx +141 -0
- package/src/web/index.ts +21 -0
- package/src/web/page.tsx +40 -0
- package/src/web/root.tsx +34 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable field definitions for the AsheeUI blocks.
|
|
3
|
+
*
|
|
4
|
+
* These builders are the boundary between a component's visual configuration and its
|
|
5
|
+
* React implementation: every option a block exposes is described here as a
|
|
6
|
+
* serializable field, so the value a consumer edits in the builder is a plain string,
|
|
7
|
+
* number or array that survives a save and a reload. Nothing in this file imports a
|
|
8
|
+
* component, and nothing it produces is a function, which is what lets the same
|
|
9
|
+
* definitions describe a block to the web editor and to the native renderer.
|
|
10
|
+
*
|
|
11
|
+
* The types come from `./types`, this package's own, rather than from the editor: the
|
|
12
|
+
* native entry has to load without a DOM editor, and a field is data either way.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
AsheeArrayField,
|
|
17
|
+
AsheeObjectField,
|
|
18
|
+
AsheeSelectField,
|
|
19
|
+
AsheeTextareaField,
|
|
20
|
+
AsheeTextField,
|
|
21
|
+
} from "./types";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build a select field from the values a prop accepts.
|
|
25
|
+
*
|
|
26
|
+
* The options are derived from the same tuple the component's type uses, so a value
|
|
27
|
+
* the builder offers and a value the component accepts cannot drift.
|
|
28
|
+
*
|
|
29
|
+
* @param label - Label shown in the builder.
|
|
30
|
+
* @param options - The accepted values.
|
|
31
|
+
* @returns The field definition.
|
|
32
|
+
*/
|
|
33
|
+
export function selectField<T extends string>(
|
|
34
|
+
label: string,
|
|
35
|
+
options: readonly T[],
|
|
36
|
+
): AsheeSelectField {
|
|
37
|
+
return {
|
|
38
|
+
type: "select",
|
|
39
|
+
label,
|
|
40
|
+
options: options.map((value) => ({ label: value, value })),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build a single-line text field.
|
|
46
|
+
*
|
|
47
|
+
* @param label - Label shown in the builder.
|
|
48
|
+
* @param placeholder - Placeholder shown in the empty field.
|
|
49
|
+
* @returns The field definition.
|
|
50
|
+
*/
|
|
51
|
+
export function textField(label: string, placeholder?: string): AsheeTextField {
|
|
52
|
+
return { type: "text", label, placeholder };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a multi-line text field.
|
|
57
|
+
*
|
|
58
|
+
* @param label - Label shown in the builder.
|
|
59
|
+
* @param placeholder - Placeholder shown in the empty field.
|
|
60
|
+
* @returns The field definition.
|
|
61
|
+
*/
|
|
62
|
+
export function textareaField(
|
|
63
|
+
label: string,
|
|
64
|
+
placeholder?: string,
|
|
65
|
+
): AsheeTextareaField {
|
|
66
|
+
return { type: "textarea", label, placeholder };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Alignment options shared by the patterns that can centre their content.
|
|
71
|
+
*/
|
|
72
|
+
export const ALIGNMENT_OPTIONS = ["start", "center"] as const;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Vertical rhythm options shared by the section-level blocks.
|
|
76
|
+
*/
|
|
77
|
+
export const SPACING_OPTIONS = [
|
|
78
|
+
"none",
|
|
79
|
+
"xs",
|
|
80
|
+
"sm",
|
|
81
|
+
"md",
|
|
82
|
+
"lg",
|
|
83
|
+
"xl",
|
|
84
|
+
"2xl",
|
|
85
|
+
] as const;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Background treatments a section-level block can offer.
|
|
89
|
+
*/
|
|
90
|
+
export const BACKGROUND_OPTIONS = ["none", "muted", "tinted"] as const;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Maximum content widths a block can offer.
|
|
94
|
+
*/
|
|
95
|
+
export const CONTAINER_SIZE_OPTIONS = ["sm", "md", "lg", "xl", "full"] as const;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Column counts a grid block can offer.
|
|
99
|
+
*/
|
|
100
|
+
export const COLUMNS_OPTIONS = ["1", "2", "3", "4"] as const;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Button variants a configured action can offer.
|
|
104
|
+
*/
|
|
105
|
+
export const ACTION_VARIANT_OPTIONS = [
|
|
106
|
+
"solid",
|
|
107
|
+
"faded",
|
|
108
|
+
"bordered",
|
|
109
|
+
"ghost",
|
|
110
|
+
] as const;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fields of a configured action.
|
|
114
|
+
*
|
|
115
|
+
* The shape matches the framework's `ActionConfig` for the fields a builder can own:
|
|
116
|
+
* wording, destination and emphasis. An icon, a custom component and a click handler
|
|
117
|
+
* stay out, because none of them can be serialized.
|
|
118
|
+
*/
|
|
119
|
+
export const ACTION_FIELDS: AsheeObjectField = {
|
|
120
|
+
type: "object",
|
|
121
|
+
label: "Action",
|
|
122
|
+
objectFields: {
|
|
123
|
+
label: textField("Action label"),
|
|
124
|
+
href: textField("Action link", "/pricing"),
|
|
125
|
+
variant: selectField("Action emphasis", ACTION_VARIANT_OPTIONS),
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A single feature inside a feature grid.
|
|
131
|
+
*/
|
|
132
|
+
export const FEATURE_ITEM_FIELD: AsheeArrayField<{
|
|
133
|
+
title: string;
|
|
134
|
+
description: string;
|
|
135
|
+
}> = {
|
|
136
|
+
type: "array",
|
|
137
|
+
label: "Features",
|
|
138
|
+
arrayFields: {
|
|
139
|
+
title: textField("Feature title"),
|
|
140
|
+
description: textareaField("Feature description"),
|
|
141
|
+
},
|
|
142
|
+
defaultItemProps: { title: "New feature", description: "" },
|
|
143
|
+
getItemSummary: (item) => item.title,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A plan's feature line.
|
|
148
|
+
*/
|
|
149
|
+
export const PRICING_FEATURE_FIELD: AsheeArrayField<{
|
|
150
|
+
label: string;
|
|
151
|
+
included: boolean;
|
|
152
|
+
}> = {
|
|
153
|
+
type: "array",
|
|
154
|
+
label: "Feature lines",
|
|
155
|
+
arrayFields: {
|
|
156
|
+
label: textField("Line"),
|
|
157
|
+
included: {
|
|
158
|
+
type: "radio",
|
|
159
|
+
label: "Included",
|
|
160
|
+
options: [
|
|
161
|
+
{ label: "Included", value: true },
|
|
162
|
+
{ label: "Not included", value: false },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
defaultItemProps: { label: "New line", included: true },
|
|
167
|
+
getItemSummary: (item) => item.label,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* One navigation link.
|
|
172
|
+
*/
|
|
173
|
+
export const LINK_ITEM_FIELDS = {
|
|
174
|
+
label: textField("Link label"),
|
|
175
|
+
href: textField("Link", "/pricing"),
|
|
176
|
+
} satisfies Record<string, AsheeTextField>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A testimonial quote.
|
|
180
|
+
*/
|
|
181
|
+
export const TESTIMONIAL_ITEM_FIELD: AsheeArrayField<{
|
|
182
|
+
quote: string;
|
|
183
|
+
name: string;
|
|
184
|
+
role: string;
|
|
185
|
+
}> = {
|
|
186
|
+
type: "array",
|
|
187
|
+
label: "Quotes",
|
|
188
|
+
arrayFields: {
|
|
189
|
+
quote: textareaField("Quote"),
|
|
190
|
+
name: textField("Name"),
|
|
191
|
+
role: textField("Role"),
|
|
192
|
+
},
|
|
193
|
+
defaultItemProps: {
|
|
194
|
+
quote: "They send twice as fast as they used to.",
|
|
195
|
+
name: "New customer",
|
|
196
|
+
role: "",
|
|
197
|
+
},
|
|
198
|
+
getItemSummary: (item) => item.name,
|
|
199
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared layer of the Puck integration.
|
|
3
|
+
*
|
|
4
|
+
* Everything in this module is true on both platforms: the field vocabulary, the block
|
|
5
|
+
* specs, the adapters that turn a builder value into a component prop, and the contract
|
|
6
|
+
* a stored page is read through. It imports no renderer and nothing from the editor, so
|
|
7
|
+
* it is the same code in a browser bundle and on a device — which is the whole reason
|
|
8
|
+
* the package is split this way.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export * from "./adapters";
|
|
12
|
+
export * from "./blocks";
|
|
13
|
+
export * from "./fields";
|
|
14
|
+
export * from "./types";
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary the two halves of the Puck integration share.
|
|
3
|
+
*
|
|
4
|
+
* A page composed in a visual builder has to mean the same thing on the web and on a
|
|
5
|
+
* device. What makes that true is that the two renderers read one description of a
|
|
6
|
+
* block: the same fields, the same default values, the same label and the same
|
|
7
|
+
* category. Those descriptions live here, and the types in this module are what the
|
|
8
|
+
* shared block specs and both renderers are written against.
|
|
9
|
+
*
|
|
10
|
+
* The module is deliberately free of two dependencies rather than one. It imports no
|
|
11
|
+
* renderer — that is what makes it shared — and it imports nothing from
|
|
12
|
+
* `@puckeditor/core`, because that package is a DOM editor and the native entry has to
|
|
13
|
+
* be loadable, and type-checkable, without it. The field types below are this
|
|
14
|
+
* package's own, structurally the field types a builder understands: `type` is the
|
|
15
|
+
* discriminator and the rest is the data a form control needs. The web registry
|
|
16
|
+
* asserts once, where it assembles its `Config`, that they describe the same fields —
|
|
17
|
+
* which is the single place the two descriptions meet.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ReactNode } from "react";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A single line of text.
|
|
24
|
+
*/
|
|
25
|
+
export interface AsheeTextField {
|
|
26
|
+
/** The builder draws this as a text input. */
|
|
27
|
+
type: "text";
|
|
28
|
+
|
|
29
|
+
/** Label shown in the builder. */
|
|
30
|
+
label: string;
|
|
31
|
+
|
|
32
|
+
/** Placeholder shown in the empty field. */
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Several lines of text.
|
|
38
|
+
*/
|
|
39
|
+
export interface AsheeTextareaField {
|
|
40
|
+
/** The builder draws this as a text area. */
|
|
41
|
+
type: "textarea";
|
|
42
|
+
|
|
43
|
+
/** Label shown in the builder. */
|
|
44
|
+
label: string;
|
|
45
|
+
|
|
46
|
+
/** Placeholder shown in the empty field. */
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One of a fixed set of values.
|
|
52
|
+
*/
|
|
53
|
+
export interface AsheeSelectField {
|
|
54
|
+
/** The builder draws this as a select control. */
|
|
55
|
+
type: "select";
|
|
56
|
+
|
|
57
|
+
/** Label shown in the builder. */
|
|
58
|
+
label: string;
|
|
59
|
+
|
|
60
|
+
/** The values the field offers. */
|
|
61
|
+
options: Array<{ label: string; value: string }>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One of a small set of values, drawn as a radio group.
|
|
66
|
+
*
|
|
67
|
+
* A radio field is what a boolean is expressed with, because a builder's radio
|
|
68
|
+
* control holds the value itself rather than a string spelling of it.
|
|
69
|
+
*/
|
|
70
|
+
export interface AsheeRadioField {
|
|
71
|
+
/** The builder draws this as a radio group. */
|
|
72
|
+
type: "radio";
|
|
73
|
+
|
|
74
|
+
/** Label shown in the builder. */
|
|
75
|
+
label: string;
|
|
76
|
+
|
|
77
|
+
/** The values the field offers. */
|
|
78
|
+
options: Array<{ label: string; value: string | number | boolean }>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A drop zone: where the builder lets a reader put other blocks.
|
|
83
|
+
*/
|
|
84
|
+
export interface AsheeSlotField {
|
|
85
|
+
/** The builder draws this as a drop zone. */
|
|
86
|
+
type: "slot";
|
|
87
|
+
|
|
88
|
+
/** Label shown in the builder. */
|
|
89
|
+
label: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A repeatable list of items, with its item type erased.
|
|
94
|
+
*
|
|
95
|
+
* This is the form the field union holds: a registry keeps fields of different item
|
|
96
|
+
* shapes in one object, so the collection's type has to state what every list has in
|
|
97
|
+
* common — the nested fields, the starting row and the summary — without naming an item.
|
|
98
|
+
* `AsheeArrayField` is the form a block writes, where the item type is known.
|
|
99
|
+
*/
|
|
100
|
+
export interface AsheeArrayFieldBase {
|
|
101
|
+
/** The builder draws this as a list with an add control. */
|
|
102
|
+
type: "array";
|
|
103
|
+
|
|
104
|
+
/** Label shown in the builder. */
|
|
105
|
+
label: string;
|
|
106
|
+
|
|
107
|
+
/** The fields of one item. */
|
|
108
|
+
arrayFields: Record<string, AsheeField>;
|
|
109
|
+
|
|
110
|
+
/** The item a new row starts as. */
|
|
111
|
+
defaultItemProps?: unknown;
|
|
112
|
+
|
|
113
|
+
/** The one line that summarises an item in the closed list. */
|
|
114
|
+
getItemSummary?: (item: never) => string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A repeatable list of items.
|
|
119
|
+
*
|
|
120
|
+
* @typeParam Item - The shape of one item, which `arrayFields` describes field by
|
|
121
|
+
* field.
|
|
122
|
+
*/
|
|
123
|
+
export interface AsheeArrayField<Item = Record<string, unknown>>
|
|
124
|
+
extends Omit<AsheeArrayFieldBase, "defaultItemProps" | "getItemSummary"> {
|
|
125
|
+
/** The item a new row starts as. */
|
|
126
|
+
defaultItemProps?: Partial<Item>;
|
|
127
|
+
|
|
128
|
+
/** The one line that summarises an item in the closed list. */
|
|
129
|
+
getItemSummary?: (item: Item) => string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A fixed group of fields.
|
|
134
|
+
*/
|
|
135
|
+
export interface AsheeObjectField {
|
|
136
|
+
/** The builder draws this as a nested group of controls. */
|
|
137
|
+
type: "object";
|
|
138
|
+
|
|
139
|
+
/** Label shown in the builder. */
|
|
140
|
+
label: string;
|
|
141
|
+
|
|
142
|
+
/** The fields of the group. */
|
|
143
|
+
objectFields: Record<string, AsheeField>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Every field type the AsheeUI blocks use.
|
|
148
|
+
*/
|
|
149
|
+
export type AsheeField =
|
|
150
|
+
| AsheeTextField
|
|
151
|
+
| AsheeTextareaField
|
|
152
|
+
| AsheeSelectField
|
|
153
|
+
| AsheeRadioField
|
|
154
|
+
| AsheeSlotField
|
|
155
|
+
| AsheeArrayFieldBase
|
|
156
|
+
| AsheeObjectField;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A block's visual configuration, stated once for both renderers.
|
|
160
|
+
*
|
|
161
|
+
* The three members are exactly the part of a block that cannot differ between
|
|
162
|
+
* platforms: a label a builder shows, the fields it fills in, and the value it starts
|
|
163
|
+
* with. The render function is deliberately absent — that is the one part the platform
|
|
164
|
+
* owns — and the two platform registries add it.
|
|
165
|
+
*
|
|
166
|
+
* `fields` is keyed by name rather than tied to the prop type. A drop zone names a key
|
|
167
|
+
* whose value is not part of the component's own props on either platform (the web
|
|
168
|
+
* passes a `SlotComponent`, the platform passes rendered children), so the linked form
|
|
169
|
+
* would be untruthful for exactly the blocks where the two differ most.
|
|
170
|
+
*
|
|
171
|
+
* @typeParam TProps - The props the block's render function receives.
|
|
172
|
+
*/
|
|
173
|
+
export interface AsheeBlockSpec<TProps> {
|
|
174
|
+
/** Name of the block in the builder's block list. */
|
|
175
|
+
label: string;
|
|
176
|
+
|
|
177
|
+
/** The fields the builder fills in. */
|
|
178
|
+
fields: Record<string, AsheeField>;
|
|
179
|
+
|
|
180
|
+
/** The value a new block starts with. */
|
|
181
|
+
defaultProps: Partial<TProps>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A block as a renderer sees it.
|
|
186
|
+
*
|
|
187
|
+
* The render function returns a plain React node rather than a builder's render
|
|
188
|
+
* result, because the same shape has to describe a block to two renderers: the web
|
|
189
|
+
* draws it into a DOM inside the editor's preview and inside a published page, and the
|
|
190
|
+
* platform draws it into a native view when it renders a stored page. On the platform
|
|
191
|
+
* it is handed the block's props with every drop zone already resolved to a rendered
|
|
192
|
+
* node, because a native screen has no editor to resolve one for it.
|
|
193
|
+
*
|
|
194
|
+
* @typeParam TProps - The props the render function receives.
|
|
195
|
+
*/
|
|
196
|
+
export interface AsheeBlock<TProps> {
|
|
197
|
+
/** Name of the block in the builder's block list. */
|
|
198
|
+
label: string;
|
|
199
|
+
|
|
200
|
+
/** The fields the block declares, read to find its drop zones. */
|
|
201
|
+
fields: Record<string, AsheeField>;
|
|
202
|
+
|
|
203
|
+
/** The value the block resolves when a stored page omits a key. */
|
|
204
|
+
defaultProps?: Partial<TProps>;
|
|
205
|
+
|
|
206
|
+
/** Draw the block. */
|
|
207
|
+
render: (props: TProps) => ReactNode;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* One category of the builder's block list.
|
|
212
|
+
*/
|
|
213
|
+
export interface AsheePuckCategoryDefinition {
|
|
214
|
+
/** Heading the builder shows for the category. */
|
|
215
|
+
title: string;
|
|
216
|
+
|
|
217
|
+
/** The blocks filed under it. */
|
|
218
|
+
components: string[];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The categories a block can be filed under, by key.
|
|
223
|
+
*/
|
|
224
|
+
export type AsheePuckCategories = Record<string, AsheePuckCategoryDefinition>;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The page shell a stored page renders into.
|
|
228
|
+
*/
|
|
229
|
+
export interface AsheePuckRootProps {
|
|
230
|
+
/** The page the builder composed. */
|
|
231
|
+
children?: ReactNode;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The native registry: every block, the categories they are filed under, and the page
|
|
236
|
+
* shell a stored page renders into.
|
|
237
|
+
*/
|
|
238
|
+
export interface AsheeNativePuckConfig {
|
|
239
|
+
/** The blocks, by the name a stored page names them with. */
|
|
240
|
+
components: Record<string, AsheeBlock<never>>;
|
|
241
|
+
|
|
242
|
+
/** The categories the blocks are filed under. */
|
|
243
|
+
categories: AsheePuckCategories;
|
|
244
|
+
|
|
245
|
+
/** The page shell every stored page renders into. */
|
|
246
|
+
root: AsheeBlock<AsheePuckRootProps>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* One block in a stored page.
|
|
251
|
+
*
|
|
252
|
+
* The shape is the builder's own serialized document, and it is described here rather
|
|
253
|
+
* than imported so that reading a stored page needs nothing from the editor. A drop
|
|
254
|
+
* zone's children are stored under the zone's own key in `props`, which is why `props`
|
|
255
|
+
* is an open record: the renderer reads a zone by name.
|
|
256
|
+
*/
|
|
257
|
+
export interface AsheePuckBlockData {
|
|
258
|
+
/** The block's name, which is the key it is registered under. */
|
|
259
|
+
type: string;
|
|
260
|
+
|
|
261
|
+
/** The value the editor stored for the block, including its drop zones. */
|
|
262
|
+
props: Record<string, unknown>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* A stored page, as the editor publishes it.
|
|
267
|
+
*
|
|
268
|
+
* Only the members a renderer reads are stated. A published document carries more — a
|
|
269
|
+
* history, a set of zones — and a renderer that ignored them would be right to: this
|
|
270
|
+
* is the contract a page is *rendered* from.
|
|
271
|
+
*/
|
|
272
|
+
export interface AsheePuckData {
|
|
273
|
+
/** The page's outer value, which the page shell may read. */
|
|
274
|
+
root?: { props?: Record<string, unknown> };
|
|
275
|
+
|
|
276
|
+
/** The blocks of the page, in order. */
|
|
277
|
+
content: AsheePuckBlockData[];
|
|
278
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The web content blocks.
|
|
5
|
+
*
|
|
6
|
+
* Each block is the framework component plus the shared spec: the label, the fields and
|
|
7
|
+
* the defaults come from `src/shared`, and only the drawing is written here. That is
|
|
8
|
+
* what keeps the editor's promise — a block cannot describe one thing and render
|
|
9
|
+
* another — while letting each platform draw with its own components.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Section, Typography } from "@asheeui/web";
|
|
13
|
+
import { SectionHeading } from "@asheeui/web/section-kit";
|
|
14
|
+
import type {
|
|
15
|
+
AsheeBlock,
|
|
16
|
+
HeadingBlockProps,
|
|
17
|
+
TextBlockProps,
|
|
18
|
+
} from "../../shared";
|
|
19
|
+
import { HEADING_SPEC, TEXT_SPEC } from "../../shared";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A section heading with an optional eyebrow and description.
|
|
23
|
+
*/
|
|
24
|
+
export const headingBlock: AsheeBlock<HeadingBlockProps> = {
|
|
25
|
+
...HEADING_SPEC,
|
|
26
|
+
render: ({ eyebrow, title, description, align }) => (
|
|
27
|
+
<Section spacing="md" contained>
|
|
28
|
+
<SectionHeading
|
|
29
|
+
eyebrow={eyebrow || undefined}
|
|
30
|
+
title={title || undefined}
|
|
31
|
+
description={description || undefined}
|
|
32
|
+
align={align ?? "start"}
|
|
33
|
+
size="lg"
|
|
34
|
+
/>
|
|
35
|
+
</Section>
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A paragraph of body text.
|
|
41
|
+
*/
|
|
42
|
+
export const textBlock: AsheeBlock<TextBlockProps> = {
|
|
43
|
+
...TEXT_SPEC,
|
|
44
|
+
render: ({ text, align }) => (
|
|
45
|
+
<Section spacing="sm" contained>
|
|
46
|
+
<Typography
|
|
47
|
+
role="body-lg"
|
|
48
|
+
className={
|
|
49
|
+
align === "center" ? "mx-auto max-w-2xl text-center" : "max-w-2xl"
|
|
50
|
+
}>
|
|
51
|
+
{text}
|
|
52
|
+
</Typography>
|
|
53
|
+
</Section>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The web layout blocks.
|
|
5
|
+
*
|
|
6
|
+
* A band and a two-column arrangement. Both are the only blocks whose props hold a
|
|
7
|
+
* drop zone, so both are written against the builder's own `SlotComponent`: the
|
|
8
|
+
* editor drops a component into the field and the block places it. The fields and the
|
|
9
|
+
* defaults still come from `src/shared`, so the platform offers the same two zones.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { cn } from "@asheeui/core";
|
|
13
|
+
import { Section } from "@asheeui/web";
|
|
14
|
+
import type { SlotComponent } from "@puckeditor/core";
|
|
15
|
+
import type {
|
|
16
|
+
AsheeBlock,
|
|
17
|
+
ColumnsBandProps,
|
|
18
|
+
SectionBandProps,
|
|
19
|
+
} from "../../shared";
|
|
20
|
+
import { COLUMN_RATIO_CLASS, COLUMNS_SPEC, SECTION_SPEC } from "../../shared";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The props of the band block.
|
|
24
|
+
*/
|
|
25
|
+
export type SectionBlockProps = SectionBandProps & {
|
|
26
|
+
/** Content dropped into the band. */
|
|
27
|
+
content?: SlotComponent;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A band with its own vertical rhythm and background.
|
|
32
|
+
*/
|
|
33
|
+
export const sectionBlock: AsheeBlock<SectionBlockProps> = {
|
|
34
|
+
...SECTION_SPEC,
|
|
35
|
+
render: ({ spacing, background, content: Content }) => (
|
|
36
|
+
<Section
|
|
37
|
+
spacing={spacing ?? "lg"}
|
|
38
|
+
background={background ?? "none"}
|
|
39
|
+
contained>
|
|
40
|
+
{Content ? <Content /> : null}
|
|
41
|
+
</Section>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The props of the two-column block.
|
|
47
|
+
*/
|
|
48
|
+
export type ColumnsBlockProps = ColumnsBandProps & {
|
|
49
|
+
/** Content of the first column. */
|
|
50
|
+
start?: SlotComponent;
|
|
51
|
+
|
|
52
|
+
/** Content of the second column. */
|
|
53
|
+
end?: SlotComponent;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Two columns that stack on a narrow screen.
|
|
58
|
+
*/
|
|
59
|
+
export const columnsBlock: AsheeBlock<ColumnsBlockProps> = {
|
|
60
|
+
...COLUMNS_SPEC,
|
|
61
|
+
render: ({ ratio, spacing, start: Start, end: End }) => (
|
|
62
|
+
<Section spacing={spacing ?? "lg"} contained>
|
|
63
|
+
<div
|
|
64
|
+
className={cn(
|
|
65
|
+
"grid grid-cols-1 gap-8",
|
|
66
|
+
COLUMN_RATIO_CLASS[ratio ?? "50/50"],
|
|
67
|
+
)}>
|
|
68
|
+
<div className="min-w-0">{Start ? <Start /> : null}</div>
|
|
69
|
+
<div className="min-w-0">{End ? <End /> : null}</div>
|
|
70
|
+
</div>
|
|
71
|
+
</Section>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The web navigation blocks.
|
|
5
|
+
*
|
|
6
|
+
* A bar and a footer, with the navigation as data: link lists a non-developer can edit
|
|
7
|
+
* and no application state. The bar's action region is the framework's own action
|
|
8
|
+
* group, so a configured action renders exactly as a hand-written button does and
|
|
9
|
+
* inherits the same cascade. The fields and the defaults come from `src/shared`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Footer, Navbar } from "@asheeui/web";
|
|
13
|
+
import { ActionGroup } from "@asheeui/web/section-kit";
|
|
14
|
+
import type {
|
|
15
|
+
AsheeBlock,
|
|
16
|
+
FooterBlockProps,
|
|
17
|
+
NavbarBlockProps,
|
|
18
|
+
} from "../../shared";
|
|
19
|
+
import { FOOTER_SPEC, NAVBAR_SPEC, toAction } from "../../shared";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The navigation bar of a page.
|
|
23
|
+
*/
|
|
24
|
+
export const navbarBlock: AsheeBlock<NavbarBlockProps> = {
|
|
25
|
+
...NAVBAR_SPEC,
|
|
26
|
+
render: ({
|
|
27
|
+
brand,
|
|
28
|
+
brandHref,
|
|
29
|
+
align,
|
|
30
|
+
links,
|
|
31
|
+
primaryAction,
|
|
32
|
+
secondaryAction,
|
|
33
|
+
}) => (
|
|
34
|
+
<Navbar
|
|
35
|
+
brand={brand || undefined}
|
|
36
|
+
brandHref={brandHref || undefined}
|
|
37
|
+
align={align ?? "center"}
|
|
38
|
+
links={(links ?? []).map((link) => ({
|
|
39
|
+
label: link.label ?? "",
|
|
40
|
+
href: link.href,
|
|
41
|
+
}))}
|
|
42
|
+
actions={
|
|
43
|
+
<ActionGroup
|
|
44
|
+
primaryAction={toAction(primaryAction)}
|
|
45
|
+
secondaryAction={toAction(secondaryAction)}
|
|
46
|
+
/>
|
|
47
|
+
}
|
|
48
|
+
/>
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The footer of a page.
|
|
54
|
+
*/
|
|
55
|
+
export const footerBlock: AsheeBlock<FooterBlockProps> = {
|
|
56
|
+
...FOOTER_SPEC,
|
|
57
|
+
render: ({ brand, description, groups, copyright }) => (
|
|
58
|
+
<Footer
|
|
59
|
+
brand={brand || undefined}
|
|
60
|
+
description={description || undefined}
|
|
61
|
+
copyright={copyright || undefined}
|
|
62
|
+
groups={(groups ?? []).map((group, index) => ({
|
|
63
|
+
id: index,
|
|
64
|
+
title: group.title ?? "Links",
|
|
65
|
+
links: (group.links ?? []).map((item) => ({
|
|
66
|
+
label: item.label ?? "",
|
|
67
|
+
href: item.href,
|
|
68
|
+
})),
|
|
69
|
+
}))}
|
|
70
|
+
/>
|
|
71
|
+
),
|
|
72
|
+
};
|