camox 0.0.0 → 0.1.2-alpha.2
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.md +110 -0
- package/dist/components/AuthGate.d.ts +7 -0
- package/dist/components/AuthGate.d.ts.map +1 -0
- package/dist/core/components/AddBlockControlBar.d.ts +9 -0
- package/dist/core/components/AddBlockControlBar.d.ts.map +1 -0
- package/dist/core/components/AddBlockControlBar.js +65 -0
- package/dist/core/components/lexical/InlineContentEditable.d.ts +7 -0
- package/dist/core/components/lexical/InlineContentEditable.d.ts.map +1 -0
- package/dist/core/components/lexical/InlineContentEditable.js +40 -0
- package/dist/core/components/lexical/InlineLexicalEditor.d.ts +12 -0
- package/dist/core/components/lexical/InlineLexicalEditor.d.ts.map +1 -0
- package/dist/core/components/lexical/InlineLexicalEditor.js +133 -0
- package/dist/core/components/lexical/InlineParagraphNode.d.ts +10 -0
- package/dist/core/components/lexical/InlineParagraphNode.d.ts.map +1 -0
- package/dist/core/components/lexical/InlineParagraphNode.js +34 -0
- package/dist/core/components/lexical/SelectionBroadcaster.d.ts +6 -0
- package/dist/core/components/lexical/SelectionBroadcaster.d.ts.map +1 -0
- package/dist/core/components/lexical/SelectionBroadcaster.js +62 -0
- package/dist/core/components/lexical/SidebarLexicalEditor.d.ts +9 -0
- package/dist/core/components/lexical/SidebarLexicalEditor.d.ts.map +1 -0
- package/dist/core/components/lexical/editorConfig.d.ts +4 -0
- package/dist/core/components/lexical/editorConfig.d.ts.map +1 -0
- package/dist/core/components/lexical/editorConfig.js +24 -0
- package/dist/core/createApp.d.ts +373 -0
- package/dist/core/createApp.d.ts.map +1 -0
- package/dist/core/createApp.js +40 -0
- package/dist/core/createBlock.d.ts +947 -0
- package/dist/core/createBlock.d.ts.map +1 -0
- package/dist/core/createBlock.js +873 -0
- package/dist/core/createLayout.d.ts +78 -0
- package/dist/core/createLayout.d.ts.map +1 -0
- package/dist/core/createLayout.js +73 -0
- package/dist/core/hooks/useFieldSelection.d.ts +11 -0
- package/dist/core/hooks/useFieldSelection.d.ts.map +1 -0
- package/dist/core/hooks/useFieldSelection.js +25 -0
- package/dist/core/hooks/useIsEditable.d.ts +2 -0
- package/dist/core/hooks/useIsEditable.d.ts.map +1 -0
- package/dist/core/hooks/useIsEditable.js +12 -0
- package/dist/core/hooks/useOverlayMessage.d.ts +10 -0
- package/dist/core/hooks/useOverlayMessage.d.ts.map +1 -0
- package/dist/core/hooks/useOverlayMessage.js +37 -0
- package/dist/core/lib/contentType.d.ts +165 -0
- package/dist/core/lib/contentType.d.ts.map +1 -0
- package/dist/core/lib/contentType.js +148 -0
- package/dist/core/lib/fieldTypes.d.ts +85 -0
- package/dist/core/lib/fieldTypes.d.ts.map +1 -0
- package/dist/core/lib/lexicalReact.d.ts +3 -0
- package/dist/core/lib/lexicalReact.d.ts.map +1 -0
- package/dist/core/lib/lexicalReact.js +24 -0
- package/dist/core/lib/lexicalState.d.ts +10 -0
- package/dist/core/lib/lexicalState.d.ts.map +1 -0
- package/dist/core/lib/lexicalState.js +38 -0
- package/dist/core/lib/modifierFormats.d.ts +9 -0
- package/dist/core/lib/modifierFormats.d.ts.map +1 -0
- package/dist/core/lib/modifierFormats.js +8 -0
- package/dist/core/lib/modifiers.d.ts +12 -0
- package/dist/core/lib/modifiers.d.ts.map +1 -0
- package/dist/core/lib/modifiers.js +27 -0
- package/dist/features/content/CamoxContent.d.ts +2 -0
- package/dist/features/content/CamoxContent.d.ts.map +1 -0
- package/dist/features/content/CamoxContent.js +100 -0
- package/dist/features/content/components/AssetCard.d.ts +10 -0
- package/dist/features/content/components/AssetCard.d.ts.map +1 -0
- package/dist/features/content/components/AssetCard.js +41 -0
- package/dist/features/content/components/AssetCardSkeleton.d.ts +2 -0
- package/dist/features/content/components/AssetCardSkeleton.d.ts.map +1 -0
- package/dist/features/content/components/AssetCardSkeleton.js +11 -0
- package/dist/features/content/components/ContentSidebar.d.ts +2 -0
- package/dist/features/content/components/ContentSidebar.d.ts.map +1 -0
- package/dist/features/content/components/ContentSidebar.js +15 -0
- package/dist/features/content/components/UploadDropZone.d.ts +9 -0
- package/dist/features/content/components/UploadDropZone.d.ts.map +1 -0
- package/dist/features/content/components/UploadDropZone.js +51 -0
- package/dist/features/content/components/UploadProgressDrawer.d.ts +11 -0
- package/dist/features/content/components/UploadProgressDrawer.d.ts.map +1 -0
- package/dist/features/content/components/UploadProgressDrawer.js +72 -0
- package/dist/features/preview/CamoxPreview.d.ts +124 -0
- package/dist/features/preview/CamoxPreview.d.ts.map +1 -0
- package/dist/features/preview/CamoxPreview.js +253 -0
- package/dist/features/preview/components/AddBlockSheet.d.ts +3 -0
- package/dist/features/preview/components/AddBlockSheet.d.ts.map +1 -0
- package/dist/features/preview/components/AddBlockSheet.js +121 -0
- package/dist/features/preview/components/AgentChatSheet.d.ts +3 -0
- package/dist/features/preview/components/AgentChatSheet.d.ts.map +1 -0
- package/dist/features/preview/components/AgentChatSheet.js +24 -0
- package/dist/features/preview/components/AssetFieldEditor.d.ts +18 -0
- package/dist/features/preview/components/AssetFieldEditor.d.ts.map +1 -0
- package/dist/features/preview/components/AssetFieldEditor.js +139 -0
- package/dist/features/preview/components/AssetLightbox.d.ts +9 -0
- package/dist/features/preview/components/AssetLightbox.d.ts.map +1 -0
- package/dist/features/preview/components/AssetLightbox.js +421 -0
- package/dist/features/preview/components/AssetPickerGrid.d.ts +11 -0
- package/dist/features/preview/components/AssetPickerGrid.d.ts.map +1 -0
- package/dist/features/preview/components/AssetPickerGrid.js +92 -0
- package/dist/features/preview/components/BlockActionsPopover.d.ts +15 -0
- package/dist/features/preview/components/BlockActionsPopover.d.ts.map +1 -0
- package/dist/features/preview/components/BlockActionsPopover.js +506 -0
- package/dist/features/preview/components/CreatePageSheet.d.ts +3 -0
- package/dist/features/preview/components/CreatePageSheet.d.ts.map +1 -0
- package/dist/features/preview/components/CreatePageSheet.js +159 -0
- package/dist/features/preview/components/DebouncedFieldEditor.d.ts +10 -0
- package/dist/features/preview/components/DebouncedFieldEditor.d.ts.map +1 -0
- package/dist/features/preview/components/DebouncedFieldEditor.js +51 -0
- package/dist/features/preview/components/EditPageSheet.d.ts +3 -0
- package/dist/features/preview/components/EditPageSheet.d.ts.map +1 -0
- package/dist/features/preview/components/EditPageSheet.js +352 -0
- package/dist/features/preview/components/ItemFieldsEditor.d.ts +29 -0
- package/dist/features/preview/components/ItemFieldsEditor.d.ts.map +1 -0
- package/dist/features/preview/components/ItemFieldsEditor.js +308 -0
- package/dist/features/preview/components/LinkFieldEditor.d.ts +8 -0
- package/dist/features/preview/components/LinkFieldEditor.d.ts.map +1 -0
- package/dist/features/preview/components/LinkFieldEditor.js +190 -0
- package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts +10 -0
- package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts.map +1 -0
- package/dist/features/preview/components/MultipleAssetFieldEditor.js +232 -0
- package/dist/features/preview/components/OverlayTracker.d.ts +6 -0
- package/dist/features/preview/components/OverlayTracker.d.ts.map +1 -0
- package/dist/features/preview/components/OverlayTracker.js +41 -0
- package/dist/features/preview/components/Overlays.d.ts +6 -0
- package/dist/features/preview/components/Overlays.d.ts.map +1 -0
- package/dist/features/preview/components/Overlays.js +58 -0
- package/dist/features/preview/components/PageContentSheet.d.ts +3 -0
- package/dist/features/preview/components/PageContentSheet.d.ts.map +1 -0
- package/dist/features/preview/components/PageContentSheet.js +492 -0
- package/dist/features/preview/components/PageLocationFieldset.d.ts +14 -0
- package/dist/features/preview/components/PageLocationFieldset.d.ts.map +1 -0
- package/dist/features/preview/components/PageLocationFieldset.js +77 -0
- package/dist/features/preview/components/PagePicker.d.ts +3 -0
- package/dist/features/preview/components/PagePicker.d.ts.map +1 -0
- package/dist/features/preview/components/PagePicker.js +185 -0
- package/dist/features/preview/components/PageTree.d.ts +3 -0
- package/dist/features/preview/components/PageTree.d.ts.map +1 -0
- package/dist/features/preview/components/PageTree.js +410 -0
- package/dist/features/preview/components/PeekedBlock.d.ts +6 -0
- package/dist/features/preview/components/PeekedBlock.d.ts.map +1 -0
- package/dist/features/preview/components/PeekedBlock.js +95 -0
- package/dist/features/preview/components/PreviewPanel.d.ts +12 -0
- package/dist/features/preview/components/PreviewPanel.d.ts.map +1 -0
- package/dist/features/preview/components/PreviewPanel.js +192 -0
- package/dist/features/preview/components/PreviewSideSheet.d.ts +13 -0
- package/dist/features/preview/components/PreviewSideSheet.d.ts.map +1 -0
- package/dist/features/preview/components/PreviewSideSheet.js +28 -0
- package/dist/features/preview/components/PreviewToolbar.d.ts +2 -0
- package/dist/features/preview/components/PreviewToolbar.d.ts.map +1 -0
- package/dist/features/preview/components/PreviewToolbar.js +79 -0
- package/dist/features/preview/components/RepeatableItemsList.d.ts +14 -0
- package/dist/features/preview/components/RepeatableItemsList.d.ts.map +1 -0
- package/dist/features/preview/components/RepeatableItemsList.js +366 -0
- package/dist/features/preview/components/ShikiMarkdown.d.ts +4 -0
- package/dist/features/preview/components/ShikiMarkdown.d.ts.map +1 -0
- package/dist/features/preview/components/ShikiMarkdown.js +37 -0
- package/dist/features/preview/components/TextFormatToolbar.d.ts +2 -0
- package/dist/features/preview/components/TextFormatToolbar.d.ts.map +1 -0
- package/dist/features/preview/components/TextFormatToolbar.js +73 -0
- package/dist/features/preview/components/UnlinkAssetButton.d.ts +9 -0
- package/dist/features/preview/components/UnlinkAssetButton.d.ts.map +1 -0
- package/dist/features/preview/components/UnlinkAssetButton.js +55 -0
- package/dist/features/preview/overlayConstants.d.ts +19 -0
- package/dist/features/preview/overlayConstants.d.ts.map +1 -0
- package/dist/features/preview/overlayConstants.js +21 -0
- package/dist/features/preview/overlayMessages.d.ts +62 -0
- package/dist/features/preview/overlayMessages.d.ts.map +1 -0
- package/dist/features/preview/overlayMessages.js +9 -0
- package/dist/features/preview/previewConstants.d.ts +2 -0
- package/dist/features/preview/previewConstants.d.ts.map +1 -0
- package/dist/features/preview/previewStore.d.ts +116 -0
- package/dist/features/preview/previewStore.d.ts.map +1 -0
- package/dist/features/preview/previewStore.js +321 -0
- package/dist/features/provider/CamoxProvider.d.ts +11 -0
- package/dist/features/provider/CamoxProvider.d.ts.map +1 -0
- package/dist/features/provider/CamoxProvider.js +73 -0
- package/dist/features/provider/actionsStore.d.ts +39 -0
- package/dist/features/provider/actionsStore.d.ts.map +1 -0
- package/dist/features/provider/actionsStore.js +35 -0
- package/dist/features/provider/components/CamoxAppContext.d.ts +371 -0
- package/dist/features/provider/components/CamoxAppContext.d.ts.map +1 -0
- package/dist/features/provider/components/CamoxAppContext.js +17 -0
- package/dist/features/provider/components/CommandPalette.d.ts +3 -0
- package/dist/features/provider/components/CommandPalette.d.ts.map +1 -0
- package/dist/features/provider/components/CommandPalette.js +127 -0
- package/dist/features/provider/useAdminShortcuts.d.ts +5 -0
- package/dist/features/provider/useAdminShortcuts.d.ts.map +1 -0
- package/dist/features/provider/useAdminShortcuts.js +83 -0
- package/dist/features/routes/ogRoute.d.ts +7 -0
- package/dist/features/routes/ogRoute.d.ts.map +1 -0
- package/dist/features/routes/ogRoute.js +19 -0
- package/dist/features/routes/pageRoute.d.ts +135 -0
- package/dist/features/routes/pageRoute.d.ts.map +1 -0
- package/dist/features/routes/pageRoute.js +112 -0
- package/dist/features/studio/CamoxStudio.d.ts +7 -0
- package/dist/features/studio/CamoxStudio.d.ts.map +1 -0
- package/dist/features/studio/CamoxStudio.js +24 -0
- package/dist/features/studio/components/Navbar.d.ts +4 -0
- package/dist/features/studio/components/Navbar.d.ts.map +1 -0
- package/dist/features/studio/components/Navbar.js +95 -0
- package/dist/features/studio/components/ProjectMenu.d.ts +2 -0
- package/dist/features/studio/components/ProjectMenu.d.ts.map +1 -0
- package/dist/features/studio/components/ProjectMenu.js +132 -0
- package/dist/features/studio/components/UserButton.d.ts +2 -0
- package/dist/features/studio/components/UserButton.d.ts.map +1 -0
- package/dist/features/studio/components/UserButton.js +96 -0
- package/dist/features/studio/studioStore.d.ts +17 -0
- package/dist/features/studio/studioStore.d.ts.map +1 -0
- package/dist/features/studio/studioStore.js +44 -0
- package/dist/features/studio/useTheme.d.ts +9 -0
- package/dist/features/studio/useTheme.d.ts.map +1 -0
- package/dist/features/studio/useTheme.js +98 -0
- package/dist/features/vite/appGeneration.d.ts +4 -0
- package/dist/features/vite/appGeneration.d.ts.map +1 -0
- package/dist/features/vite/appGeneration.js +67 -0
- package/dist/features/vite/blockBoilerplate.d.ts +3 -0
- package/dist/features/vite/blockBoilerplate.d.ts.map +1 -0
- package/dist/features/vite/blockBoilerplate.js +59 -0
- package/dist/features/vite/convexSync.d.ts +6 -0
- package/dist/features/vite/convexSync.d.ts.map +1 -0
- package/dist/features/vite/convexSync.js +98 -0
- package/dist/features/vite/definitionsSync.d.ts +11 -0
- package/dist/features/vite/definitionsSync.d.ts.map +1 -0
- package/dist/features/vite/definitionsSync.js +157 -0
- package/dist/features/vite/routeGeneration.d.ts +4 -0
- package/dist/features/vite/routeGeneration.d.ts.map +1 -0
- package/dist/features/vite/routeGeneration.js +194 -0
- package/dist/features/vite/skillGeneration.d.ts +4 -0
- package/dist/features/vite/skillGeneration.d.ts.map +1 -0
- package/dist/features/vite/skillGeneration.js +69 -0
- package/dist/features/vite/utils.d.ts +2 -0
- package/dist/features/vite/utils.d.ts.map +1 -0
- package/dist/features/vite/utils.js +10 -0
- package/dist/features/vite/vite.d.ts +18 -0
- package/dist/features/vite/vite.d.ts.map +1 -0
- package/dist/features/vite/vite.js +77 -0
- package/dist/hooks/use-file-upload.d.ts +22 -0
- package/dist/hooks/use-file-upload.d.ts.map +1 -0
- package/dist/hooks/use-marquee-selection.d.ts +17 -0
- package/dist/hooks/use-marquee-selection.d.ts.map +1 -0
- package/dist/lib/analytics-client.d.ts +3 -0
- package/dist/lib/analytics-client.d.ts.map +1 -0
- package/dist/lib/analytics.d.ts +3 -0
- package/dist/lib/analytics.d.ts.map +1 -0
- package/dist/lib/analytics.js +24 -0
- package/dist/lib/auth.d.ts +3683 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/convex-site.d.ts +3 -0
- package/dist/lib/convex-site.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +40 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/studio.css +2 -0
- package/package.json +123 -10
- package/server/api.d.ts +1 -0
- package/server/api.js +1 -0
- package/server/dataModel.d.ts +1 -0
- package/server/dataModel.js +1 -0
- package/skills/camox-block/SKILL.md +357 -0
- package/skills/camox-layout/SKILL.md +181 -0
- package/index.js +0 -3
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: camox-block
|
|
3
|
+
description: "How to create Camox block definition files. Use this skill whenever the user wants to create a new block for their Camox website, add a page section/component, build a reusable content block, or asks about the block definition API. Trigger on mentions of blocks, sections, page components, content types, or any request to add new visual sections to a Camox site — even if they don't say 'block' explicitly."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Creating Camox Block Definitions
|
|
7
|
+
|
|
8
|
+
A block is a reusable page section (hero, testimonial, gallery, footer...). Users compose pages by assembling blocks. This skill covers creating block **definitions** — the template that describes a block's schema and rendering. Not the content (an instance of a block).
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
A block file lives in the app's `src/camox/blocks/` folder, is a `.tsx` file, and exports `block`:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Type, createBlock } from "camox/createBlock";
|
|
16
|
+
|
|
17
|
+
const myBlock = createBlock({
|
|
18
|
+
id: "my-block", // Must match filename (kebab-case)
|
|
19
|
+
title: "My Block", // Human-readable name
|
|
20
|
+
description: "...", // Tells the AI when/how to use this block
|
|
21
|
+
toMarkdown: ["# {{title}}", "{{description}}"], // Markdown template
|
|
22
|
+
content: {
|
|
23
|
+
/* ... */
|
|
24
|
+
}, // Editable content schema
|
|
25
|
+
settings: {
|
|
26
|
+
/* ... */
|
|
27
|
+
}, // Optional config toggles
|
|
28
|
+
component: MyBlockComponent,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function MyBlockComponent() {
|
|
32
|
+
return (
|
|
33
|
+
<section>
|
|
34
|
+
<myBlock.Field name="title">{(content) => <h1>{content}</h1>}</myBlock.Field>
|
|
35
|
+
</section>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { myBlock as block };
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The `createBlock` options
|
|
43
|
+
|
|
44
|
+
| Option | Required | Description |
|
|
45
|
+
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `id` | yes | Unique kebab-case identifier. Must match the filename without extension. |
|
|
47
|
+
| `title` | yes | Display name shown in the CMS UI. |
|
|
48
|
+
| `description` | yes | Tells the AI assistant when to use this block and what content it expects. Write it like guidance for an LLM — be specific about placement, tone, and content guidelines. |
|
|
49
|
+
| `toMarkdown` | yes | A `string[]` template for rendering block content as markdown. Each line is joined with `\n\n`. Use `{{fieldName}}` placeholders for field values. Lines where all placeholders resolve to empty are omitted. |
|
|
50
|
+
| `content` | yes | An object where each key is a field name and each value is a `Type.*` call. These fields are inline-editable in the CMS. |
|
|
51
|
+
| `settings` | no | Same shape as `content`, but for configuration that lives in a settings panel (not inline). Only `Type.Enum` and `Type.Boolean` should be used here. |
|
|
52
|
+
| `layoutOnly` | no | If `true`, the block won't appear in the "add block" sheet — it can only be placed inside layouts (e.g. navbar, footer). |
|
|
53
|
+
| `component` | yes | A named React function component that renders the block. |
|
|
54
|
+
|
|
55
|
+
## Markdown Template (`toMarkdown`)
|
|
56
|
+
|
|
57
|
+
The `toMarkdown` array controls how block content is rendered as markdown for AI features (summaries, SEO). Each string in the array becomes a paragraph (joined with `\n\n`). Use `{{fieldName}}` placeholders that reference keys in `content`.
|
|
58
|
+
|
|
59
|
+
**Field resolution rules:**
|
|
60
|
+
|
|
61
|
+
- **String**: raw text value
|
|
62
|
+
- **Link**: `[text](href)`
|
|
63
|
+
- **Image**: ``
|
|
64
|
+
- **File**: `[filename](url)`
|
|
65
|
+
- **Embed**: raw URL string
|
|
66
|
+
- **RepeatableObject**: each item rendered via its own `toMarkdown` (if set on the RepeatableObject options), items joined with `\n\n`
|
|
67
|
+
- **Boolean/Enum**: raw string value
|
|
68
|
+
|
|
69
|
+
Lines where ALL placeholders resolve to empty are omitted from output.
|
|
70
|
+
|
|
71
|
+
**Examples:**
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// Hero block
|
|
75
|
+
createBlock({
|
|
76
|
+
toMarkdown: ["# {{title}}", "{{description}}", "{{illustration}}", "{{cta}}"],
|
|
77
|
+
content: { ... },
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Testimonial — combine fields on one line
|
|
81
|
+
createBlock({
|
|
82
|
+
toMarkdown: ["> {{quote}}", "— {{author}}, {{title}}, {{company}}"],
|
|
83
|
+
content: { ... },
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Statistics — RepeatableObject with its own toMarkdown
|
|
87
|
+
createBlock({
|
|
88
|
+
toMarkdown: ["## {{subtitle}}", "{{description}}", "{{statistics}}"],
|
|
89
|
+
content: {
|
|
90
|
+
subtitle: Type.String({ default: "..." }),
|
|
91
|
+
description: Type.String({ default: "..." }),
|
|
92
|
+
statistics: Type.RepeatableObject(
|
|
93
|
+
{
|
|
94
|
+
number: Type.String({ default: "100M+" }),
|
|
95
|
+
label: Type.String({ default: "pages served" }),
|
|
96
|
+
},
|
|
97
|
+
{ minItems: 4, maxItems: 8, toMarkdown: ["**{{number}}** — {{label}}"] }
|
|
98
|
+
),
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The template is type-safe — `{{fieldName}}` placeholders are validated against the content keys at compile time. Typos like `{{titl}}` produce a TypeScript error.
|
|
104
|
+
|
|
105
|
+
## Content Field Types
|
|
106
|
+
|
|
107
|
+
Import `Type` from `"camox/createBlock"`. Every field requires a default value.
|
|
108
|
+
|
|
109
|
+
### Type.String
|
|
110
|
+
|
|
111
|
+
Inline-editable text. The workhorse field type.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
Type.String({
|
|
115
|
+
default: "Hello world", // Required
|
|
116
|
+
title: "Heading", // Optional label
|
|
117
|
+
maxLength: 280, // Optional
|
|
118
|
+
minLength: 1, // Optional
|
|
119
|
+
pattern: "^[A-Z]", // Optional regex
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Type.Boolean
|
|
124
|
+
|
|
125
|
+
A toggle. Use in `settings` for config, or in `content` for user-controlled flags.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
Type.Boolean({ default: false, title: "Show background" });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Type.Enum
|
|
132
|
+
|
|
133
|
+
A dropdown with predefined options. Most commonly used in `settings`.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
Type.Enum({
|
|
137
|
+
default: "left",
|
|
138
|
+
options: { left: "Left", center: "Center", right: "Right" },
|
|
139
|
+
title: "Alignment",
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `default` must be one of the keys in `options`.
|
|
144
|
+
|
|
145
|
+
### Type.Link
|
|
146
|
+
|
|
147
|
+
A link with text, URL (or internal page reference), and new-tab toggle.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
Type.Link({
|
|
151
|
+
default: { text: "Learn more", href: "/", newTab: false },
|
|
152
|
+
title: "CTA",
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Type.Image
|
|
157
|
+
|
|
158
|
+
A single image, or a repeatable array of images.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
// Single image
|
|
162
|
+
Type.Image({ title: "Cover photo" });
|
|
163
|
+
|
|
164
|
+
// Multiple images (creates a repeatable list)
|
|
165
|
+
Type.Image({ multiple: true, defaultItems: 6, title: "Gallery images" });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Type.File
|
|
169
|
+
|
|
170
|
+
A file upload, with MIME type filtering.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
Type.File({
|
|
174
|
+
accept: ["application/pdf"],
|
|
175
|
+
title: "PDF Document",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Multiple files
|
|
179
|
+
Type.File({
|
|
180
|
+
accept: ["application/pdf"],
|
|
181
|
+
multiple: true,
|
|
182
|
+
defaultItems: 0,
|
|
183
|
+
title: "Documents",
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Type.Embed
|
|
188
|
+
|
|
189
|
+
A URL validated against a regex pattern. Used for embedding external content.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
Type.Embed({
|
|
193
|
+
pattern: "https:\\/\\/(www\\.)?(youtube\\.com|youtu\\.be)\\/.+",
|
|
194
|
+
default: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
195
|
+
title: "YouTube URL",
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The `default` must match the `pattern` — an error is thrown at definition time otherwise.
|
|
200
|
+
|
|
201
|
+
### Type.RepeatableObject
|
|
202
|
+
|
|
203
|
+
An array of structured items. Each item is an object with its own fields. This is how you create lists of things (testimonials, features, stats, links...).
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
Type.RepeatableObject(
|
|
207
|
+
{
|
|
208
|
+
name: Type.String({ default: "Feature" }),
|
|
209
|
+
description: Type.String({ default: "Description" }),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
minItems: 1, // Must be >= 1
|
|
213
|
+
maxItems: 10,
|
|
214
|
+
title: "Features",
|
|
215
|
+
toMarkdown: ["### {{name}}", "{{description}}"], // Optional markdown template for each item
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The `toMarkdown` option on RepeatableObject defines how each item is rendered as markdown when the parent block's `toMarkdown` references this field. If omitted, fields are joined with " — " as a fallback.
|
|
221
|
+
|
|
222
|
+
RepeatableObjects can be nested — an item can contain another RepeatableObject:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
columns: Type.RepeatableObject(
|
|
226
|
+
{
|
|
227
|
+
title: Type.String({ default: "Column" }),
|
|
228
|
+
links: Type.RepeatableObject(
|
|
229
|
+
{
|
|
230
|
+
link: Type.Link({ default: { text: "Link", href: "#", newTab: false } }),
|
|
231
|
+
},
|
|
232
|
+
{ minItems: 1, maxItems: 999, toMarkdown: ["{{link}}"] },
|
|
233
|
+
),
|
|
234
|
+
},
|
|
235
|
+
{ minItems: 2, maxItems: 4, title: "Columns", toMarkdown: ["### {{title}}", "{{links}}"] },
|
|
236
|
+
);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Rendering in the Component
|
|
240
|
+
|
|
241
|
+
The component is a regular React function. It uses methods on the block constant to render each field. The pattern is always: use the appropriate renderer for each field type, with a render-prop child that receives the field value.
|
|
242
|
+
|
|
243
|
+
### Rendering String fields — `block.Field`
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
<myBlock.Field name="title">{(content) => <h1>{content}</h1>}</myBlock.Field>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The `name` must match a key in `content` that is a `Type.String`. The `content` argument is a string. This is what makes the field inline-editable in the CMS.
|
|
250
|
+
|
|
251
|
+
### Rendering Link fields — `block.Link`
|
|
252
|
+
|
|
253
|
+
Inside the render prop, use the `Link` component from `@tanstack/react-router` instead of a plain `<a>` tag. This enables client-side navigation for internal links.
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { Link } from "@tanstack/react-router";
|
|
257
|
+
|
|
258
|
+
<myBlock.Link name="cta">
|
|
259
|
+
{({ text, href, newTab }) => (
|
|
260
|
+
<Link to={href} target={newTab ? "_blank" : undefined} rel={newTab ? "noreferrer" : undefined}>
|
|
261
|
+
{text}
|
|
262
|
+
</Link>
|
|
263
|
+
)}
|
|
264
|
+
</myBlock.Link>;
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Rendering Image fields — `block.Image`
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<myBlock.Image name="cover">{(img) => <img src={img.url} alt={img.alt} />}</myBlock.Image>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Rendering File fields — `block.File`
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
<myBlock.File name="document">
|
|
277
|
+
{(file) => (
|
|
278
|
+
<a href={file.url} download={file.filename}>
|
|
279
|
+
Download
|
|
280
|
+
</a>
|
|
281
|
+
)}
|
|
282
|
+
</myBlock.File>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Rendering Embed fields — `block.Embed`
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
<myBlock.Embed name="videoUrl">{(url) => <iframe src={url} />}</myBlock.Embed>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Rendering RepeatableObject fields — `block.Repeater`
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<myBlock.Repeater name="features">
|
|
295
|
+
{(item) => (
|
|
296
|
+
<div>
|
|
297
|
+
<item.Field name="name">{(content) => <h3>{content}</h3>}</item.Field>
|
|
298
|
+
<item.Field name="description">{(content) => <p>{content}</p>}</item.Field>
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
</myBlock.Repeater>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Inside a Repeater, the `item` callback argument exposes the same `.Field`, `.Link`, `.Image`, `.File`, `.Embed`, and `.Repeater` methods — scoped to that item. This is how nested repeaters work too:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<footer.Repeater name="columns">
|
|
308
|
+
{(column) => (
|
|
309
|
+
<column.Repeater name="links">
|
|
310
|
+
{(linkItem) => (
|
|
311
|
+
<linkItem.Link name="link">{({ text, href }) => <a href={href}>{text}</a>}</linkItem.Link>
|
|
312
|
+
)}
|
|
313
|
+
</column.Repeater>
|
|
314
|
+
)}
|
|
315
|
+
</footer.Repeater>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
For `Type.Image({ multiple: true })`, the repeater item has a single `image` key:
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
<gallery.Repeater name="images">
|
|
322
|
+
{(item) => <item.Image name="image">{(img) => <img src={img.url} alt={img.alt} />}</item.Image>}
|
|
323
|
+
</gallery.Repeater>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Reading settings — `block.useSetting`
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
function MyComponent() {
|
|
330
|
+
const theme = myBlock.useSetting("theme");
|
|
331
|
+
const compact = myBlock.useSetting("compact");
|
|
332
|
+
// Use these values in your JSX for conditional rendering/styling
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Detached rendering — `block.Detached`
|
|
337
|
+
|
|
338
|
+
Renders content outside the block's DOM container. Useful for fixed/floating elements like sticky navbars or modals.
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
<myBlock.Detached>
|
|
342
|
+
<div className="fixed top-0 left-0 right-0 z-50">{/* floating content */}</div>
|
|
343
|
+
</myBlock.Detached>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Rules and Conventions
|
|
347
|
+
|
|
348
|
+
1. **File = one block.** One `.tsx` file per block in `src/camox/blocks/`. The `id` must match the filename (without `.tsx`).
|
|
349
|
+
2. **Named export as `block`.** Always: `export { myVar as block }`. Not a default export.
|
|
350
|
+
3. **Named function component.** Use `function MyComponent()`, not an arrow function. Reference it in `createBlock` before its declaration is fine (hoisting).
|
|
351
|
+
4. **All fields need defaults.** Every `Type.*` call requires a default value (images and files get automatic placeholders).
|
|
352
|
+
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.
|
|
353
|
+
6. **`toMarkdown` is required.** Every block must define how its content renders as markdown. Use `{{fieldName}}` placeholders matching content keys.
|
|
354
|
+
7. **Settings = Enum and Boolean only.** Keep settings simple. Use `content` for everything the user edits inline.
|
|
355
|
+
8. **RepeatableObject minItems >= 1.** You can't have an empty repeatable — there's always at least one item.
|
|
356
|
+
9. **Import path is `"camox/createBlock"`.** Both `Type` and `createBlock` come from this import.
|
|
357
|
+
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.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: camox-layout
|
|
3
|
+
description: "How to create and edit Camox layout definition files. Use this skill whenever the user wants to create a new layout for their Camox website, modify an existing layout, wrap pages in shared structure (navbar + footer), customize meta titles or OG images, or asks about the layout definition API. Trigger on mentions of layouts, page wrappers, page templates, shared page structure, navbar/footer placement, or any request to define how pages are structured — even if they don't say 'layout' explicitly."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Creating Camox Layout Definitions
|
|
7
|
+
|
|
8
|
+
A layout wraps pages in shared structure — a navbar at the top, a footer at the bottom, consistent styling. Each page in the CMS is assigned a layout. This skill covers creating layout **definitions** — the template that describes which blocks surround page content and how to render them.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
A layout file lives in the app's `src/camox/layouts/` folder, is a `.tsx` file, and exports `layout`:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { createLayout } from "camox/createLayout";
|
|
16
|
+
import { block as navbarBlock } from "../blocks/navbar";
|
|
17
|
+
import { block as footerBlock } from "../blocks/footer";
|
|
18
|
+
|
|
19
|
+
const myLayout = createLayout({
|
|
20
|
+
id: "my-layout", // Must match filename (kebab-case)
|
|
21
|
+
title: "My Layout", // Human-readable name
|
|
22
|
+
description: "When to use this layout",
|
|
23
|
+
blocks: {
|
|
24
|
+
before: [navbarBlock], // Blocks rendered before page content
|
|
25
|
+
after: [footerBlock], // Blocks rendered after page content
|
|
26
|
+
},
|
|
27
|
+
component: MyLayoutComponent,
|
|
28
|
+
buildMetaTitle: ({ pageMetaTitle, projectName }) => `${pageMetaTitle} | ${projectName}`,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function MyLayoutComponent({ children }: { children: React.ReactNode }) {
|
|
32
|
+
return (
|
|
33
|
+
<main className="flex min-h-screen flex-col">
|
|
34
|
+
<myLayout.blocks.Navbar />
|
|
35
|
+
<div className="flex-1">{children}</div>
|
|
36
|
+
<myLayout.blocks.Footer />
|
|
37
|
+
</main>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { myLayout as layout };
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## The `createLayout` options
|
|
45
|
+
|
|
46
|
+
| Option | Required | Description |
|
|
47
|
+
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| `id` | yes | Unique kebab-case identifier. Must match the filename without extension. |
|
|
49
|
+
| `title` | yes | Display name shown in the CMS UI. |
|
|
50
|
+
| `description` | yes | Tells the CMS user (or AI agent) when to pick this layout. Write it as guidance — explain what kind of pages this layout suits. |
|
|
51
|
+
| `blocks` | yes | An object with `before` and `after` arrays. Each array contains block instances (imported from block files). `before` blocks render above the page content, `after` blocks render below it. |
|
|
52
|
+
| `component` | yes | A named React function component that renders the layout shell. Receives `{ children }` — the page content. |
|
|
53
|
+
| `buildMetaTitle` | yes | A function that builds the `<title>` tag. Receives `{ pageMetaTitle, projectName, pageFullPath }` and returns a string. |
|
|
54
|
+
| `buildOgImage` | no | A function that returns a JSX element for generating Open Graph images. Receives `{ title, description, projectName }`. |
|
|
55
|
+
|
|
56
|
+
## Block Placement — `before` and `after`
|
|
57
|
+
|
|
58
|
+
Layout blocks are split into two groups:
|
|
59
|
+
|
|
60
|
+
- **`before`**: rendered above the page content (navbars, banners, announcements)
|
|
61
|
+
- **`after`**: rendered below the page content (footers, cookie bars)
|
|
62
|
+
|
|
63
|
+
The blocks you reference here are regular block definitions (created with `createBlock`). They're typically marked with `layoutOnly: true` in their block definition so they don't appear in the "add block" picker for page content.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
blocks: {
|
|
67
|
+
before: [navbarBlock, announcementBlock],
|
|
68
|
+
after: [footerBlock],
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can have multiple blocks in either group, or leave one empty:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
blocks: {
|
|
76
|
+
before: [navbarBlock],
|
|
77
|
+
after: [], // No blocks after page content
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## The Layout Component
|
|
82
|
+
|
|
83
|
+
The component is a named React function that receives `{ children }` and renders the overall page structure. Inside it, use the slot components from the layout constant to place each block.
|
|
84
|
+
|
|
85
|
+
Slot components are accessed via `layoutVar.blocks.PascalCaseName`, where the name is the PascalCase version of the block's `id`. For example, a block with `id: "navbar"` becomes `layoutVar.blocks.Navbar`; `id: "cookie-bar"` becomes `layoutVar.blocks.CookieBar`.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
function MyLayoutComponent({ children }: { children: React.ReactNode }) {
|
|
89
|
+
return (
|
|
90
|
+
<main className="flex min-h-screen flex-col">
|
|
91
|
+
<myLayout.blocks.Navbar />
|
|
92
|
+
<div className="flex-1">{children}</div>
|
|
93
|
+
<myLayout.blocks.Footer />
|
|
94
|
+
</main>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The component controls the HTML structure — you decide how to wrap and position the blocks and page content. Use Tailwind CSS for styling.
|
|
100
|
+
|
|
101
|
+
## Meta Title — `buildMetaTitle`
|
|
102
|
+
|
|
103
|
+
Controls how the browser tab title is built. Receives three parameters:
|
|
104
|
+
|
|
105
|
+
- `pageMetaTitle` — the title set on the individual page
|
|
106
|
+
- `projectName` — the site/project name
|
|
107
|
+
- `pageFullPath` — the full URL path of the page
|
|
108
|
+
|
|
109
|
+
Common patterns:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// "Page Title | Site Name" (most common for content pages)
|
|
113
|
+
buildMetaTitle: ({ pageMetaTitle, projectName }) =>
|
|
114
|
+
`${pageMetaTitle} | ${projectName}`,
|
|
115
|
+
|
|
116
|
+
// "Site Name | Page Title" (common for landing/home pages)
|
|
117
|
+
buildMetaTitle: ({ pageMetaTitle, projectName }) =>
|
|
118
|
+
`${projectName} | ${pageMetaTitle}`,
|
|
119
|
+
|
|
120
|
+
// Just the page title
|
|
121
|
+
buildMetaTitle: ({ pageMetaTitle }) => pageMetaTitle,
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## OG Image — `buildOgImage` (optional)
|
|
125
|
+
|
|
126
|
+
Generates an Open Graph image (the preview shown when sharing links on social media). The function returns a JSX element that gets rendered as a 1200x630 image.
|
|
127
|
+
|
|
128
|
+
The JSX here uses **inline styles only** (no Tailwind) because it's rendered by an image generation engine, not a browser. Use `display: "flex"` for layout.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
buildOgImage: ({ title, description, projectName }) => (
|
|
132
|
+
<div
|
|
133
|
+
style={{
|
|
134
|
+
display: "flex",
|
|
135
|
+
flexDirection: "column",
|
|
136
|
+
justifyContent: "center",
|
|
137
|
+
alignItems: "flex-start",
|
|
138
|
+
width: "100%",
|
|
139
|
+
height: "100%",
|
|
140
|
+
backgroundColor: "#09090b",
|
|
141
|
+
padding: "60px 80px",
|
|
142
|
+
fontFamily: "sans-serif",
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
{projectName && (
|
|
146
|
+
<div style={{ fontSize: 24, color: "#a1a1aa", marginBottom: 24 }}>
|
|
147
|
+
{projectName}
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
<div
|
|
151
|
+
style={{
|
|
152
|
+
fontSize: 64,
|
|
153
|
+
fontWeight: 700,
|
|
154
|
+
color: "#fafafa",
|
|
155
|
+
lineHeight: 1.2,
|
|
156
|
+
marginBottom: 24,
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
{title}
|
|
160
|
+
</div>
|
|
161
|
+
{description && (
|
|
162
|
+
<div style={{ fontSize: 28, color: "#a1a1aa", lineHeight: 1.5 }}>
|
|
163
|
+
{description}
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
),
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Rules and Conventions
|
|
171
|
+
|
|
172
|
+
1. **File = one layout.** One `.tsx` file per layout in `src/camox/layouts/`. The `id` must match the filename (without `.tsx`).
|
|
173
|
+
2. **Named export as `layout`.** Always: `export { myVar as layout }`. Not a default export.
|
|
174
|
+
3. **Named function component.** Use `function MyComponent()`, not an arrow function. Reference it in `createLayout` before its declaration is fine (hoisting).
|
|
175
|
+
4. **Import path is `"camox/createLayout"`.** The `createLayout` function comes from this import.
|
|
176
|
+
5. **Import blocks from `"../blocks/filename"`.** Layout blocks are imported from the blocks directory. Import the named `block` export.
|
|
177
|
+
6. **Use Tailwind CSS for the component.** Style the layout shell with Tailwind utility classes. The OG image function uses inline styles instead.
|
|
178
|
+
7. **Slot names are PascalCase.** A block with `id: "my-block"` becomes `layout.blocks.MyBlock`.
|
|
179
|
+
8. **`buildMetaTitle` is required.** Every layout must define how page titles are constructed.
|
|
180
|
+
9. **Description guides layout selection.** Write the `description` to help CMS users choose the right layout for their page — explain what types of pages it's suited for.
|
|
181
|
+
10. **Layout blocks should use `layoutOnly: true`.** Blocks intended only for layouts (navbars, footers) should set `layoutOnly: true` in their block definition so they don't clutter the page block picker.
|
package/index.js
DELETED