camox 0.42.0 → 0.43.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/dist/core/components/lexical/{GradientNode.js → HighlightNode.js} +34 -34
- package/dist/core/components/lexical/InlineLexicalEditor.js +14 -14
- package/dist/core/components/lexical/InlineStylesPlugin.js +6 -6
- package/dist/core/components/lexical/SelectionBroadcaster.js +5 -5
- package/dist/core/components/lexical/SidebarLexicalEditor.js +20 -20
- package/dist/core/components/lexical/TextLinkPopover.js +191 -208
- package/dist/core/components/lexical/editorConfig.js +2 -2
- package/dist/core/createApp.d.ts +3 -2
- package/dist/core/createApp.js +2 -1
- package/dist/core/createLayout.d.ts +16 -3
- package/dist/core/createLayout.js +15 -15
- package/dist/core/derivedRoutes.js +1 -1
- package/dist/core/dom.d.ts +17 -0
- package/dist/core/dom.js +22 -0
- package/dist/core/lib/lexicalReact.js +4 -4
- package/dist/core/lib/lexicalState.js +3 -3
- package/dist/core/lib/modifierFormats.js +1 -1
- package/dist/core/lib/textLinks.js +5 -2
- package/dist/core/lib/textStyles.d.ts +2 -2
- package/dist/core/lib/textStyles.js +6 -11
- package/dist/core/pageDestinations.js +37 -0
- package/dist/features/og/imageResponse.node.js +11 -9
- package/dist/features/preview/CamoxPreview.js +42 -65
- package/dist/features/preview/components/AddBlockSidebar.js +1 -1
- package/dist/features/preview/components/BlockActionsPopover.js +1 -1
- package/dist/features/preview/components/CreatePageModal.js +4 -4
- package/dist/features/preview/components/DerivedLayoutSidebar.js +45 -26
- package/dist/features/preview/components/DerivedPageInfoSidebar.js +38 -26
- package/dist/features/preview/components/FieldToolbar.js +7 -7
- package/dist/features/preview/components/LinkFieldEditor.js +212 -209
- package/dist/features/preview/components/PageEditorSidebar.js +1 -1
- package/dist/features/preview/components/PageInfoSidebar.js +28 -28
- package/dist/features/preview/components/PagePicker.js +16 -16
- package/dist/features/preview/components/PageTree.js +1 -1
- package/dist/features/preview/components/PublicationRow.js +8 -4
- package/dist/features/preview/components/useRepeatableItemActions.js +1 -1
- package/dist/features/preview/publication.js +10 -0
- package/dist/features/provider/CoreCamoxProvider.js +1 -1
- package/dist/features/runtime/runtime.js +20 -21
- package/dist/features/studio/components/EnvironmentMenu.js +3 -2
- package/dist/features/studio/components/Navbar.js +53 -80
- package/dist/features/vite/layoutGeneration.js +7 -3
- package/dist/features/vite/vite.js +5 -1
- package/dist/hooks/use-page-destinations.js +53 -0
- package/dist/lib/auth.js +11 -14
- package/dist/lib/server-auth-cookie.js +21 -0
- package/dist/studio.css +1 -1
- package/package.json +8 -4
- package/skills/camox/SKILL.md +32 -9
- package/skills/camox/references/block-definitions.md +84 -290
- package/skills/camox/references/cli-common.md +2 -4
- package/skills/camox/references/cli-content.md +147 -0
- package/skills/camox/references/cli-environments.md +1 -1
- package/skills/camox/references/dom-integrations.md +93 -0
- package/skills/camox/references/field-styling.md +50 -0
- package/skills/camox/references/layout-definitions.md +33 -147
- package/skills/camox/references/routed-layouts.md +71 -0
- package/skills/camoxify/SKILL.md +52 -0
- package/skills/camox/references/cli-blocks.md +0 -78
- package/skills/camox/references/cli-pages.md +0 -53
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
# Creating Camox Block Definitions
|
|
2
2
|
|
|
3
|
-
A block
|
|
3
|
+
A block definition specifies a reusable section's schema and rendering, not an instance's content.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
A definition only adds a new _type_ of block to the catalog — it doesn't put one on any Page. Users almost never just want "a definition"; they want a section that actually shows up on the site. Once the Block Definition exists and the dev server has picked it up, follow the umbrella skill's CLI routing to create a Block on the relevant Page. For shared chrome, follow the Layout Definition reference instead. If the user asks for "a hero on the homepage", define the Block Definition and then create the hero Block on the home Page. Don't stop after the definition unless the user explicitly requested only the reusable type.
|
|
5
|
+
When asked to add a section, also place an instance after dev-server discovery using the [CLI workflow](../SKILL.md#content-and-cli). For shared chrome, use [Layout Definitions](layout-definitions.md) instead. Defining a type alone does not place it on a Page.
|
|
8
6
|
|
|
9
7
|
## Quick Start
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Use one `.tsx` file per block in `src/blocks/`, a named `block` export (not default), a named function component, and Tailwind styling.
|
|
12
10
|
|
|
13
11
|
```tsx
|
|
14
12
|
import { Type, createBlock } from "camox/createBlock";
|
|
15
13
|
|
|
16
14
|
const myBlock = createBlock({
|
|
17
|
-
id: "my-block",
|
|
18
|
-
title: "My Block",
|
|
19
|
-
description: "
|
|
15
|
+
id: "my-block",
|
|
16
|
+
title: "My Block",
|
|
17
|
+
description: "A page introduction with an editable heading.",
|
|
20
18
|
content: {
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
settings: {
|
|
24
|
-
/* ... */
|
|
25
|
-
}, // Optional config toggles
|
|
19
|
+
title: Type.String({ default: "Welcome" }),
|
|
20
|
+
},
|
|
26
21
|
component: MyBlockComponent,
|
|
27
|
-
toMarkdown: (c) => [`# ${c.title}
|
|
22
|
+
toMarkdown: (c) => [`# ${c.title}`],
|
|
28
23
|
});
|
|
29
24
|
|
|
30
25
|
function MyBlockComponent() {
|
|
@@ -38,132 +33,83 @@ function MyBlockComponent() {
|
|
|
38
33
|
export { myBlock as block };
|
|
39
34
|
```
|
|
40
35
|
|
|
36
|
+
For DOM listeners, scrolling, scripts, or widgets, read [DOM integrations](dom-integrations.md). Preview DOM lives in an iframe; use `getElementContext` from `camox/dom`, not component-global `window` or `document`.
|
|
37
|
+
|
|
41
38
|
## The `createBlock` options
|
|
42
39
|
|
|
43
|
-
| Option | Required | Description
|
|
44
|
-
| ------------- | -------- |
|
|
45
|
-
| `id` | yes | Unique kebab-case identifier
|
|
46
|
-
| `title` | yes |
|
|
47
|
-
| `description` | yes |
|
|
48
|
-
| `content` | yes |
|
|
49
|
-
| `settings` | no |
|
|
50
|
-
| `layoutOnly` | no | If
|
|
51
|
-
| `synced` | no |
|
|
52
|
-
| `component` | yes |
|
|
53
|
-
| `toMarkdown` | yes |
|
|
40
|
+
| Option | Required | Description |
|
|
41
|
+
| ------------- | -------- | --------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `id` | yes | Unique kebab-case identifier matching the filename without extension. |
|
|
43
|
+
| `title` | yes | CMS display name. |
|
|
44
|
+
| `description` | yes | Agent guidance on placement, expected content, and tone. |
|
|
45
|
+
| `content` | yes | Field names mapped to `Type.*` schemas for editable content. |
|
|
46
|
+
| `settings` | no | Settings-panel fields; only `Type.Enum` and `Type.Boolean`. |
|
|
47
|
+
| `layoutOnly` | no | If true, restricts placement to layouts and hides the type from the add-block picker. |
|
|
48
|
+
| `synced` | no | Defaults to false. Shares content/settings across instances within an environment; see below. |
|
|
49
|
+
| `component` | yes | Named React function component. |
|
|
50
|
+
| `toMarkdown` | yes | `(c, s) => [...]` markdown builder; see below. |
|
|
54
51
|
|
|
55
52
|
## Synced blocks (`synced`, optional)
|
|
56
53
|
|
|
57
|
-
`
|
|
54
|
+
`synced: true` shares content and settings, including nested repeater items and their settings, across every instance of the block **type** within one environment. Dev and production remain independent. Synced blocks have purple editor highlights.
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
For shared navbar/footer data across layouts, use `layoutOnly: true, synced: true`. These flags are independent: synced blocks can also be ordinary page-content blocks.
|
|
60
57
|
|
|
61
|
-
- Editing any instance updates the shared draft
|
|
62
|
-
-
|
|
63
|
-
- Publishing a page or layout containing the block publishes
|
|
64
|
-
- Enabling syncing on an existing type
|
|
65
|
-
- Synced blocks have **purple highlights and overlays** in the editor.
|
|
58
|
+
- Editing any instance updates the shared draft everywhere. New instances reuse existing data rather than resetting to defaults.
|
|
59
|
+
- Position, placement, and deletion remain per instance; deleting one placement leaves the others.
|
|
60
|
+
- Publishing a page or layout containing the block publishes shared data for all live instances. Unpublished drafts do not leak into live.
|
|
61
|
+
- Enabling syncing on an existing type applies the oldest instance's data to all instances. Disabling it leaves independently editable copies.
|
|
66
62
|
|
|
67
63
|
## Markdown Template (`toMarkdown`)
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
Required on blocks and repeaters. Receives typed proxies `c` (content) and `s` (settings); returns entries joined with `\n\n` for AI summaries and SEO. Lines whose referenced fields are all empty, or whose condition is false, are omitted.
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
- `s` — a proxy typed on your `settings` keys, used to wrap lines in conditionals
|
|
73
|
-
|
|
74
|
-
…and returns an array of entries. Each entry becomes a paragraph (joined with `\n\n`).
|
|
75
|
-
|
|
76
|
-
A bare `c.fieldName` is the default for single-field lines. Use template literals when combining fields or adding markdown syntax:
|
|
67
|
+
Use bare `c.field` for single-field lines and template literals for markdown or combined fields:
|
|
77
68
|
|
|
78
69
|
```tsx
|
|
79
|
-
toMarkdown: (c) => [
|
|
70
|
+
toMarkdown: (c) => [`> ${c.quote}`, `— ${c.author}, ${c.company}`, c.photo];
|
|
80
71
|
```
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- **String**: raw text value
|
|
85
|
-
- **Link**: `[text](href)`
|
|
86
|
-
- **Image**: ``
|
|
87
|
-
- **File**: `[filename](url)`
|
|
88
|
-
- **Embed**: raw URL string
|
|
89
|
-
- **Repeater**: each item rendered via its own `toMarkdown` (if set on the Repeater options), items joined with `\n\n`
|
|
90
|
-
- **ImageList / FileList**: each asset rendered as a markdown bullet
|
|
91
|
-
- **Boolean/Enum**: raw string value
|
|
92
|
-
|
|
93
|
-
Lines where ALL referenced fields resolve to empty are omitted from output.
|
|
94
|
-
|
|
95
|
-
**Examples:**
|
|
96
|
-
|
|
97
|
-
```tsx
|
|
98
|
-
// Hero block
|
|
99
|
-
createBlock({
|
|
100
|
-
toMarkdown: (c) => [`# ${c.title}`, c.description, c.illustration, c.cta],
|
|
101
|
-
content: { ... },
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// Testimonial — combine fields on one line
|
|
105
|
-
createBlock({
|
|
106
|
-
toMarkdown: (c) => [`> ${c.quote}`, `— ${c.author}, ${c.title}, ${c.company}`],
|
|
107
|
-
content: { ... },
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// Statistics — Repeater with its own toMarkdown
|
|
111
|
-
createBlock({
|
|
112
|
-
toMarkdown: (c) => [`## ${c.subtitle}`, c.description, c.statistics],
|
|
113
|
-
content: {
|
|
114
|
-
subtitle: Type.String({ default: "..." }),
|
|
115
|
-
description: Type.String({ default: "..." }),
|
|
116
|
-
statistics: Type.Repeater({
|
|
117
|
-
content: {
|
|
118
|
-
number: Type.String({ default: "100M+" }),
|
|
119
|
-
label: Type.String({ default: "pages served" }),
|
|
120
|
-
},
|
|
121
|
-
minItems: 4,
|
|
122
|
-
maxItems: 8,
|
|
123
|
-
toMarkdown: (c) => [`**${c.number}** — ${c.label}`],
|
|
124
|
-
}),
|
|
125
|
-
},
|
|
126
|
-
})
|
|
127
|
-
```
|
|
73
|
+
Field resolution:
|
|
128
74
|
|
|
129
|
-
|
|
75
|
+
| Type | Markdown |
|
|
76
|
+
| -------------------- | ------------------------------------------------ |
|
|
77
|
+
| String | Raw text |
|
|
78
|
+
| Link | `[text](href)` |
|
|
79
|
+
| Image | `` |
|
|
80
|
+
| File | `[filename](url)` |
|
|
81
|
+
| Embed | Raw URL |
|
|
82
|
+
| Repeater | Each item's own `toMarkdown`, joined with `\n\n` |
|
|
83
|
+
| ImageList / FileList | One markdown bullet per asset |
|
|
84
|
+
| Boolean / Enum | Raw string value |
|
|
130
85
|
|
|
131
86
|
### Conditional lines from settings
|
|
132
87
|
|
|
133
|
-
|
|
88
|
+
Boolean settings wrap one line or an array; Enum settings match a variant:
|
|
134
89
|
|
|
135
90
|
```tsx
|
|
136
|
-
// Boolean setting — include a line only when the setting is true
|
|
137
|
-
toMarkdown: (c, s) => [`# ${c.title}`, c.description, s.showCta(c.cta)];
|
|
138
|
-
|
|
139
|
-
// Boolean wrapping multiple lines — all included together, or all dropped
|
|
140
|
-
toMarkdown: (c, s) => [
|
|
141
|
-
`# ${c.title}`,
|
|
142
|
-
s.showDetails([c.subtitle, c.description, c.backgroundImage]),
|
|
143
|
-
c.cta,
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
// Enum — emit different lines depending on the selected variant
|
|
147
91
|
toMarkdown: (c, s) => [
|
|
148
92
|
`# ${c.title}`,
|
|
93
|
+
s.showCta(c.cta),
|
|
94
|
+
s.showDetails([c.subtitle, c.description]),
|
|
149
95
|
s.variant("banner", `**${c.headline}** — ${c.subtext}`),
|
|
150
96
|
s.variant("inline", `${c.headline}: ${c.cta}`),
|
|
151
97
|
];
|
|
152
98
|
```
|
|
153
99
|
|
|
154
|
-
Only
|
|
100
|
+
Only Boolean/Enum settings support these conditionals. A repeater's `s` refers to the item's settings, not the parent block's.
|
|
155
101
|
|
|
156
102
|
## Content Field Types
|
|
157
103
|
|
|
158
|
-
Import `Type` from `"camox/createBlock"`.
|
|
104
|
+
Import `Type` from `"camox/createBlock"`. Supply defaults for scalar fields; images/files get automatic placeholders, lists use `defaultItems`, and repeaters use their item schemas and bounds.
|
|
159
105
|
|
|
160
106
|
### Type.String
|
|
161
107
|
|
|
162
|
-
Inline-editable text
|
|
108
|
+
Inline-editable text supporting formatting and links while remaining a string. See [Field styling](field-styling.md) for formatting syntax and appearance customization.
|
|
163
109
|
|
|
164
110
|
```tsx
|
|
165
111
|
Type.String({
|
|
166
|
-
default: "Hello world",
|
|
112
|
+
default: "Hello world",
|
|
167
113
|
title: "Heading", // Optional label
|
|
168
114
|
maxLength: 280, // Optional
|
|
169
115
|
minLength: 1, // Optional
|
|
@@ -171,19 +117,12 @@ Type.String({
|
|
|
171
117
|
});
|
|
172
118
|
```
|
|
173
119
|
|
|
174
|
-
### Type.Boolean
|
|
120
|
+
### Type.Boolean and Type.Enum
|
|
175
121
|
|
|
176
|
-
|
|
122
|
+
Use in `settings` for configuration or `content` for editable values. Enum defaults must match an option key.
|
|
177
123
|
|
|
178
124
|
```tsx
|
|
179
125
|
Type.Boolean({ default: false, title: "Show background" });
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Type.Enum
|
|
183
|
-
|
|
184
|
-
A dropdown with predefined options. Most commonly used in `settings`.
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
126
|
Type.Enum({
|
|
188
127
|
default: "left",
|
|
189
128
|
options: { left: "Left", center: "Center", right: "Right" },
|
|
@@ -191,11 +130,9 @@ Type.Enum({
|
|
|
191
130
|
});
|
|
192
131
|
```
|
|
193
132
|
|
|
194
|
-
The `default` must be one of the keys in `options`.
|
|
195
|
-
|
|
196
133
|
### Type.Link
|
|
197
134
|
|
|
198
|
-
|
|
135
|
+
Text, destination, and new-tab toggle. Curated page links store page IDs; singleton links store fixed URLs using the existing `type: "external"` representation, which also supports site-relative URLs: `{ type: "external", href: "/pokedex", text: "Pokédex", newTab: false }`. Both page kinds appear in Studio's destination picker. Inline `[Pokédex](/pokedex)` links stay in the current tab.
|
|
199
136
|
|
|
200
137
|
```tsx
|
|
201
138
|
Type.Link({
|
|
@@ -204,48 +141,20 @@ Type.Link({
|
|
|
204
141
|
});
|
|
205
142
|
```
|
|
206
143
|
|
|
207
|
-
###
|
|
144
|
+
### Images and files
|
|
208
145
|
|
|
209
|
-
|
|
146
|
+
Files support MIME filtering. Render each type with its matching helper; asset lists are **not** repeaters.
|
|
210
147
|
|
|
211
148
|
```tsx
|
|
212
149
|
Type.Image({ title: "Cover photo" });
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Type.ImageList
|
|
216
|
-
|
|
217
|
-
A flat array of images — render with `block.ImageList` (NOT `block.Repeater`).
|
|
218
|
-
|
|
219
|
-
```tsx
|
|
220
150
|
Type.ImageList({ defaultItems: 6, title: "Gallery images" });
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
### Type.File
|
|
224
|
-
|
|
225
|
-
A single file upload, with MIME type filtering — render with `block.File`.
|
|
226
|
-
|
|
227
|
-
```tsx
|
|
228
|
-
Type.File({
|
|
229
|
-
accept: ["application/pdf"],
|
|
230
|
-
title: "PDF Document",
|
|
231
|
-
});
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Type.FileList
|
|
235
|
-
|
|
236
|
-
A flat array of files — render with `block.FileList` (NOT `block.Repeater`).
|
|
237
|
-
|
|
238
|
-
```tsx
|
|
239
|
-
Type.FileList({
|
|
240
|
-
accept: ["application/pdf"],
|
|
241
|
-
defaultItems: 0,
|
|
242
|
-
title: "Documents",
|
|
243
|
-
});
|
|
151
|
+
Type.File({ accept: ["application/pdf"], title: "PDF Document" });
|
|
152
|
+
Type.FileList({ accept: ["application/pdf"], defaultItems: 0, title: "Documents" });
|
|
244
153
|
```
|
|
245
154
|
|
|
246
155
|
### Type.Embed
|
|
247
156
|
|
|
248
|
-
A URL validated against a regex
|
|
157
|
+
A URL validated against a regex. A nonmatching default throws at definition time.
|
|
249
158
|
|
|
250
159
|
```tsx
|
|
251
160
|
Type.Embed({
|
|
@@ -255,11 +164,9 @@ Type.Embed({
|
|
|
255
164
|
});
|
|
256
165
|
```
|
|
257
166
|
|
|
258
|
-
The `default` must match the `pattern` — an error is thrown at definition time otherwise.
|
|
259
|
-
|
|
260
167
|
### Type.Repeater
|
|
261
168
|
|
|
262
|
-
|
|
169
|
+
Structured items with their own content, optional settings, and required `toMarkdown`. `minItems` must be at least 1; repeaters cannot be empty. Nest repeaters in an item's `content` when needed.
|
|
263
170
|
|
|
264
171
|
```tsx
|
|
265
172
|
Type.Repeater({
|
|
@@ -267,91 +174,39 @@ Type.Repeater({
|
|
|
267
174
|
name: Type.String({ default: "Feature" }),
|
|
268
175
|
description: Type.String({ default: "Description" }),
|
|
269
176
|
},
|
|
270
|
-
minItems: 1,
|
|
177
|
+
minItems: 1,
|
|
271
178
|
maxItems: 10,
|
|
272
179
|
title: "Features",
|
|
273
|
-
toMarkdown: (c) => [`### ${c.name}`, c.description],
|
|
180
|
+
toMarkdown: (c) => [`### ${c.name}`, c.description],
|
|
274
181
|
});
|
|
275
182
|
```
|
|
276
183
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Repeaters can be nested — an item can contain another Repeater:
|
|
280
|
-
|
|
281
|
-
```tsx
|
|
282
|
-
columns: Type.Repeater({
|
|
283
|
-
content: {
|
|
284
|
-
title: Type.String({ default: "Column" }),
|
|
285
|
-
links: Type.Repeater({
|
|
286
|
-
content: {
|
|
287
|
-
link: Type.Link({ default: { text: "Link", href: "#", newTab: false } }),
|
|
288
|
-
},
|
|
289
|
-
minItems: 1,
|
|
290
|
-
maxItems: 999,
|
|
291
|
-
toMarkdown: (c) => [c.link],
|
|
292
|
-
}),
|
|
293
|
-
},
|
|
294
|
-
minItems: 2,
|
|
295
|
-
maxItems: 4,
|
|
296
|
-
title: "Columns",
|
|
297
|
-
toMarkdown: (c) => [`### ${c.title}`, c.links],
|
|
298
|
-
});
|
|
299
|
-
```
|
|
184
|
+
Reference the repeater field in the parent block's `toMarkdown` to include its rendered items.
|
|
300
185
|
|
|
301
186
|
## Rendering in the Component
|
|
302
187
|
|
|
303
|
-
|
|
188
|
+
Field renderers receive `(props, data)`:
|
|
304
189
|
|
|
305
|
-
-
|
|
306
|
-
-
|
|
190
|
+
- **Spread `props` onto the element** to preserve editor refs, attributes, and handlers, plus field-specific values such as `children`, `to`, `src`, and `alt`.
|
|
191
|
+
- `data` is the raw field value for custom logic, not an object to spread. Prefer `props`; use raw data only where needed.
|
|
307
192
|
|
|
308
|
-
###
|
|
193
|
+
### Strings — `block.Field`
|
|
309
194
|
|
|
310
|
-
|
|
311
|
-
<myBlock.Field name="title">{(props) => <h1 {...props} />}</myBlock.Field>
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
The `name` must match a key in `content` that is a `Type.String`. Spread `props` onto the element — `props.children` contains the rendered content. This is what makes the field inline-editable in the CMS.
|
|
315
|
-
|
|
316
|
-
Camox owns inline markup so editing and published output have the same appearance. Customize appearance with `textStyle` and `linkStyle` (these replace the old `components` prop):
|
|
195
|
+
`name` must select a `Type.String` field. `props.children` contains Camox's rendered inline markup.
|
|
317
196
|
|
|
318
197
|
```tsx
|
|
319
|
-
<myBlock.Field
|
|
320
|
-
name="description"
|
|
321
|
-
textStyle={({ bold, italic, gradient }) => {
|
|
322
|
-
if (gradient) {
|
|
323
|
-
return { style: { backgroundImage: "linear-gradient(to right, orange, deeppink)" } };
|
|
324
|
-
}
|
|
325
|
-
return {
|
|
326
|
-
className: bold ? "text-primary" : undefined,
|
|
327
|
-
style: bold && italic ? { letterSpacing: "0.02em" } : undefined,
|
|
328
|
-
};
|
|
329
|
-
}}
|
|
330
|
-
linkStyle={({ external }) => ({
|
|
331
|
-
className: external ? "text-blue-600" : "text-primary font-medium",
|
|
332
|
-
})}
|
|
333
|
-
>
|
|
334
|
-
{(props) => <p {...props} />}
|
|
335
|
-
</myBlock.Field>
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
- `textStyle` receives `{ bold, italic, underline, gradient }` and returns `{ className?, style? }` or `undefined`. For text runs, `gradient` is false and the other flags describe explicit content formatting, not inherited heading styles. Unmarked text keeps its parent's typography.
|
|
339
|
-
- `linkStyle` receives `{ target, href, external, pageId }` and returns the same appearance-only object. Links are underlined by default; override `style.textDecorationLine` to change that.
|
|
340
|
-
- Camox also calls `textStyle` once for each shared gradient wrapper with `{ gradient: true, bold: false, italic: false, underline: false }`. That result styles the whole range; its children receive their own text-run calls. `gradient` identifies the wrapper being styled, not whether a run is inside a gradient. This keeps one continuous background when bold/italic formatting changes within the range. The default gradient uses the site's shadcn chart palette: `--chart-1` → `--chart-2`, following light/dark theme changes. Missing chart tokens fall back to `--primary` / `--muted-foreground`, then `currentColor`, never a hardcoded palette. Set `--camox-gradient-from` and `--camox-gradient-to` in app CSS for app-wide colors, or return `style.backgroundImage` when `gradient` is true.
|
|
341
|
-
- Styles apply to inline children; the outer element's `className` stays on that element and is inherited normally. Explicit formatting defaults (combined italic, underline, gradient clipping) use inline styles; use `style` to override those defaults rather than competing utility classes.
|
|
342
|
-
- These props accept appearance only, never replacement elements, children, or event handlers. They also work on `item.Field` inside repeaters. The sidebar editor shows default formatting; field-specific styles apply on the page.
|
|
343
|
-
|
|
344
|
-
When the content needs to be placed inside a more complex structure, use `props.children` explicitly:
|
|
198
|
+
<myBlock.Field name="title">{(props) => <h1 {...props} />}</myBlock.Field>
|
|
345
199
|
|
|
346
|
-
```tsx
|
|
347
200
|
<myBlock.Field name="quote">
|
|
348
201
|
{(props) => <blockquote {...props}>"{props.children}"</blockquote>}
|
|
349
202
|
</myBlock.Field>
|
|
350
203
|
```
|
|
351
204
|
|
|
352
|
-
|
|
205
|
+
For `textStyle` and `linkStyle`, read [Field styling](field-styling.md).
|
|
206
|
+
|
|
207
|
+
### Links — `block.Link`
|
|
353
208
|
|
|
354
|
-
|
|
209
|
+
Use `Link` from `camox/navigation`, not `<a>`, for client-side internal navigation. `props` supplies `to`, `target`, and `rel`; raw `data` exposes `{ text, href, newTab }`.
|
|
355
210
|
|
|
356
211
|
```tsx
|
|
357
212
|
import { Link } from "camox/navigation";
|
|
@@ -359,51 +214,21 @@ import { Link } from "camox/navigation";
|
|
|
359
214
|
<myBlock.Link name="cta">{(props) => <Link {...props} />}</myBlock.Link>;
|
|
360
215
|
```
|
|
361
216
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
```tsx
|
|
365
|
-
<myBlock.Link name="cta">
|
|
366
|
-
{(props, { href }) => <Link {...props} className={href === "/" ? "active" : ""} />}
|
|
367
|
-
</myBlock.Link>
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### Rendering Image fields — `block.Image`
|
|
217
|
+
### Images, files, and embeds
|
|
371
218
|
|
|
372
219
|
```tsx
|
|
373
220
|
<myBlock.Image name="cover">{(props) => <img {...props} />}</myBlock.Image>
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
`props` includes `src` and `alt`. The optional `data` argument exposes the raw `ImageValue` for cases like background images:
|
|
377
|
-
|
|
378
|
-
```tsx
|
|
379
|
-
<myBlock.Image name="cover">
|
|
380
|
-
{(_props, { url }) => <div style={{ backgroundImage: `url(${url})` }} />}
|
|
381
|
-
</myBlock.Image>
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
### Rendering File fields — `block.File`
|
|
385
|
-
|
|
386
|
-
```tsx
|
|
387
221
|
<myBlock.File name="document">{(props) => <a {...props}>Download</a>}</myBlock.File>
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
`props` includes `href` and `download` (filename). The optional `data` argument exposes the raw `FileValue`.
|
|
391
|
-
|
|
392
|
-
### Rendering Embed fields — `block.Embed`
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
222
|
<myBlock.Embed name="videoUrl">{(props) => <iframe {...props} />}</myBlock.Embed>
|
|
396
223
|
```
|
|
397
224
|
|
|
398
|
-
`props` includes `src
|
|
225
|
+
- Image: `props` includes `src`/`alt`; `data` is `ImageValue`.
|
|
226
|
+
- File: `props` includes `href`/`download` (filename); `data` is `FileValue`.
|
|
227
|
+
- Embed: `props` includes `src`; `data` is `{ url }`, available for URL transformation.
|
|
399
228
|
|
|
400
|
-
|
|
401
|
-
<myBlock.Embed name="videoUrl">
|
|
402
|
-
{(_props, { url }) => <iframe src={transformUrl(url)} />}
|
|
403
|
-
</myBlock.Embed>
|
|
404
|
-
```
|
|
229
|
+
### Repeaters — `block.Repeater`
|
|
405
230
|
|
|
406
|
-
|
|
231
|
+
The callback receives an item-scoped API with the same `.Field`, `.Link`, `.Image`, `.File`, `.Embed`, `.ImageList`, `.FileList`, and `.Repeater` methods.
|
|
407
232
|
|
|
408
233
|
```tsx
|
|
409
234
|
<myBlock.Repeater name="features">
|
|
@@ -416,53 +241,35 @@ The optional second argument `data` exposes raw values `{ text, href, newTab }`
|
|
|
416
241
|
</myBlock.Repeater>
|
|
417
242
|
```
|
|
418
243
|
|
|
419
|
-
|
|
244
|
+
Nested repeaters use the item-scoped `.Repeater`:
|
|
420
245
|
|
|
421
246
|
```tsx
|
|
422
247
|
<footer.Repeater name="columns">
|
|
423
248
|
{(column) => (
|
|
424
249
|
<column.Repeater name="links">
|
|
425
|
-
{(
|
|
250
|
+
{(item) => <item.Link name="link">{(props) => <Link {...props} />}</item.Link>}
|
|
426
251
|
</column.Repeater>
|
|
427
252
|
)}
|
|
428
253
|
</footer.Repeater>
|
|
429
254
|
```
|
|
430
255
|
|
|
431
|
-
###
|
|
256
|
+
### Asset lists — `block.ImageList` / `block.FileList`
|
|
432
257
|
|
|
433
|
-
|
|
258
|
+
Use the matching helper, **not `block.Repeater`**. Its `(props, data)` callback runs once per asset, with the same shape as `.Image` / `.File`. These helpers also work on repeater items.
|
|
434
259
|
|
|
435
260
|
```tsx
|
|
436
|
-
// Top-level
|
|
437
261
|
<gallery.ImageList name="images">
|
|
438
262
|
{(props) => <img {...props} className="rounded-lg" />}
|
|
439
263
|
</gallery.ImageList>
|
|
440
|
-
|
|
441
|
-
// Nested inside a Type.Repeater
|
|
442
|
-
<paragraphGrid.Repeater name="paragraphs">
|
|
443
|
-
{(item) => (
|
|
444
|
-
<item.ImageList name="logos">
|
|
445
|
-
{(props) => <img {...props} className="size-10 object-contain" />}
|
|
446
|
-
</item.ImageList>
|
|
447
|
-
)}
|
|
448
|
-
</paragraphGrid.Repeater>
|
|
449
264
|
```
|
|
450
265
|
|
|
451
|
-
|
|
266
|
+
### Settings — `block.useSetting`
|
|
452
267
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
```tsx
|
|
456
|
-
function MyComponent() {
|
|
457
|
-
const theme = myBlock.useSetting("theme");
|
|
458
|
-
const compact = myBlock.useSetting("compact");
|
|
459
|
-
// Use these values in your JSX for conditional rendering/styling
|
|
460
|
-
}
|
|
461
|
-
```
|
|
268
|
+
Read settings inside the component with `myBlock.useSetting("theme")`.
|
|
462
269
|
|
|
463
270
|
### Detached rendering — `block.Detached`
|
|
464
271
|
|
|
465
|
-
Renders
|
|
272
|
+
Renders outside the block's DOM container for fixed/floating elements. Spread its `props` (`ref`, `onClick`, `onMouseEnter`, `onMouseLeave`) onto the root element.
|
|
466
273
|
|
|
467
274
|
```tsx
|
|
468
275
|
<myBlock.Detached>
|
|
@@ -473,16 +280,3 @@ Renders content outside the block's DOM container. Useful for fixed/floating ele
|
|
|
473
280
|
)}
|
|
474
281
|
</myBlock.Detached>
|
|
475
282
|
```
|
|
476
|
-
|
|
477
|
-
## Rules and Conventions
|
|
478
|
-
|
|
479
|
-
1. **File = one block.** One `.tsx` file per block in `src/blocks/`. The `id` must match the filename (without `.tsx`).
|
|
480
|
-
2. **Named export as `block`.** Always: `export { myVar as block }`. Not a default export.
|
|
481
|
-
3. **Named function component.** Use `function MyComponent()`, not an arrow function. Reference it in `createBlock` before its declaration is fine (hoisting).
|
|
482
|
-
4. **All fields need defaults.** Every `Type.*` call requires a default value (images and files get automatic placeholders).
|
|
483
|
-
5. **Description is for the AI.** Write the `description` as guidance for an LLM — explain when to use this block, what kind of content it's for, and where it fits on a page.
|
|
484
|
-
6. **`toMarkdown` is required.** Every block must define how its content renders as markdown. Use the builder function `(c) => [...]` and reference content fields via `c.fieldName`.
|
|
485
|
-
7. **Settings = Enum and Boolean only.** Keep settings simple. Use `content` for everything the user edits inline.
|
|
486
|
-
8. **Repeater minItems >= 1.** You can't have an empty repeater — there's always at least one item.
|
|
487
|
-
9. **Import path is `"camox/createBlock"`.** Both `Type` and `createBlock` come from this import.
|
|
488
|
-
10. **Use Tailwind CSS for styling.** All example blocks use Tailwind utility classes. Follow the same patterns: `container mx-auto px-4` for centered content, responsive breakpoints, etc.
|
|
@@ -18,15 +18,13 @@ In the examples below, replace `{{CAMOX_CMD}}` with that command.
|
|
|
18
18
|
|
|
19
19
|
## Discover the current command surface
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Before a write, verify the relevant subcommand's `--help` unless already checked for the installed version in this session:
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
{{CAMOX_CMD}} --help
|
|
25
|
-
{{CAMOX_CMD}} pages --help
|
|
26
24
|
{{CAMOX_CMD}} pages create --help
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
Use root or group help only to discover unfamiliar commands. Reuse verified help within the session; recheck if the installed version changes. Installed help overrides these examples—do not guess commands or flags.
|
|
30
28
|
|
|
31
29
|
## Draft-first workflow
|
|
32
30
|
|