@mandolop97/constructor-nexora 1.5.0 → 1.7.1
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/CHANGELOG.md +106 -0
- package/NEXORA.md +934 -0
- package/dist/NexoraBuilderApp.d.ts +4 -2
- package/dist/components/builder/ArrayEditor.d.ts +27 -0
- package/dist/components/builder/BuilderCanvas.d.ts +4 -1
- package/dist/components/builder/BuilderEditorShell.d.ts +4 -2
- package/dist/components/schema/NodeRegistry.d.ts +3 -1
- package/dist/components/schema/PageRenderer.d.ts +4 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/resizable.d.ts +1 -1
- package/dist/components/ui/sheet.d.ts +1 -1
- package/dist/components/ui/sidebar.d.ts +1 -1
- package/dist/components/ui/toggle-group.d.ts +2 -2
- package/dist/components/ui/toggle.d.ts +2 -2
- package/dist/{index-wna1ND1R.js → index-CBsG57XR.js} +9529 -9050
- package/dist/index.css +1 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +79 -56
- package/dist/lib/block-registry.d.ts +20 -2
- package/dist/lib/host-data.d.ts +69 -0
- package/dist/lib/mock-data.d.ts +3 -37
- package/dist/lib/publish-validator.d.ts +33 -0
- package/dist/lib/render-context-utils.d.ts +55 -0
- package/dist/lib/slot-utils.d.ts +66 -0
- package/dist/{lucide-react-BWfGGYG0.js → lucide-react-iz8-2rfs.js} +7676 -7696
- package/dist/types/contract.d.ts +121 -12
- package/dist/types/page-types.d.ts +65 -0
- package/dist/types/schema.d.ts +6 -0
- package/package.json +4 -2
package/NEXORA.md
ADDED
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
# Nexora Visual Builder — Full Technical Documentation
|
|
2
|
+
|
|
3
|
+
> **Package:** `@mandolop97/constructor-nexora`
|
|
4
|
+
> **Contract Version:** 1.7.0
|
|
5
|
+
> **This file ships with the NPM package.** Any developer or AI assistant working in a project where this package is installed can read this documentation from `node_modules/@mandolop97/constructor-nexora/NEXORA.md`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Architecture Overview](#1-architecture-overview)
|
|
12
|
+
2. [Core Types](#2-core-types)
|
|
13
|
+
3. [NexoraBuilderApp — Main Component API](#3-nexorabuilderapp--main-component-api)
|
|
14
|
+
4. [PageRenderer — Schema Rendering](#4-pagerenderer--schema-rendering)
|
|
15
|
+
5. [Block System & Registry](#5-block-system--registry)
|
|
16
|
+
6. [Node Factory](#6-node-factory)
|
|
17
|
+
7. [Style System](#7-style-system)
|
|
18
|
+
8. [Theme Tokens](#8-theme-tokens)
|
|
19
|
+
9. [Data Binding & RenderContext](#9-data-binding--rendercontext)
|
|
20
|
+
10. [Slot System](#10-slot-system)
|
|
21
|
+
11. [Multi-Page System](#11-multi-page-system)
|
|
22
|
+
12. [Publish Pipeline](#12-publish-pipeline)
|
|
23
|
+
13. [Internationalization (i18n)](#13-internationalization-i18n)
|
|
24
|
+
14. [Extensibility](#14-extensibility)
|
|
25
|
+
15. [All Package Exports](#15-all-package-exports)
|
|
26
|
+
16. [Integration Examples](#16-integration-examples)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Architecture Overview
|
|
31
|
+
|
|
32
|
+
The builder follows a **schema-first** model: the entire UI is described as a flat JSON tree (`Schema`) that is rendered recursively by `PageRenderer`.
|
|
33
|
+
|
|
34
|
+
### Render Modes (`RenderMode`)
|
|
35
|
+
|
|
36
|
+
| Mode | Use Case | Characteristics |
|
|
37
|
+
|-----------|----------------------|--------------------------------------------------|
|
|
38
|
+
| `edit` | Visual builder | Selection outlines, drop zones, drag & drop |
|
|
39
|
+
| `preview` | Clean preview | No editing controls |
|
|
40
|
+
| `public` | Published site | Production mode, no builder dependencies |
|
|
41
|
+
|
|
42
|
+
### Data Flow
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Host App
|
|
46
|
+
└─ <NexoraBuilderApp initialSchema={...} onSave={...} />
|
|
47
|
+
└─ BuilderEditorShell (internal schema state + undo/redo)
|
|
48
|
+
├─ TopBar (actions: save, publish, preview, export)
|
|
49
|
+
├─ BlocksPalette (left sidebar: draggable blocks)
|
|
50
|
+
├─ BuilderCanvas (central canvas with PageRenderer in edit mode)
|
|
51
|
+
└─ Inspector (right sidebar: props, styles, theme)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Data Ownership Policy
|
|
55
|
+
|
|
56
|
+
| Layer | Responsibility | Prohibitions |
|
|
57
|
+
|------------------|-------------------------------|-------------------------------------|
|
|
58
|
+
| Host/Template | Fetch + adapt data. Build RenderContext. Provide hostData. | Cannot modify schema nodes directly |
|
|
59
|
+
| Builder (editor) | Schema CRUD. Adapt hostData for preview. Validate before publish. | NO fetch to external APIs. NO direct DB calls for business data. |
|
|
60
|
+
| Renderer | Render nodes from schema + resolved context. Pure. | NO fetch. NO state mutation. Read-only. |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2. Core Types
|
|
65
|
+
|
|
66
|
+
### `Schema`
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface Schema {
|
|
70
|
+
id: string;
|
|
71
|
+
version: number;
|
|
72
|
+
updatedAt: string;
|
|
73
|
+
themeTokens: ThemeTokens;
|
|
74
|
+
rootNodeId: string;
|
|
75
|
+
nodes: Record<string, SchemaNode>;
|
|
76
|
+
globalStyles?: Record<string, GlobalStyleDef>;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Flat map of nodes. `rootNodeId` points to the root node. Children are referenced by ID in `children: string[]`.
|
|
81
|
+
|
|
82
|
+
### `SchemaNode`
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface SchemaNode {
|
|
86
|
+
id: string;
|
|
87
|
+
type: NodeType;
|
|
88
|
+
props: NodeProps;
|
|
89
|
+
style: NodeStyle;
|
|
90
|
+
children: string[];
|
|
91
|
+
locked?: boolean;
|
|
92
|
+
hidden?: boolean;
|
|
93
|
+
customName?: string;
|
|
94
|
+
customCSS?: string;
|
|
95
|
+
appliedGlobalStyles?: string[];
|
|
96
|
+
slot?: SlotAssignment;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `NodeType`
|
|
101
|
+
|
|
102
|
+
Built-in types:
|
|
103
|
+
|
|
104
|
+
- **Layout:** `Section`, `Container`, `Grid`, `Stack`
|
|
105
|
+
- **Content:** `Text`, `Image`, `Divider`, `Badge`, `Spacer`, `Icon`, `SocialIcons`
|
|
106
|
+
- **UI:** `Button`, `Card`, `Input`
|
|
107
|
+
- **Interactive:** `Accordion`, `TabsBlock`, `VideoEmbed`, `FormBlock`
|
|
108
|
+
- **Commerce:** `ProductCard`, `ProductGrid`, `CollectionGrid`
|
|
109
|
+
- **Site (global):** `Navbar`, `Footer`, `AnnouncementBar`
|
|
110
|
+
- **Template blocks:** `HeroSection`, `FeatureBar`, `TestimonialCard`, `NewsletterSection`, `ImageBanner`, `RichTextSection`, `CTASection`, `TestimonialSection`, `FAQSection`
|
|
111
|
+
|
|
112
|
+
Extensible with `string & {}` for custom host types.
|
|
113
|
+
|
|
114
|
+
### `NodeStyle`
|
|
115
|
+
|
|
116
|
+
Object with ~80+ CSS properties organized in categories:
|
|
117
|
+
|
|
118
|
+
- **Layout & Box Model:** `padding`, `margin`, `gap`, `width`, `height`, `minHeight`, `maxWidth`, `display`, `flexDirection`, `alignItems`, `justifyContent`, `gridTemplateColumns`, `position`, `zIndex`, etc.
|
|
119
|
+
- **Typography:** `color`, `fontSize`, `fontWeight`, `fontFamily`, `lineHeight`, `letterSpacing`, `textAlign`, `textTransform`, `textDecoration`, etc.
|
|
120
|
+
- **Background:** `backgroundColor`, `backgroundImage`, `backgroundSize`, `backgroundPosition`, `backgroundGradient`, etc.
|
|
121
|
+
- **Border:** `borderColor`, `borderWidth`, `borderRadius`, `borderStyle`, etc.
|
|
122
|
+
- **Shadow & Effects:** `boxShadow`, `opacity`, `filter`, `backdropFilter`, `clipPath`, etc.
|
|
123
|
+
- **Transforms:** `transform`, `transformOrigin`, `perspective`
|
|
124
|
+
- **Transitions:** `transition`, `transitionProperty`, `transitionDuration`, etc.
|
|
125
|
+
- **Animations:** `animation`, `animationName`, `animationDuration`, etc.
|
|
126
|
+
- **Pseudo-state overrides:** `hover`, `focus`, `active` — partial style overrides per pseudo-state
|
|
127
|
+
- **Responsive overrides:** `responsive.{sm|md|lg|xl}` — partial style overrides per breakpoint (generates `@container` queries)
|
|
128
|
+
|
|
129
|
+
### `NodeProps`
|
|
130
|
+
|
|
131
|
+
Semantic properties for each node type: `text`, `href`, `src`, `alt`, `label`, `price`, `links[]`, `items[]`, `panels[]`, `videoUrl`, `heading`, `subtitle`, `ctaText`, `ctaHref`, `scrollAnimation`, etc. Accepts `[key: string]: any` for custom props.
|
|
132
|
+
|
|
133
|
+
### `ThemeTokens`
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
interface ThemeTokens {
|
|
137
|
+
colors: {
|
|
138
|
+
primary: string;
|
|
139
|
+
secondary: string;
|
|
140
|
+
background: string;
|
|
141
|
+
text: string;
|
|
142
|
+
muted: string;
|
|
143
|
+
border: string;
|
|
144
|
+
accent?: string;
|
|
145
|
+
};
|
|
146
|
+
typography: {
|
|
147
|
+
fontFamily: string;
|
|
148
|
+
baseSize: string;
|
|
149
|
+
headingScale: number;
|
|
150
|
+
};
|
|
151
|
+
radius: { sm: string; md: string; lg: string };
|
|
152
|
+
spacing: { xs: string; sm: string; md: string; lg: string; xl: string };
|
|
153
|
+
gradient?: string;
|
|
154
|
+
defaultCardLayout?: 'vertical' | 'horizontal' | 'minimal' | 'overlay';
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `AnimationPreset`
|
|
159
|
+
|
|
160
|
+
Available presets: `fadeIn`, `fadeOut`, `slideUp`, `slideDown`, `slideLeft`, `slideRight`, `scaleIn`, `scaleOut`, `bounceIn`, `pulse`, `shake`, `none`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 3. NexoraBuilderApp — Main Component API
|
|
165
|
+
|
|
166
|
+
The main entry point for the visual editor.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
interface NexoraBuilderAppProps {
|
|
170
|
+
/** Initial schema to load in the editor */
|
|
171
|
+
initialSchema?: Schema;
|
|
172
|
+
/** Domain for publishing */
|
|
173
|
+
domain?: string;
|
|
174
|
+
/** Current page slug */
|
|
175
|
+
pageSlug?: string;
|
|
176
|
+
|
|
177
|
+
// ── Callbacks ──
|
|
178
|
+
onSave?: (schema: Schema) => void;
|
|
179
|
+
onPublish?: (schema: Schema) => void;
|
|
180
|
+
onPreview?: (schema: Schema) => void;
|
|
181
|
+
onExport?: (schema: Schema) => void;
|
|
182
|
+
onSaveWithSlug?: (slug: string, schema: Schema) => void;
|
|
183
|
+
onPublishSubmit?: (payload: PublishPayload) => Promise<void>;
|
|
184
|
+
|
|
185
|
+
// ── Multi-page ──
|
|
186
|
+
pages?: PageDefinition[];
|
|
187
|
+
activePage?: string;
|
|
188
|
+
onPageChange?: (slug: string) => void;
|
|
189
|
+
|
|
190
|
+
// ── Localization ──
|
|
191
|
+
locale?: 'es' | 'en' | BuilderLocale;
|
|
192
|
+
|
|
193
|
+
// ── Extensibility ──
|
|
194
|
+
customComponents?: CustomComponentMap;
|
|
195
|
+
extraBlocks?: BlockDefinition[];
|
|
196
|
+
|
|
197
|
+
// ── Custom injection ──
|
|
198
|
+
customStylesheets?: string[]; // External CSS URLs
|
|
199
|
+
customCSS?: string; // Raw CSS string
|
|
200
|
+
customScripts?: string[]; // External script URLs
|
|
201
|
+
|
|
202
|
+
// ── Asset handling ──
|
|
203
|
+
onImageUpload?: (file: File) => Promise<string>;
|
|
204
|
+
resolveAssetUrl?: (path: string) => string;
|
|
205
|
+
|
|
206
|
+
// ── Data ──
|
|
207
|
+
hostData?: Record<string, any>;
|
|
208
|
+
renderContext?: RenderContext;
|
|
209
|
+
|
|
210
|
+
className?: string;
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Usage
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { NexoraBuilderApp } from '@mandolop97/constructor-nexora';
|
|
218
|
+
import '@mandolop97/constructor-nexora/styles.css';
|
|
219
|
+
|
|
220
|
+
function App() {
|
|
221
|
+
return (
|
|
222
|
+
<NexoraBuilderApp
|
|
223
|
+
initialSchema={mySchema}
|
|
224
|
+
onSave={(schema) => saveToDatabase(schema)}
|
|
225
|
+
onPublish={(schema) => publishSite(schema)}
|
|
226
|
+
locale="en"
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 4. PageRenderer — Schema Rendering
|
|
235
|
+
|
|
236
|
+
Recursive renderer that traverses the node tree from `rootNodeId`. Used both inside the builder and for standalone rendering (e.g., published sites).
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { PageRenderer } from '@mandolop97/constructor-nexora';
|
|
240
|
+
|
|
241
|
+
// Render a schema outside the builder
|
|
242
|
+
<PageRenderer
|
|
243
|
+
schema={mySchema}
|
|
244
|
+
mode="public"
|
|
245
|
+
renderContext={myRenderContext}
|
|
246
|
+
/>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### NodeComponentProps
|
|
250
|
+
|
|
251
|
+
Every node component receives:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
interface NodeComponentProps {
|
|
255
|
+
node: SchemaNode;
|
|
256
|
+
mode: RenderMode;
|
|
257
|
+
renderChildren: (childIds: string[]) => React.ReactNode;
|
|
258
|
+
mockData?: Record<string, any>;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 5. Block System & Registry
|
|
265
|
+
|
|
266
|
+
### `BlockDefinition`
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
interface BlockDefinition {
|
|
270
|
+
type: NodeType;
|
|
271
|
+
label: string;
|
|
272
|
+
icon?: React.ComponentType;
|
|
273
|
+
category: string;
|
|
274
|
+
canHaveChildren: boolean;
|
|
275
|
+
defaultProps?: Partial<NodeProps>;
|
|
276
|
+
defaultStyle?: Partial<NodeStyle>;
|
|
277
|
+
inspectorFields: InspectorFieldDef[];
|
|
278
|
+
compositeFactory?: () => CompositeNodeTree;
|
|
279
|
+
templateType?: TemplateType;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### `InspectorFieldDef`
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
interface InspectorFieldDef {
|
|
287
|
+
key: string;
|
|
288
|
+
label: string;
|
|
289
|
+
type: 'text' | 'select' | 'color' | 'number' | 'image' | 'toggle'
|
|
290
|
+
| 'slider' | 'textarea' | 'link' | 'icon' | 'spacing'
|
|
291
|
+
| 'group' | 'binding' | 'array';
|
|
292
|
+
options?: { label: string; value: string }[];
|
|
293
|
+
min?: number;
|
|
294
|
+
max?: number;
|
|
295
|
+
step?: number;
|
|
296
|
+
placeholder?: string;
|
|
297
|
+
rows?: number;
|
|
298
|
+
children?: InspectorFieldDef[]; // For 'group' type
|
|
299
|
+
arrayFields?: ArrayFieldDef[]; // For 'array' type
|
|
300
|
+
newItemDefaults?: Record<string, any>;
|
|
301
|
+
addLabel?: string;
|
|
302
|
+
maxItems?: number;
|
|
303
|
+
allowedDataSources?: string[]; // For 'binding' type
|
|
304
|
+
bindableFields?: string[];
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Composite Blocks
|
|
309
|
+
|
|
310
|
+
`compositeFactory` returns `{ rootId, nodes }` — a pre-built mini node tree (e.g., HeroSection with Container + Text + Button).
|
|
311
|
+
|
|
312
|
+
### Registry API
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { blockRegistry, registerBlock, registerBlocks, getBlockDef,
|
|
316
|
+
getCategories, getBlocksByCategory, getCategoriesForTemplate } from '@mandolop97/constructor-nexora';
|
|
317
|
+
|
|
318
|
+
// Register custom blocks
|
|
319
|
+
registerBlock(myBlockDef);
|
|
320
|
+
registerBlocks([block1, block2]);
|
|
321
|
+
|
|
322
|
+
// Query blocks
|
|
323
|
+
getBlockDef('Button'); // → BlockDefinition | undefined
|
|
324
|
+
getCategories(); // → string[]
|
|
325
|
+
getBlocksByCategory('Layout'); // → BlockDefinition[]
|
|
326
|
+
getCategoriesForTemplate('page'); // → string[]
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 6. Node Factory
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { createNode, createNodeTree, isContainerType, duplicateNodeTree } from '@mandolop97/constructor-nexora';
|
|
335
|
+
|
|
336
|
+
createNode('Button'); // Single node with defaults from registry
|
|
337
|
+
createNodeTree('HeroSection'); // Full composite tree if compositeFactory exists
|
|
338
|
+
isContainerType('Section'); // true — can have children
|
|
339
|
+
duplicateNodeTree(nodeId, nodes); // Deep clone with new IDs
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## 7. Style System
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
import { nodeStyleToCSS, generatePseudoStateCSS, generateResponsiveCSS, themeTokensToCSS } from '@mandolop97/constructor-nexora';
|
|
348
|
+
|
|
349
|
+
// Convert NodeStyle to React.CSSProperties
|
|
350
|
+
const css = nodeStyleToCSS(node.style);
|
|
351
|
+
|
|
352
|
+
// Generate :hover/:focus/:active CSS rules
|
|
353
|
+
const pseudoCSS = generatePseudoStateCSS(node.id, node.style);
|
|
354
|
+
|
|
355
|
+
// Generate @container responsive queries
|
|
356
|
+
const responsiveCSS = generateResponsiveCSS(node.id, node.style);
|
|
357
|
+
|
|
358
|
+
// Convert ThemeTokens to CSS custom properties
|
|
359
|
+
const themeCSSVars = themeTokensToCSS(schema.themeTokens);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### CSS Scoping
|
|
363
|
+
|
|
364
|
+
The editor lives inside `.nxr-editor` which defines fallback CSS variables for Tailwind/Shadcn tokens, avoiding conflicts with the host application.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 8. Theme Tokens
|
|
369
|
+
|
|
370
|
+
ThemeTokens are injected as CSS custom properties in the canvas:
|
|
371
|
+
|
|
372
|
+
```css
|
|
373
|
+
--primary: [value]
|
|
374
|
+
--secondary: [value]
|
|
375
|
+
--background: [value]
|
|
376
|
+
--foreground: [value] /* = colors.text */
|
|
377
|
+
--muted: [value]
|
|
378
|
+
--border: [value]
|
|
379
|
+
--accent: [value]
|
|
380
|
+
--radius: [value] /* = radius.md */
|
|
381
|
+
--font-family: [value]
|
|
382
|
+
--font-size-base: [value]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The `ThemeEditor` component updates these tokens in the schema, re-injected in real time.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 9. Data Binding & RenderContext
|
|
390
|
+
|
|
391
|
+
### RenderContext v1.7.0
|
|
392
|
+
|
|
393
|
+
The single source of truth for all rendering data, built by the Host/Template layer:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
interface RenderContext {
|
|
397
|
+
mode: RenderMode;
|
|
398
|
+
page?: PageContext;
|
|
399
|
+
data?: {
|
|
400
|
+
products: any[];
|
|
401
|
+
collections: any[];
|
|
402
|
+
pages: any[];
|
|
403
|
+
settings: Record<string, any>;
|
|
404
|
+
cardTemplate?: {
|
|
405
|
+
nodes: Record<string, SchemaNode>;
|
|
406
|
+
rootNodeId: string;
|
|
407
|
+
themeTokens?: ThemeTokens;
|
|
408
|
+
};
|
|
409
|
+
custom: Record<string, any>;
|
|
410
|
+
};
|
|
411
|
+
currentItem?: any;
|
|
412
|
+
currentIndex?: number;
|
|
413
|
+
theme?: ThemeTokens;
|
|
414
|
+
resolveAssetUrl?: (path: string) => string;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### PageContext
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
interface PageContext {
|
|
422
|
+
pageType: PageType;
|
|
423
|
+
slug: string;
|
|
424
|
+
mode: RenderMode;
|
|
425
|
+
params?: Record<string, string>;
|
|
426
|
+
query?: Record<string, string>;
|
|
427
|
+
metadata?: PageMetadata;
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Building RenderContext
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
import { buildRenderContext, buildEditContext, createIterationContext,
|
|
435
|
+
validateRenderContext, isStrictRenderContext } from '@mandolop97/constructor-nexora';
|
|
436
|
+
|
|
437
|
+
// For public/preview mode
|
|
438
|
+
const ctx = buildRenderContext({
|
|
439
|
+
mode: 'public',
|
|
440
|
+
page: { pageType: 'home', slug: '/' },
|
|
441
|
+
products: myProducts,
|
|
442
|
+
collections: myCollections,
|
|
443
|
+
settings: mySettings,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// For edit mode (uses sample data automatically)
|
|
447
|
+
const editCtx = buildEditContext(hostData, themeTokens);
|
|
448
|
+
|
|
449
|
+
// For collection iteration (e.g., inside ProductGrid)
|
|
450
|
+
const itemCtx = createIterationContext(parentCtx, product, index);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Data Binding Types
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
interface DataBinding {
|
|
457
|
+
propKey: string; // Node prop (e.g., 'text', 'src', 'price')
|
|
458
|
+
fieldPath: string; // Data path (e.g., 'product.title')
|
|
459
|
+
transform?: string; // Optional transform (e.g., 'formatPrice')
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
interface NodeBindings {
|
|
463
|
+
dataSource?: NodeDataSource;
|
|
464
|
+
bindings?: DataBinding[];
|
|
465
|
+
fallbackProps?: Record<string, any>;
|
|
466
|
+
mode: 'manual' | 'bound' | 'hybrid';
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Binding Utilities
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import { resolveBindings, hydrateTemplate, hydrateNodeForItem,
|
|
474
|
+
resolveFieldPath, setFieldPath,
|
|
475
|
+
createDefaultBindings, hasActiveBindings, getBoundProps,
|
|
476
|
+
registerTransform, getAvailableTransforms } from '@mandolop97/constructor-nexora';
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Host Data / Sample Data
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import { DEFAULT_SAMPLE_PRODUCTS, DEFAULT_SAMPLE_COLLECTIONS,
|
|
483
|
+
DEFAULT_SAMPLE_SETTINGS, buildHostData } from '@mandolop97/constructor-nexora';
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 10. Slot System
|
|
489
|
+
|
|
490
|
+
Slots enable template integration with controlled editability:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
type SlotBehavior = 'locked' | 'editable' | 'dynamic';
|
|
494
|
+
|
|
495
|
+
interface SlotAssignment {
|
|
496
|
+
__slot: string; // Slot ID (e.g., 'header', 'footer', 'main')
|
|
497
|
+
behavior: SlotBehavior; // What can be done with the slot content
|
|
498
|
+
fallbackNodeId?: string;
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
| Behavior | Description |
|
|
503
|
+
|------------|----------------------------------------------------------------|
|
|
504
|
+
| `locked` | Render as-is. Inspector disabled. Cannot delete/move. |
|
|
505
|
+
| `editable` | Full inspector access. Can reorder children. |
|
|
506
|
+
| `dynamic` | Data-driven. Bindings resolved at render. Children from data. |
|
|
507
|
+
|
|
508
|
+
### Slot Utilities
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
import { getSlotAssignment, isInSlot, getSlotBehavior, isSlotLocked,
|
|
512
|
+
isSlotEditable, isSlotDynamic, getNodesInSlot, getSlotFallback,
|
|
513
|
+
getSlotConstraints, validateSlots } from '@mandolop97/constructor-nexora';
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## 11. Multi-Page System
|
|
519
|
+
|
|
520
|
+
```tsx
|
|
521
|
+
<NexoraBuilderApp
|
|
522
|
+
pages={[
|
|
523
|
+
{ slug: 'home', title: 'Home', schema: homeSchema, templateType: 'page' },
|
|
524
|
+
{ slug: 'header', title: 'Header', schema: headerSchema,
|
|
525
|
+
templateType: 'header', canvasSize: { width: 1200, height: 80 } },
|
|
526
|
+
]}
|
|
527
|
+
activePage="home"
|
|
528
|
+
onPageChange={(slug) => setActivePage(slug)}
|
|
529
|
+
onSaveWithSlug={(slug, schema) => saveToDb(slug, schema)}
|
|
530
|
+
/>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Template Types
|
|
534
|
+
|
|
535
|
+
| Type | Description |
|
|
536
|
+
|-------------|--------------------------------|
|
|
537
|
+
| `page` | Full page |
|
|
538
|
+
| `header` | Site header fragment |
|
|
539
|
+
| `footer` | Site footer fragment |
|
|
540
|
+
| `component` | Reusable component |
|
|
541
|
+
| `single` | Single piece (e.g., banner) |
|
|
542
|
+
|
|
543
|
+
### Page Types
|
|
544
|
+
|
|
545
|
+
Available page types: `home`, `products`, `product-detail`, `collection`, `cart`, `checkout`, `faq`, `contact`, `help`, `privacy`, `terms`, `wishlist`, `custom`.
|
|
546
|
+
|
|
547
|
+
### Default Schemas
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
import { PAGE_DEFINITIONS, getDefaultSchemaForSlug,
|
|
551
|
+
createHomeSchema, createProductsSchema, createFAQSchema,
|
|
552
|
+
createContactSchema, createHelpSchema, createPrivacySchema,
|
|
553
|
+
createTermsSchema, createWishlistSchema } from '@mandolop97/constructor-nexora';
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## 12. Publish Pipeline
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
interface PublishPipeline {
|
|
562
|
+
saveDraft: (schema: Schema) => Promise<void>;
|
|
563
|
+
validate: (schema: Schema, opts?: ValidatorOptions) => { errors: string[]; warnings: string[] };
|
|
564
|
+
preview?: (schema: Schema) => Promise<string>;
|
|
565
|
+
publish: (payload: any) => Promise<void>;
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Rules:
|
|
570
|
+
- `validate().errors.length > 0` **blocks** publish.
|
|
571
|
+
- Warnings are displayed but do NOT block.
|
|
572
|
+
|
|
573
|
+
### Pre-Publish Validation
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
import { validateForPublish } from '@mandolop97/constructor-nexora';
|
|
577
|
+
|
|
578
|
+
const result = validateForPublish(schema);
|
|
579
|
+
// result: { valid: boolean; errors: ValidationIssue[]; warnings: ValidationIssue[] }
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Schema Validation
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import { validateSchema } from '@mandolop97/constructor-nexora';
|
|
586
|
+
|
|
587
|
+
const result = validateSchema(schema);
|
|
588
|
+
// result: { valid: boolean; errors: string[] }
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## 13. Internationalization (i18n)
|
|
594
|
+
|
|
595
|
+
```tsx
|
|
596
|
+
// Use a predefined locale
|
|
597
|
+
<NexoraBuilderApp locale="es" />
|
|
598
|
+
<NexoraBuilderApp locale="en" />
|
|
599
|
+
|
|
600
|
+
// Use a custom locale object
|
|
601
|
+
<NexoraBuilderApp locale={{
|
|
602
|
+
save: 'Guardar',
|
|
603
|
+
publish: 'Publicar',
|
|
604
|
+
// ... all keys
|
|
605
|
+
}} />
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
Programmatic API:
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
import { setLocaleByCode, setLocale, t, es, en, translateCategory } from '@mandolop97/constructor-nexora';
|
|
612
|
+
|
|
613
|
+
setLocaleByCode('en');
|
|
614
|
+
setLocale(myCustomLocale);
|
|
615
|
+
t('save'); // → 'Save'
|
|
616
|
+
translateCategory('Layout'); // → translated category name
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## 14. Extensibility
|
|
622
|
+
|
|
623
|
+
### Custom Components
|
|
624
|
+
|
|
625
|
+
```tsx
|
|
626
|
+
import { NexoraBuilderApp } from '@mandolop97/constructor-nexora';
|
|
627
|
+
import type { NodeComponent } from '@mandolop97/constructor-nexora';
|
|
628
|
+
|
|
629
|
+
const MyWidget: NodeComponent = ({ node, mode, renderChildren }) => (
|
|
630
|
+
<div>{node.props.text}</div>
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
<NexoraBuilderApp
|
|
634
|
+
customComponents={{ MyWidget }}
|
|
635
|
+
extraBlocks={[{
|
|
636
|
+
type: 'MyWidget',
|
|
637
|
+
label: 'My Widget',
|
|
638
|
+
category: 'Custom',
|
|
639
|
+
canHaveChildren: false,
|
|
640
|
+
defaultProps: { text: 'Hello' },
|
|
641
|
+
inspectorFields: [{ key: 'text', label: 'Text', type: 'text' }],
|
|
642
|
+
}]}
|
|
643
|
+
/>
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Custom Styles & Scripts
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
<NexoraBuilderApp
|
|
650
|
+
customStylesheets={['https://cdn.example.com/theme.css']}
|
|
651
|
+
customCSS=".my-class { color: red; }"
|
|
652
|
+
customScripts={['https://cdn.tailwindcss.com']}
|
|
653
|
+
/>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Persistence
|
|
657
|
+
|
|
658
|
+
The builder is **stateless regarding storage**. The host controls persistence:
|
|
659
|
+
|
|
660
|
+
```tsx
|
|
661
|
+
<NexoraBuilderApp
|
|
662
|
+
onSave={(schema) => saveToDatabase(schema)}
|
|
663
|
+
onSaveWithSlug={(slug, schema) => savePageToDb(slug, schema)}
|
|
664
|
+
onPublish={(schema) => publishSite(schema)}
|
|
665
|
+
onPublishSubmit={async (payload) => {
|
|
666
|
+
// payload: { domain, pages, status }
|
|
667
|
+
await publishToProduction(payload);
|
|
668
|
+
}}
|
|
669
|
+
/>
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
## 15. All Package Exports
|
|
675
|
+
|
|
676
|
+
### Components
|
|
677
|
+
|
|
678
|
+
| Export | Description |
|
|
679
|
+
|-------------------------|--------------------------------------------------|
|
|
680
|
+
| `NexoraBuilderApp` | Main visual editor component |
|
|
681
|
+
| `PageRenderer` | Standalone schema renderer |
|
|
682
|
+
| `CustomStylesInjector` | Inject custom CSS in public render |
|
|
683
|
+
| `MediaGallery` | Media gallery component |
|
|
684
|
+
| `ProductPicker` | Product picker component |
|
|
685
|
+
| `PageManager` | Page list/selector component |
|
|
686
|
+
|
|
687
|
+
### Block Registry
|
|
688
|
+
|
|
689
|
+
| Export | Description |
|
|
690
|
+
|-------------------------------|----------------------------------------|
|
|
691
|
+
| `blockRegistry` | The block registry instance |
|
|
692
|
+
| `registerBlock(def)` | Register a single block |
|
|
693
|
+
| `registerBlocks(defs[])` | Register multiple blocks |
|
|
694
|
+
| `getBlockDef(type)` | Get block definition by type |
|
|
695
|
+
| `getCategories()` | List all block categories |
|
|
696
|
+
| `getBlocksByCategory(cat)` | List blocks in a category |
|
|
697
|
+
| `getCategoriesForTemplate(t)` | Filter categories by template type |
|
|
698
|
+
|
|
699
|
+
### Node Utilities
|
|
700
|
+
|
|
701
|
+
| Export | Description |
|
|
702
|
+
|---------------------------|----------------------------------------|
|
|
703
|
+
| `createNode(type)` | Create node with registry defaults |
|
|
704
|
+
| `createNodeTree(type)` | Create composite node tree |
|
|
705
|
+
| `isContainerType(type)` | Check if type accepts children |
|
|
706
|
+
| `duplicateNodeTree(id,n)` | Deep clone with new IDs |
|
|
707
|
+
|
|
708
|
+
### Style Utilities
|
|
709
|
+
|
|
710
|
+
| Export | Description |
|
|
711
|
+
|----------------------------|---------------------------------------|
|
|
712
|
+
| `nodeStyleToCSS(style)` | Convert NodeStyle to CSSProperties |
|
|
713
|
+
| `generatePseudoStateCSS()` | Generate :hover/:focus/:active CSS |
|
|
714
|
+
| `generateResponsiveCSS()` | Generate @container queries |
|
|
715
|
+
| `themeTokensToCSS(tokens)` | Convert ThemeTokens to CSS vars |
|
|
716
|
+
|
|
717
|
+
### Schema & Validation
|
|
718
|
+
|
|
719
|
+
| Export | Description |
|
|
720
|
+
|----------------------------|---------------------------------------|
|
|
721
|
+
| `createDefaultHomeSchema()`| Default home page schema |
|
|
722
|
+
| `validateSchema(schema)` | Validate schema structure |
|
|
723
|
+
| `validateForPublish(schema)`| Pre-publish validation |
|
|
724
|
+
|
|
725
|
+
### Data Binding
|
|
726
|
+
|
|
727
|
+
| Export | Description |
|
|
728
|
+
|----------------------------|---------------------------------------|
|
|
729
|
+
| `resolveBindings()` | Resolve data bindings on a node |
|
|
730
|
+
| `hydrateTemplate()` | Hydrate template with data |
|
|
731
|
+
| `hydrateNodeForItem()` | Hydrate node for a single data item |
|
|
732
|
+
| `resolveFieldPath()` | Resolve a dot-path on an object |
|
|
733
|
+
| `setFieldPath()` | Set a value at a dot-path |
|
|
734
|
+
| `createDefaultBindings()` | Create default bindings for a node |
|
|
735
|
+
| `hasActiveBindings()` | Check if a node has active bindings |
|
|
736
|
+
| `getBoundProps()` | Get resolved bound props |
|
|
737
|
+
| `registerTransform()` | Register a custom data transform |
|
|
738
|
+
| `getAvailableTransforms()` | List available transforms |
|
|
739
|
+
|
|
740
|
+
### RenderContext Utilities
|
|
741
|
+
|
|
742
|
+
| Export | Description |
|
|
743
|
+
|-----------------------------|---------------------------------------|
|
|
744
|
+
| `buildRenderContext(opts)` | Build complete RenderContext |
|
|
745
|
+
| `buildEditContext(data,t)` | Build edit-mode context |
|
|
746
|
+
| `createIterationContext()` | Create iteration context for loops |
|
|
747
|
+
| `validateRenderContext()` | Validate a RenderContext |
|
|
748
|
+
| `isStrictRenderContext()` | Check if context is production-ready |
|
|
749
|
+
|
|
750
|
+
### Slot Utilities
|
|
751
|
+
|
|
752
|
+
| Export | Description |
|
|
753
|
+
|-----------------------------|---------------------------------------|
|
|
754
|
+
| `getSlotAssignment(node)` | Get slot assignment from a node |
|
|
755
|
+
| `isInSlot(node)` | Check if node is in a slot |
|
|
756
|
+
| `getSlotBehavior(node)` | Get slot behavior |
|
|
757
|
+
| `isSlotLocked(node)` | Check if slot is locked |
|
|
758
|
+
| `isSlotEditable(node)` | Check if slot is editable |
|
|
759
|
+
| `isSlotDynamic(node)` | Check if slot is dynamic |
|
|
760
|
+
| `getNodesInSlot()` | Get all nodes in a slot |
|
|
761
|
+
| `getSlotFallback()` | Get slot fallback node |
|
|
762
|
+
| `getSlotConstraints()` | Get slot constraints |
|
|
763
|
+
| `validateSlots()` | Validate all slot assignments |
|
|
764
|
+
|
|
765
|
+
### Host Data
|
|
766
|
+
|
|
767
|
+
| Export | Description |
|
|
768
|
+
|--------------------------------|------------------------------------|
|
|
769
|
+
| `DEFAULT_SAMPLE_PRODUCTS` | Sample product data for preview |
|
|
770
|
+
| `DEFAULT_SAMPLE_COLLECTIONS` | Sample collection data |
|
|
771
|
+
| `DEFAULT_SAMPLE_SETTINGS` | Sample site settings |
|
|
772
|
+
| `buildHostData(raw)` | Build normalized host data |
|
|
773
|
+
|
|
774
|
+
### Default Page Schemas
|
|
775
|
+
|
|
776
|
+
| Export | Description |
|
|
777
|
+
|------------------------------|-------------------------------------|
|
|
778
|
+
| `createHomeSchema()` | Home page schema |
|
|
779
|
+
| `createProductsSchema()` | Products listing schema |
|
|
780
|
+
| `createFAQSchema()` | FAQ page schema |
|
|
781
|
+
| `createContactSchema()` | Contact page schema |
|
|
782
|
+
| `createHelpSchema()` | Help center schema |
|
|
783
|
+
| `createPrivacySchema()` | Privacy policy schema |
|
|
784
|
+
| `createTermsSchema()` | Terms of service schema |
|
|
785
|
+
| `createWishlistSchema()` | Wishlist page schema |
|
|
786
|
+
| `PAGE_DEFINITIONS` | All page definitions |
|
|
787
|
+
| `getDefaultSchemaForSlug(s)` | Get default schema for a slug |
|
|
788
|
+
|
|
789
|
+
### i18n
|
|
790
|
+
|
|
791
|
+
| Export | Description |
|
|
792
|
+
|-------------------------------|------------------------------------|
|
|
793
|
+
| `setLocale(locale)` | Set custom locale object |
|
|
794
|
+
| `setLocaleByCode(code)` | Set locale by code ('es'/'en') |
|
|
795
|
+
| `t(key)` | Translate a key |
|
|
796
|
+
| `es` / `en` | Built-in locale objects |
|
|
797
|
+
| `translateCategory(cat)` | Translate block category name |
|
|
798
|
+
|
|
799
|
+
### Types (TypeScript)
|
|
800
|
+
|
|
801
|
+
All types are exported for full TypeScript support:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
import type {
|
|
805
|
+
Schema, SchemaNode, NodeType, BuiltInNodeType, NodeProps, NodeStyle,
|
|
806
|
+
ThemeTokens, Page, PageDefinition, RenderMode, TemplateType, AnimationPreset,
|
|
807
|
+
RenderContext, PageContext, DataBinding, NodeBindings, NodeDataSource,
|
|
808
|
+
BoundSchemaNode, SlotBehavior, SlotAssignment,
|
|
809
|
+
PublishStage, ValidatorOptions, PublishPipeline,
|
|
810
|
+
TextNodeProps, ImageNodeProps, ButtonNodeProps,
|
|
811
|
+
ProductCardNodeProps, ProductGridNodeProps, CollectionGridNodeProps,
|
|
812
|
+
HeroSectionNodeProps, ImageBannerNodeProps, RichTextSectionNodeProps,
|
|
813
|
+
CTASectionNodeProps, TestimonialSectionNodeProps, FAQSectionNodeProps,
|
|
814
|
+
NavbarNodeProps, FooterNodeProps,
|
|
815
|
+
NodeComponentProps, NodeComponent, CustomComponentMap,
|
|
816
|
+
BlockDefinition, InspectorFieldDef, CompositeNodeTree,
|
|
817
|
+
PageType, PageMetadata, VisibilityRule, DeviceVisibility,
|
|
818
|
+
BuilderLocale, PublishPayload, SchemaValidationResult,
|
|
819
|
+
PublishValidationResult, ValidationIssue,
|
|
820
|
+
BuildRenderContextOptions, SlotConstraints, PageSchemaDefinition,
|
|
821
|
+
} from '@mandolop97/constructor-nexora';
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
### Constants
|
|
825
|
+
|
|
826
|
+
| Export | Description |
|
|
827
|
+
|---------------------|-------------------------------------------|
|
|
828
|
+
| `EDITOR_VERSION` | Current editor version string |
|
|
829
|
+
| `ANIMATION_PRESETS` | Map of animation preset → CSS animation |
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## 16. Integration Examples
|
|
834
|
+
|
|
835
|
+
### Minimal Setup
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
import { NexoraBuilderApp } from '@mandolop97/constructor-nexora';
|
|
839
|
+
import '@mandolop97/constructor-nexora/styles.css';
|
|
840
|
+
|
|
841
|
+
export default function BuilderPage() {
|
|
842
|
+
return (
|
|
843
|
+
<NexoraBuilderApp
|
|
844
|
+
onSave={(schema) => console.log('Saved:', schema)}
|
|
845
|
+
locale="en"
|
|
846
|
+
/>
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### E-Commerce Template with Data
|
|
852
|
+
|
|
853
|
+
```tsx
|
|
854
|
+
import { NexoraBuilderApp, buildRenderContext } from '@mandolop97/constructor-nexora';
|
|
855
|
+
import '@mandolop97/constructor-nexora/styles.css';
|
|
856
|
+
|
|
857
|
+
export default function StoreBuilder({ products, collections, settings }) {
|
|
858
|
+
const renderContext = buildRenderContext({
|
|
859
|
+
mode: 'edit',
|
|
860
|
+
products,
|
|
861
|
+
collections,
|
|
862
|
+
settings,
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
return (
|
|
866
|
+
<NexoraBuilderApp
|
|
867
|
+
initialSchema={savedSchema}
|
|
868
|
+
renderContext={renderContext}
|
|
869
|
+
hostData={{ products, collections, settings }}
|
|
870
|
+
onSave={(schema) => api.saveSchema(schema)}
|
|
871
|
+
onPublishSubmit={async (payload) => api.publish(payload)}
|
|
872
|
+
locale="en"
|
|
873
|
+
/>
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### Standalone Renderer (Published Site)
|
|
879
|
+
|
|
880
|
+
```tsx
|
|
881
|
+
import { PageRenderer, buildRenderContext, CustomStylesInjector } from '@mandolop97/constructor-nexora';
|
|
882
|
+
import '@mandolop97/constructor-nexora/styles.css';
|
|
883
|
+
|
|
884
|
+
export default function PublicPage({ schema, products, settings }) {
|
|
885
|
+
const ctx = buildRenderContext({
|
|
886
|
+
mode: 'public',
|
|
887
|
+
page: { pageType: 'home', slug: '/' },
|
|
888
|
+
products,
|
|
889
|
+
settings,
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
return (
|
|
893
|
+
<>
|
|
894
|
+
<CustomStylesInjector css={schema.customCSS} />
|
|
895
|
+
<PageRenderer schema={schema} mode="public" renderContext={ctx} />
|
|
896
|
+
</>
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Custom Block Extension
|
|
902
|
+
|
|
903
|
+
```tsx
|
|
904
|
+
import { NexoraBuilderApp } from '@mandolop97/constructor-nexora';
|
|
905
|
+
import type { NodeComponent, BlockDefinition } from '@mandolop97/constructor-nexora';
|
|
906
|
+
|
|
907
|
+
const CountdownTimer: NodeComponent = ({ node }) => (
|
|
908
|
+
<div className="countdown">
|
|
909
|
+
<h3>{node.props.heading || 'Sale ends in'}</h3>
|
|
910
|
+
<span>{node.props.targetDate || '2025-12-31'}</span>
|
|
911
|
+
</div>
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
const countdownBlock: BlockDefinition = {
|
|
915
|
+
type: 'CountdownTimer',
|
|
916
|
+
label: 'Countdown Timer',
|
|
917
|
+
category: 'Marketing',
|
|
918
|
+
canHaveChildren: false,
|
|
919
|
+
defaultProps: { heading: 'Sale ends in', targetDate: '2025-12-31' },
|
|
920
|
+
inspectorFields: [
|
|
921
|
+
{ key: 'heading', label: 'Heading', type: 'text' },
|
|
922
|
+
{ key: 'targetDate', label: 'Target Date', type: 'text', placeholder: 'YYYY-MM-DD' },
|
|
923
|
+
],
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
<NexoraBuilderApp
|
|
927
|
+
customComponents={{ CountdownTimer }}
|
|
928
|
+
extraBlocks={[countdownBlock]}
|
|
929
|
+
/>
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
---
|
|
933
|
+
|
|
934
|
+
*This documentation is auto-generated and ships with the `@mandolop97/constructor-nexora` NPM package. For the latest version, check the package source.*
|