@onexapis/cli 1.1.45 → 1.1.47
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/cli.js +232 -12
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +232 -12
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/default/.cursorrules +126 -0
- package/templates/default/AGENTS.md +126 -0
- package/templates/default/CLAUDE.md +88 -1726
- package/templates/default/THEME_REFERENCE.md +1764 -0
|
@@ -1,1764 +1,126 @@
|
|
|
1
|
-
# CLAUDE.md —
|
|
1
|
+
# CLAUDE.md — Routing rules for AI agents
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is an **OneX theme project**. It ships with the `onexthm` MCP server,
|
|
4
|
+
which provides tools that **write section files for you**. You must use those
|
|
5
|
+
tools as your first move on every theme task.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### Required tool usage
|
|
10
|
-
|
|
11
|
-
| Task | MUST use | Why |
|
|
12
|
-
| ---------------------------------- | ------------------------------------- | ------------------------------------------------------------------- |
|
|
13
|
-
| Create a new section | `onexthm_create_section` | Guarantees correct file structure, schema, data attributes, imports |
|
|
14
|
-
| Convert HTML/React code to section | `onexthm_from_html` | Parses HTML elements and maps to OneX components automatically |
|
|
15
|
-
| Convert Figma design to section | `onexthm_from_figma` + `figma` tools | Extracts layout, colors, typography from design |
|
|
16
|
-
| Generate schema from description | `onexthm_generate_schema` | Infers category, fields, blocks, data requirements |
|
|
17
|
-
| Validate theme | `onexthm_validate` | Catches missing data attributes, wrong imports, structural errors |
|
|
18
|
-
| Look up hooks | `onexthm_list_hooks` | Shows all 44 hooks with params, returns, and examples |
|
|
19
|
-
| Look up components | Read `onexthm://components` resource | Shows all component types, fields, slots |
|
|
20
|
-
| Look up field types | Read `onexthm://field-types` resource | Shows all 25 field types with options |
|
|
21
|
-
| Look up rules | Read `onexthm://rules` resource | Shows DOs/DON'Ts and patterns |
|
|
22
|
-
|
|
23
|
-
### Workflow for creating sections
|
|
24
|
-
|
|
25
|
-
1. **From a description**: Use `onexthm_generate_schema` or `onexthm_create_section`
|
|
26
|
-
2. **From HTML/React code**: Use `onexthm_from_html` — it parses headings, paragraphs, buttons, images, lists, etc. and maps them to OneX components
|
|
27
|
-
3. **From Figma design**: Use `figma:get_metadata` → `onexthm_from_figma`
|
|
28
|
-
4. **Always validate after**: Use `onexthm_validate`
|
|
29
|
-
5. **Then customize**: Edit the generated files to refine layout and styling
|
|
30
|
-
|
|
31
|
-
### When you have HTML/React code to convert
|
|
32
|
-
|
|
33
|
-
If the user provides HTML, JSX, or a React component, ALWAYS use `onexthm_from_html` first:
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
onexthm_from_html({
|
|
37
|
-
htmlCode: "<the HTML/JSX code>",
|
|
38
|
-
sectionName: "section-name",
|
|
39
|
-
category: "hero" // optional
|
|
40
|
-
})
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
This automatically:
|
|
44
|
-
|
|
45
|
-
- Maps `<h1>`-`<h6>` → heading components
|
|
46
|
-
- Maps `<p>`, `<span>` → paragraph components
|
|
47
|
-
- Maps `<button>`, `<a class="btn">` → button components
|
|
48
|
-
- Maps `<img>` → image components
|
|
49
|
-
- Maps `<svg>`, icons → icon components
|
|
50
|
-
- Maps `<ul>/<ol>` → list components
|
|
51
|
-
- Maps `<hr>` → divider components
|
|
52
|
-
- Detects repeating patterns → block definitions
|
|
53
|
-
- Extracts Tailwind classes → layout settings
|
|
54
|
-
- Generates schema + component + index files
|
|
7
|
+
For deep reference (component types, hooks, animation tokens, color system,
|
|
8
|
+
dark mode, locale, full architecture), read **`THEME_REFERENCE.md`** in this
|
|
9
|
+
directory or call the relevant `onexthm_*` tool.
|
|
55
10
|
|
|
56
11
|
---
|
|
57
12
|
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
**IMPORTANT: When creating new sections, blocks, or modifying theme code, you MUST follow these rules. Violations will cause the editor and storefront to break.**
|
|
61
|
-
|
|
62
|
-
### Creating New Sections
|
|
63
|
-
|
|
64
|
-
Every section MUST use `ComponentRenderer` and `BlockRenderer` from `@onexapis/core` to render content. Sections define LAYOUT only — all content (text, images, buttons) is rendered through Blocks and Components.
|
|
65
|
-
|
|
66
|
-
Use `onexthm_create_section` MCP tool or `onexthm create:section` CLI to scaffold. If writing manually, every section MUST have:
|
|
67
|
-
|
|
68
|
-
1. **`"use client"` directive** at the top
|
|
69
|
-
2. **Import from `@onexapis/core/renderers`** — use `ComponentRenderer` and `BlockRenderer`
|
|
70
|
-
3. **Import from `@onexapis/core/utils`** — use `getSectionValues`, `toComponentInstance`, `filterEnabledComponents`
|
|
71
|
-
4. **`data-section-id={section.id}`** attribute on the root element
|
|
72
|
-
5. **`data-section-type={section.type}`** attribute on the root element
|
|
73
|
-
6. **`data-block-id={block.id}`** and **`data-block-type={block.type}`** on every block wrapper
|
|
74
|
-
7. **Handle `isEditing` prop** — show placeholders when empty, disable links
|
|
75
|
-
8. **A matching `.schema.ts` file** with type, name, category, settings, defaults
|
|
76
|
-
9. **An `index.ts`** that re-exports the component and schema
|
|
77
|
-
10. **Registration in `sections-registry.ts`**
|
|
78
|
-
|
|
79
|
-
### NEVER do these
|
|
80
|
-
|
|
81
|
-
- **NEVER render text/buttons/icons directly** — always use `ComponentRenderer` from core
|
|
82
|
-
- **NEVER use `<h1>`, `<p>`, `<button>` for content** — use `ComponentRenderer` which renders heading, paragraph, button components with proper editor integration
|
|
83
|
-
- **NEVER hardcode content** — content comes from `section.components` and `section.blocks`, rendered via `ComponentRenderer`/`BlockRenderer`
|
|
84
|
-
- **NEVER skip data attributes** — without `data-section-id`, `data-block-id`, the editor cannot select or edit sections
|
|
85
|
-
- **NEVER use `useEffect` for data fetching** — use `useProducts()`, `useBlogs()` hooks
|
|
86
|
-
- **NEVER import from `@onexapis/core/internal`**
|
|
87
|
-
|
|
88
|
-
### Section Template (MUST follow exactly)
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
"use client";
|
|
92
|
-
|
|
93
|
-
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
94
|
-
import coreRenderers from "@onexapis/core/renderers";
|
|
95
|
-
import coreUtils from "@onexapis/core/utils";
|
|
96
|
-
|
|
97
|
-
const { ComponentRenderer, BlockRenderer } = coreRenderers;
|
|
98
|
-
const { toComponentInstance, getSectionValues, filterEnabledComponents } =
|
|
99
|
-
coreUtils;
|
|
13
|
+
## Before you start
|
|
100
14
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
schema,
|
|
104
|
-
isEditing,
|
|
105
|
-
}: SectionComponentProps) {
|
|
106
|
-
const { settings } = getSectionValues(section, schema);
|
|
107
|
-
const components = filterEnabledComponents(section.components || []);
|
|
108
|
-
const blocks = (section.blocks || []).filter((b) => b.enabled !== false);
|
|
15
|
+
At the start of every session, confirm the `onexthm_*` tools are available
|
|
16
|
+
in your tool list. If they are not, STOP and tell the user:
|
|
109
17
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
data-section-id={section.id}
|
|
113
|
-
data-section-type={section.type}
|
|
114
|
-
data-section-template="default"
|
|
115
|
-
className="py-16"
|
|
116
|
-
style={{ backgroundColor: String(settings.backgroundColor || "#FFFFFF") }}
|
|
117
|
-
>
|
|
118
|
-
<div className="container mx-auto px-4 max-w-6xl">
|
|
119
|
-
{/* MUST use ComponentRenderer for section-level components */}
|
|
120
|
-
{components.map((comp) => (
|
|
121
|
-
<ComponentRenderer
|
|
122
|
-
key={comp.id}
|
|
123
|
-
instance={toComponentInstance(comp)}
|
|
124
|
-
sectionId={section.id}
|
|
125
|
-
isEditing={isEditing}
|
|
126
|
-
/>
|
|
127
|
-
))}
|
|
18
|
+
> "The onexthm MCP server is not loaded. Restart your AI client after
|
|
19
|
+
> verifying `.mcp.json` registers `@onexapis/theme-mcp`."
|
|
128
20
|
|
|
129
|
-
|
|
130
|
-
{blocks.map((block) => (
|
|
131
|
-
<div
|
|
132
|
-
key={block.id}
|
|
133
|
-
data-section-id={section.id}
|
|
134
|
-
data-block-id={block.id}
|
|
135
|
-
data-block-type={block.type}
|
|
136
|
-
>
|
|
137
|
-
<BlockRenderer
|
|
138
|
-
block={block}
|
|
139
|
-
sectionId={section.id}
|
|
140
|
-
isEditing={isEditing}
|
|
141
|
-
/>
|
|
142
|
-
</div>
|
|
143
|
-
))}
|
|
144
|
-
|
|
145
|
-
{/* MUST show empty state in editor */}
|
|
146
|
-
{blocks.length === 0 && isEditing && (
|
|
147
|
-
<div className="text-center py-12 border-2 border-dashed border-gray-300 rounded-lg">
|
|
148
|
-
<p className="text-gray-500">Add blocks to populate this section</p>
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
</div>
|
|
152
|
-
</section>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export default MySectionDefault;
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### How content rendering works
|
|
160
|
-
|
|
161
|
-
- **Sections** define LAYOUT (grid, padding, background color)
|
|
162
|
-
- **Components** (from core) render CONTENT (text, images, buttons)
|
|
163
|
-
- A section component NEVER renders `<h1>Hello</h1>` directly — instead:
|
|
164
|
-
|
|
165
|
-
```tsx
|
|
166
|
-
// WRONG ❌
|
|
167
|
-
<h1>{settings.title}</h1>;
|
|
168
|
-
|
|
169
|
-
// CORRECT ✅
|
|
170
|
-
const titleComp = components.find((c) => c.slot === "section-title");
|
|
171
|
-
{
|
|
172
|
-
titleComp && (
|
|
173
|
-
<ComponentRenderer
|
|
174
|
-
instance={toComponentInstance(titleComp)}
|
|
175
|
-
sectionId={section.id}
|
|
176
|
-
isEditing={isEditing}
|
|
177
|
-
/>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
- The editor manages component content — users edit text/images through the sidebar, not through section settings
|
|
21
|
+
Do not silently fall back to writing files by hand.
|
|
183
22
|
|
|
184
23
|
---
|
|
185
24
|
|
|
186
|
-
##
|
|
187
|
-
|
|
188
|
-
A OneX theme is a collection of **sections** compiled into a browser-ready JS bundle. Themes are:
|
|
189
|
-
|
|
190
|
-
1. Developed locally with `onexthm dev`
|
|
191
|
-
2. Compiled with `onexthm build` (esbuild → ES module)
|
|
192
|
-
3. Uploaded to S3 with `onexthm upload` (bundle.zip)
|
|
193
|
-
4. Loaded by the storefront (customer-facing site) and editor (visual editor)
|
|
194
|
-
|
|
195
|
-
Both storefront and editor render the same bundle — sections must look identical in both.
|
|
196
|
-
|
|
197
|
-
## Architecture
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
Theme
|
|
201
|
-
├── Sections Top-level page blocks (hero, features, pricing, footer)
|
|
202
|
-
│ ├── Blocks Nested containers inside sections (feature-item, pricing-tier)
|
|
203
|
-
│ └── Components Atomic UI rendered by @onexapis/core (heading, paragraph, button, icon, badge, divider, image)
|
|
204
|
-
└── Pages Page configs that list which sections to show
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
**Key principle**: Sections define layout & structure. Components (from core) handle rendering text, images, buttons with inline styles. The section component orchestrates how core components are arranged.
|
|
208
|
-
|
|
209
|
-
## File Structure
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
my-theme/
|
|
213
|
-
├── theme.config.ts # Colors, typography, spacing, breakpoints
|
|
214
|
-
├── bundle-entry.ts # Build entry point (DO NOT import React here)
|
|
215
|
-
├── sections-registry.ts # Lazy-loaded section imports
|
|
216
|
-
├── theme.layout.ts # Header/footer section configuration
|
|
217
|
-
├── package.json # @onexapis/core as dependency
|
|
218
|
-
├── tsconfig.json # ES2020 target, react-jsx
|
|
219
|
-
├── esbuild.config.js # Bundle → dist/bundle.mjs
|
|
220
|
-
├── sections/
|
|
221
|
-
│ └── hero/
|
|
222
|
-
│ ├── hero-default.tsx # React component (the visual)
|
|
223
|
-
│ ├── hero.schema.ts # Schema (settings, fields, defaults)
|
|
224
|
-
│ └── index.ts # Re-exports component + schema
|
|
225
|
-
├── pages/
|
|
226
|
-
│ └── home.ts # Page config with section instances
|
|
227
|
-
└── CLAUDE.md # This file
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Section Component Pattern
|
|
231
|
-
|
|
232
|
-
Every section component receives `SectionComponentProps` and uses core renderers:
|
|
233
|
-
|
|
234
|
-
```tsx
|
|
235
|
-
"use client";
|
|
236
|
-
|
|
237
|
-
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
238
|
-
import coreRenderers from "@onexapis/core/renderers";
|
|
239
|
-
import coreUtils from "@onexapis/core/utils";
|
|
240
|
-
|
|
241
|
-
const { ComponentRenderer, BlockRenderer } = coreRenderers;
|
|
242
|
-
const { toComponentInstance, getSectionValues, filterEnabledComponents } =
|
|
243
|
-
coreUtils;
|
|
244
|
-
|
|
245
|
-
export function MySection({
|
|
246
|
-
section,
|
|
247
|
-
schema,
|
|
248
|
-
isEditing,
|
|
249
|
-
data,
|
|
250
|
-
}: SectionComponentProps) {
|
|
251
|
-
const { settings } = getSectionValues(section, schema);
|
|
252
|
-
const { title, backgroundColor } = settings;
|
|
253
|
-
|
|
254
|
-
// Section-level components (title, subtitle, etc.)
|
|
255
|
-
const components = filterEnabledComponents(section.components || []);
|
|
256
|
-
const titleComp = components.find((c) => c.slot === "section-title");
|
|
257
|
-
|
|
258
|
-
// Nested blocks
|
|
259
|
-
const blocks = (section.blocks || []).filter((b) => b.enabled !== false);
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
<section
|
|
263
|
-
className="py-16"
|
|
264
|
-
style={{ backgroundColor: String(backgroundColor || "#FFFFFF") }}
|
|
265
|
-
data-section-id={section.id}
|
|
266
|
-
data-section-type={section.type}
|
|
267
|
-
>
|
|
268
|
-
<div className="container mx-auto px-4 max-w-6xl">
|
|
269
|
-
{/* Render a core component (heading, paragraph, etc.) */}
|
|
270
|
-
{titleComp && (
|
|
271
|
-
<ComponentRenderer
|
|
272
|
-
instance={toComponentInstance(titleComp)}
|
|
273
|
-
sectionId={section.id}
|
|
274
|
-
isEditing={isEditing}
|
|
275
|
-
/>
|
|
276
|
-
)}
|
|
277
|
-
|
|
278
|
-
{/* Render blocks (nested containers with their own components) */}
|
|
279
|
-
{blocks.map((block) => (
|
|
280
|
-
<div
|
|
281
|
-
key={block.id}
|
|
282
|
-
data-section-id={section.id}
|
|
283
|
-
data-block-id={block.id}
|
|
284
|
-
data-block-type={block.type}
|
|
285
|
-
>
|
|
286
|
-
<BlockRenderer
|
|
287
|
-
block={block}
|
|
288
|
-
sectionId={section.id}
|
|
289
|
-
isEditing={isEditing}
|
|
290
|
-
/>
|
|
291
|
-
</div>
|
|
292
|
-
))}
|
|
293
|
-
|
|
294
|
-
{/* Editor empty state */}
|
|
295
|
-
{blocks.length === 0 && isEditing && (
|
|
296
|
-
<div className="text-center py-12 border-2 border-dashed border-gray-300 rounded-lg">
|
|
297
|
-
<p className="text-gray-500">Add blocks to populate this section</p>
|
|
298
|
-
</div>
|
|
299
|
-
)}
|
|
300
|
-
</div>
|
|
301
|
-
</section>
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export default MySection;
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
## Schema Definition Pattern
|
|
309
|
-
|
|
310
|
-
Every section needs a schema that defines its settings, templates, and defaults:
|
|
311
|
-
|
|
312
|
-
```tsx
|
|
313
|
-
import type { SectionSchema } from "@onexapis/core/types";
|
|
314
|
-
|
|
315
|
-
export const mySchema: SectionSchema = {
|
|
316
|
-
type: "my-section",
|
|
317
|
-
name: "My Section",
|
|
318
|
-
description: "A custom section",
|
|
319
|
-
icon: "layout",
|
|
320
|
-
category: "content", // header | hero | content | features | testimonials | cta | gallery | pricing | faq | team | contact | footer | products | blog | newsletter
|
|
321
|
-
templates: [
|
|
322
|
-
{ id: "default", name: "Default", isDefault: true },
|
|
323
|
-
{ id: "minimal", name: "Minimal" },
|
|
324
|
-
],
|
|
325
|
-
settings: [
|
|
326
|
-
// Text fields
|
|
327
|
-
{ id: "title", type: "text", label: "Title", default: "Hello World" },
|
|
328
|
-
{ id: "subtitle", type: "textarea", label: "Subtitle" },
|
|
329
|
-
|
|
330
|
-
// Numbers
|
|
331
|
-
{ id: "columns", type: "number", label: "Columns", default: 3 },
|
|
332
|
-
{ id: "limit", type: "range", label: "Items", default: 8, min: 1, max: 20 },
|
|
333
|
-
|
|
334
|
-
// Selection
|
|
335
|
-
{
|
|
336
|
-
id: "layout",
|
|
337
|
-
type: "select",
|
|
338
|
-
label: "Layout",
|
|
339
|
-
default: "grid",
|
|
340
|
-
options: [
|
|
341
|
-
{ value: "grid", label: "Grid" },
|
|
342
|
-
{ value: "list", label: "List" },
|
|
343
|
-
],
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
// Boolean
|
|
347
|
-
{ id: "showTitle", type: "checkbox", label: "Show Title", default: true },
|
|
348
|
-
|
|
349
|
-
// Colors — use hex directly (standard approach)
|
|
350
|
-
{
|
|
351
|
-
id: "backgroundColor",
|
|
352
|
-
type: "color",
|
|
353
|
-
label: "Background",
|
|
354
|
-
default: "#FFFFFF",
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
id: "textColor",
|
|
358
|
-
type: "color_token",
|
|
359
|
-
label: "Text Color",
|
|
360
|
-
default: "#1F2937",
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
// Images
|
|
364
|
-
{ id: "image", type: "image", label: "Background Image" },
|
|
365
|
-
],
|
|
366
|
-
defaults: {
|
|
367
|
-
title: "Hello World",
|
|
368
|
-
columns: 3,
|
|
369
|
-
backgroundColor: "#FFFFFF",
|
|
370
|
-
},
|
|
371
|
-
// Declare if this section needs commerce data for SSR
|
|
372
|
-
dataRequirements: {
|
|
373
|
-
products: false,
|
|
374
|
-
blogs: false,
|
|
375
|
-
settings: false,
|
|
376
|
-
},
|
|
377
|
-
};
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
## Available Field Types
|
|
381
|
-
|
|
382
|
-
| Type | Description | Example Use |
|
|
383
|
-
| ------------------------ | ------------------------------------------------- | ----------------------------- |
|
|
384
|
-
| `text` | Single-line text input | Title, label, button text |
|
|
385
|
-
| `textarea` | Multi-line text | Description, bio |
|
|
386
|
-
| `number` | Numeric input | Columns, count, limit |
|
|
387
|
-
| `range` | Slider with min/max | Opacity, spacing, items count |
|
|
388
|
-
| `checkbox` / `boolean` | Toggle switch | Show/hide elements |
|
|
389
|
-
| `select` | Dropdown selector | Layout variant, size |
|
|
390
|
-
| `radio` | Radio button group | Alignment, position |
|
|
391
|
-
| `color` | Color picker (hex + system tokens) | Any color |
|
|
392
|
-
| `color_token` | Color picker (tokens: Primary/Secondary + Custom) | Text color, icon color |
|
|
393
|
-
| `color_background` | Solid color or gradient | Section background |
|
|
394
|
-
| `image` / `image_picker` | Image upload/selection | Hero image, avatar |
|
|
395
|
-
| `video_url` | Video embed URL | Background video |
|
|
396
|
-
| `font` / `font_picker` | Font family selector | Section font override |
|
|
397
|
-
| `text_alignment` | Left / center / right | Text alignment |
|
|
398
|
-
| `richtext` | Rich text editor | Content blocks, articles |
|
|
399
|
-
| `inline_richtext` | Inline rich text | Short formatted text |
|
|
400
|
-
| `html` | Raw HTML/code editor | Custom embeds, scripts |
|
|
401
|
-
| `url` | URL input | Link href |
|
|
402
|
-
| `array` / `repeater` | List of repeating items | Feature list, social links |
|
|
403
|
-
|
|
404
|
-
## Available Hooks (44 hooks)
|
|
405
|
-
|
|
406
|
-
All hooks import from `@onexapis/core/hooks`. Use `onexthm_list_hooks` MCP tool for full API details.
|
|
407
|
-
|
|
408
|
-
```tsx
|
|
409
|
-
import {
|
|
410
|
-
useProducts,
|
|
411
|
-
useCart,
|
|
412
|
-
useCheckout,
|
|
413
|
-
useDesignSystem,
|
|
414
|
-
} from "@onexapis/core/hooks";
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### Quick Reference
|
|
418
|
-
|
|
419
|
-
| Category | Hooks |
|
|
420
|
-
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
421
|
-
| **Commerce** (11) | `useProducts`, `useProductBySlug`, `useProductById`, `useProductCategories`, `useBlogs`, `useBlogBySlug`, `useBlogById`, `useBlogCategories`, `useSettings`, `useProductListing`, `useBlogListing` |
|
|
422
|
-
| **State** (3) | `useCart`, `useAuth`, `useOrders` |
|
|
423
|
-
| **Checkout** (8) | `useCheckout`, `usePayment`, `useOrderLookup`, `useOrderStatus`, `useOrderSuccess`, `useOrderSummary`, `useFinance`, `saveBuyNowItem` |
|
|
424
|
-
| **Products** (3) | `useSearchProducts`, `useAddToCart`, `useProductDetail` |
|
|
425
|
-
| **Theme** (9) | `useDesignSystem`, `usePageBackground`, `useCommerceData`, `useThemeMode`, `useLocale`, `useViewport`, `usePageData`, `useWebsiteSettings`, `useMotion` |
|
|
426
|
-
| **Utilities** (7) | `useDebounce`, `useMediaQuery`, `useIsMobile`, `useIsTablet`, `useIsDesktop`, `useContactForm`, `useCopyToClipboard`, `useFormatPrice`, `formatVndPrice` |
|
|
427
|
-
| **Animation** (1) | `useFlyToCart` |
|
|
428
|
-
| **Server** (4) | `fetchProducts`, `fetchBlogs`, `fetchSettings`, `prefetchSectionData` |
|
|
429
|
-
|
|
430
|
-
### Most Used Patterns
|
|
431
|
-
|
|
432
|
-
```tsx
|
|
433
|
-
// Fetch products
|
|
434
|
-
const { data, isLoading } = useProducts({ limit: 8 });
|
|
435
|
-
const products = data?.data ?? [];
|
|
436
|
-
|
|
437
|
-
// Cart
|
|
438
|
-
const { items, addItem, itemCount, subtotal } = useCart();
|
|
439
|
-
|
|
440
|
-
// Auth
|
|
441
|
-
const { user, isAuthenticated, login, logout } = useAuth();
|
|
442
|
-
|
|
443
|
-
// Checkout (full form)
|
|
444
|
-
const checkout = useCheckout({ paymentRedirectUrl: "/payment" });
|
|
445
|
-
// checkout.fullName, checkout.handleCheckout, checkout.errors
|
|
446
|
-
|
|
447
|
-
// Product detail with buy now
|
|
448
|
-
const detail = useProductDetail({ slug: routeParams.slug });
|
|
449
|
-
// detail.product, detail.handleAddToCart, detail.handleBuyNow
|
|
450
|
-
|
|
451
|
-
// Search with debounce
|
|
452
|
-
const search = useSearchProducts({ debounceMs: 300 });
|
|
453
|
-
// search.query, search.setQuery, search.results
|
|
454
|
-
|
|
455
|
-
// Responsive
|
|
456
|
-
const isMobile = useIsMobile();
|
|
457
|
-
const isDesktop = useIsDesktop();
|
|
458
|
-
|
|
459
|
-
// Price formatting
|
|
460
|
-
const { formatPrice } = useFormatPrice();
|
|
461
|
-
formatPrice(1250000); // "1.250.000đ"
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### Orders & Payment Hooks
|
|
465
|
-
|
|
466
|
-
These hooks cover the full order lifecycle: creating orders, looking up orders, processing payments, and displaying order history.
|
|
467
|
-
|
|
468
|
-
#### `useOrders` — Order state management (Zustand)
|
|
469
|
-
|
|
470
|
-
```tsx
|
|
471
|
-
import { useOrders } from "@onexapis/core/hooks";
|
|
472
|
-
|
|
473
|
-
const {
|
|
474
|
-
orders, // Order[] — list of orders
|
|
475
|
-
currentOrder, // Order | null — single order detail
|
|
476
|
-
isLoading,
|
|
477
|
-
error,
|
|
478
|
-
total,
|
|
479
|
-
totalPages,
|
|
480
|
-
currentPage,
|
|
481
|
-
|
|
482
|
-
// Actions
|
|
483
|
-
createOrder, // (data: CreateOrderData) => Promise<Order>
|
|
484
|
-
createPrivateOrder, // (data: CreateOrderData) => Promise<Order> — requires auth
|
|
485
|
-
fetchOrders, // (params?) => Promise<void> — public order list
|
|
486
|
-
fetchOrderHistory, // (params?) => Promise<void> — authenticated user's orders
|
|
487
|
-
fetchOrderById, // (orderId: string) => Promise<void>
|
|
488
|
-
fetchOrderByNumber, // (orderNumber: string) => Promise<void>
|
|
489
|
-
updateOrderStatus, // (orderId, status) => Promise<void>
|
|
490
|
-
cancelOrder, // (orderId) => Promise<void>
|
|
491
|
-
payOrder, // (orderId, paymentData?) => Promise<Order> — returns updated order
|
|
492
|
-
clearError,
|
|
493
|
-
clearCurrentOrder,
|
|
494
|
-
} = useOrders();
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
**`payOrder` returns the updated `Order`** — always check `order.status` and `order.payment_status` before navigating to success. Do NOT blindly redirect after calling `payOrder`.
|
|
498
|
-
|
|
499
|
-
#### `useOrderLookup` — Search/track order by number or ID
|
|
500
|
-
|
|
501
|
-
```tsx
|
|
502
|
-
import { useOrderLookup } from "@onexapis/core/hooks";
|
|
503
|
-
|
|
504
|
-
// Basic — manual search via input
|
|
505
|
-
const lookup = useOrderLookup();
|
|
506
|
-
|
|
507
|
-
// With auto-fetch from URL route param (for dynamic pages like /order-lookup/[orderId])
|
|
508
|
-
const routeParams = (data?.routeParams || {}) as Record<string, string>;
|
|
509
|
-
const lookup = useOrderLookup({
|
|
510
|
-
initialOrderId: routeParams.orderId, // auto-fetches on mount
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// lookup.orderNumber — input value (string)
|
|
514
|
-
// lookup.setOrderNumber — update input
|
|
515
|
-
// lookup.handleTrackOrder — search (auto-detects UUID vs order number)
|
|
516
|
-
// lookup.handleClear — reset search
|
|
517
|
-
// lookup.order — Order | null (result)
|
|
518
|
-
// lookup.isSearching — boolean
|
|
519
|
-
// lookup.errorMessage — string | null
|
|
520
|
-
// lookup.getStatusLabel — (status: string) => Vietnamese label
|
|
521
|
-
// lookup.formatCurrency — (amount: number) => "1.250.000đ"
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
**Dynamic page setup** for `/order-lookup/[orderId]`:
|
|
525
|
-
|
|
526
|
-
```typescript
|
|
527
|
-
// pages/order-lookup.ts
|
|
528
|
-
export const orderLookupPageConfig = {
|
|
529
|
-
handle: "order-lookup",
|
|
530
|
-
path: "/order-lookup/[orderId]",
|
|
531
|
-
isDynamic: true,
|
|
532
|
-
dynamicSegments: ["orderId"],
|
|
533
|
-
type: "custom",
|
|
534
|
-
// ...sections
|
|
535
|
-
};
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
#### `usePayment` — QR code payment with bank transfer
|
|
539
|
-
|
|
540
|
-
```tsx
|
|
541
|
-
import { usePayment } from "@onexapis/core/hooks";
|
|
542
|
-
|
|
543
|
-
const payment = usePayment({
|
|
544
|
-
successRedirectUrl: "/order-success", // default
|
|
545
|
-
checkoutRedirectUrl: "/checkout", // default — redirect if no order info
|
|
546
|
-
isEditing: false,
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
// payment.orderId, payment.orderNumber, payment.total
|
|
550
|
-
// payment.qrCodeImage — data URI for QR code
|
|
551
|
-
// payment.qrNote — transfer note (e.g. "DH4X7K2M")
|
|
552
|
-
// payment.bankTransferData — { accountNumber, accountName, bankName }
|
|
553
|
-
// payment.isLoading, payment.isProcessing, payment.error
|
|
554
|
-
// payment.handleConfirmPayment — calls payOrder API, checks response, then redirects
|
|
555
|
-
// payment.handleCancel — clears order info, redirects to checkout
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
**`handleConfirmPayment` validates the API response:**
|
|
559
|
-
|
|
560
|
-
- Checks `order.status` — blocks redirect if `failed`, `cancelled`, or `refunded`
|
|
561
|
-
- Checks `order.payment_status` — blocks redirect if `failed`
|
|
562
|
-
- Only navigates to success page when payment is valid
|
|
563
|
-
- Shows error message from API on failure
|
|
564
|
-
|
|
565
|
-
#### `useOrderSuccess` — Post-purchase success page
|
|
566
|
-
|
|
567
|
-
```tsx
|
|
568
|
-
import { useOrderSuccess } from "@onexapis/core/hooks";
|
|
569
|
-
|
|
570
|
-
const success = useOrderSuccess();
|
|
571
|
-
// success.orderId, success.orderNumber, success.displayOrderNumber
|
|
572
|
-
// success.trackOrderUrl — link to order lookup page
|
|
573
|
-
// success.hasOrder — boolean (false if no order in URL params)
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
#### `useOrderSummary` — Calculate order totals
|
|
577
|
-
|
|
578
|
-
```tsx
|
|
579
|
-
import { useOrderSummary, calculateOrderTotal } from "@onexapis/core/hooks";
|
|
580
|
-
|
|
581
|
-
const summary = useOrderSummary({
|
|
582
|
-
vatRate: 0.1,
|
|
583
|
-
shippingFee: 30000,
|
|
584
|
-
discount: 0,
|
|
585
|
-
freeShippingThreshold: 500000,
|
|
586
|
-
});
|
|
587
|
-
// summary.subtotal, summary.discount, summary.shipping, summary.vat, summary.total
|
|
588
|
-
// summary.formatPrice(amount)
|
|
589
|
-
|
|
590
|
-
// Standalone helper (no hook needed)
|
|
591
|
-
const total = calculateOrderTotal(items, { vatRate: 0.1, shippingFee: 30000 });
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
#### `useOrderStatus` — Status label translation
|
|
595
|
-
|
|
596
|
-
```tsx
|
|
597
|
-
import { useOrderStatus, getOrderStatusLabel } from "@onexapis/core/hooks";
|
|
598
|
-
|
|
599
|
-
const { getLabel, getPaymentLabel } = useOrderStatus();
|
|
600
|
-
getLabel("shipping"); // "Đang giao hàng"
|
|
601
|
-
getLabel("completed"); // "Hoàn thành"
|
|
602
|
-
getPaymentLabel("paid"); // "Đã thanh toán"
|
|
603
|
-
|
|
604
|
-
// Or use the standalone function
|
|
605
|
-
getOrderStatusLabel("pending"); // "Chờ xử lý"
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
#### `useOrderListPage` — Order history page
|
|
609
|
-
|
|
610
|
-
```tsx
|
|
611
|
-
import { useOrderListPage } from "@onexapis/core/hooks";
|
|
612
|
-
|
|
613
|
-
const orderList = useOrderListPage({ autoFetch: true, collapsedItemCount: 2 });
|
|
614
|
-
// orderList.orders — Order[]
|
|
615
|
-
// orderList.isLoading, orderList.error
|
|
616
|
-
// orderList.toggleOrder — expand/collapse order items
|
|
617
|
-
// orderList.refresh — re-fetch orders
|
|
618
|
-
// orderList.getDisplayItems — get visible items (respects collapsed state)
|
|
619
|
-
// orderList.hasMoreItems — check if order has hidden items
|
|
620
|
-
// orderList.formatCurrency, orderList.formatDate
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
#### Order Type Reference
|
|
624
|
-
|
|
625
|
-
```typescript
|
|
626
|
-
interface Order {
|
|
627
|
-
id: string;
|
|
628
|
-
order_number: string;
|
|
629
|
-
status: string; // pending | confirmed | processing | packing | shipping | completed | cancelled | failed | refunded
|
|
630
|
-
payment_status?: string; // paid | unpaid | failed
|
|
631
|
-
payment_method?: string;
|
|
632
|
-
total: number;
|
|
633
|
-
sub_total?: number;
|
|
634
|
-
tax?: number;
|
|
635
|
-
shipping_fee?: number;
|
|
636
|
-
discount?: number;
|
|
637
|
-
order_line_items?: OrderLineItem[];
|
|
638
|
-
shipping_address?: OrderAddress;
|
|
639
|
-
note?: string;
|
|
640
|
-
created_at: string;
|
|
641
|
-
updated_at?: string;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
interface OrderLineItem {
|
|
645
|
-
name: string;
|
|
646
|
-
unit_price: number;
|
|
647
|
-
quantity: number;
|
|
648
|
-
product_id: string;
|
|
649
|
-
variant_name?: string;
|
|
650
|
-
image_url?: string;
|
|
651
|
-
sku?: string;
|
|
652
|
-
location?: { id: string; name: string };
|
|
653
|
-
}
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
## Color System
|
|
657
|
-
|
|
658
|
-
Colors in OneX themes work with **two approaches** (both are valid):
|
|
659
|
-
|
|
660
|
-
### Approach 1: Direct hex colors (standard — used by simple, cool-store themes)
|
|
661
|
-
|
|
662
|
-
```tsx
|
|
663
|
-
// In schema
|
|
664
|
-
{ id: "backgroundColor", type: "color", label: "Background", default: "#3B82F6" }
|
|
25
|
+
## Routing rules — what tool to call when
|
|
665
26
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
// Resolves to CSS variable: var(--theme-color-primary)
|
|
679
|
-
// Changes automatically when user updates Primary Color in theme settings
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
Token values: `token:primary`, `token:secondary`, `token:accent`, `token:background`, `token:foreground`, `token:muted`
|
|
683
|
-
Custom values: `custom:#FF5733` or direct `#FF5733`
|
|
684
|
-
|
|
685
|
-
## Data Attributes (Required for Editor)
|
|
686
|
-
|
|
687
|
-
Sections and blocks MUST include `data-` attributes so the editor's visual inspector can identify and select them:
|
|
688
|
-
|
|
689
|
-
```tsx
|
|
690
|
-
// Section wrapper
|
|
691
|
-
<section
|
|
692
|
-
data-section-id={section.id} // REQUIRED
|
|
693
|
-
data-section-type={section.type} // REQUIRED
|
|
694
|
-
data-section-template="default" // Optional
|
|
695
|
-
data-section-name="My Section" // Optional
|
|
696
|
-
>
|
|
697
|
-
|
|
698
|
-
// Block wrapper
|
|
699
|
-
<div
|
|
700
|
-
data-section-id={section.id} // REQUIRED — parent section
|
|
701
|
-
data-block-id={block.id} // REQUIRED
|
|
702
|
-
data-block-type={block.type} // REQUIRED
|
|
703
|
-
>
|
|
704
|
-
```
|
|
27
|
+
| When the user says… | Call this tool | Notes |
|
|
28
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| "add / create / scaffold a section" (hero, header, footer, pricing, FAQ, testimonials, gallery, CTA, features, contact, newsletter, product grid, blog list, anything) | **`onexthm_create_section`** | Pass `projectDir` (this directory). Writes 3 files + patches `sections-registry.ts` in one call. |
|
|
30
|
+
| "add / create a page" (home, about, product detail, blog detail, checkout, order lookup, custom) | **`onexthm_create_page`** | Pass `projectDir`. Writes page config + patches `bundle-entry.ts`. |
|
|
31
|
+
| "convert / port / turn this HTML into a section", user pastes markup | **`onexthm_from_html`** | Pass `projectDir` + the source markup. |
|
|
32
|
+
| "convert Figma to a section", user references a Figma design | **`figma:get_metadata`** + **`figma:get_variable_defs`** → **`onexthm_from_figma`** | Call the figma tools first, then pass output to `onexthm_from_figma`. |
|
|
33
|
+
| "validate / lint / check my theme" | **`onexthm_validate`** | Run automatically after every write. |
|
|
34
|
+
| "fix / auto-fix / repair errors" | **`onexthm_fix`** | Auto-fixes: missing "use client", unregistered sections, schema mismatches, invalid page refs. Call `onexthm_validate` after to confirm. |
|
|
35
|
+
| "build / compile / bundle my theme" | **`onexthm_build`** | Returns structured errors with file:line. Run after code changes. |
|
|
36
|
+
| "what hooks can I use?" | **`onexthm_list_hooks`** | Filter by category. 44 hooks across 8 categories. |
|
|
37
|
+
| Anything else (architecture, animation, dark mode, locale, blocks, components) | Read `THEME_REFERENCE.md` first | Do not improvise. |
|
|
705
38
|
|
|
706
|
-
|
|
39
|
+
`projectDir` is **the directory containing `theme.config.ts` and `sections-registry.ts`**. In a fresh session opened in this folder, that's the current working directory.
|
|
707
40
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
34 built-in components from `@onexapis/core` that render inside sections via `ComponentRenderer`:
|
|
711
|
-
|
|
712
|
-
| Component | Description | Key Settings |
|
|
713
|
-
| --------- | ----------- | ------------ |
|
|
714
|
-
|
|
715
|
-
**Text:** `heading` (H1-H6), `paragraph`, `quote`
|
|
716
|
-
**Interactive:** `button` (default/outline/ghost/link), `link`, `input`, `textarea`, `checkbox`, `select`
|
|
717
|
-
**Media:** `image`, `video` (youtube/vimeo/hosted), `icon` (Lucide icons), `gallery`
|
|
718
|
-
**Layout:** `divider`, `spacer`, `container`, `grid`, `columns`, `card`
|
|
719
|
-
**Display:** `badge`, `alert`, `progress`, `rating`, `timer`, `list`, `table`, `accordion`, `tabs`, `code`, `map`
|
|
720
|
-
**Special:** `product-card`, `blog-card`, `social-links`, `hotline-contacts`, `company-info`
|
|
721
|
-
**Decorative:** `torn-separator`
|
|
722
|
-
|
|
723
|
-
Use `onexthm://components` MCP resource for full details (content/style fields, slots, examples).
|
|
724
|
-
|
|
725
|
-
Components are rendered via `ComponentRenderer` — you don't import them directly.
|
|
726
|
-
|
|
727
|
-
## Component Slots
|
|
728
|
-
|
|
729
|
-
Components use `slot` to identify their role:
|
|
730
|
-
|
|
731
|
-
```tsx
|
|
732
|
-
const titleComp = components.find((c) => c.slot === "section-title");
|
|
733
|
-
const ctaButton = components.find((c) => c.slot === "cta-button");
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
Common slots: `section-title`, `section-subtitle`, `description`, `cta-button`, `secondary-cta`, `badge`, `icon`, `image`, `block-title`, `block-description`, `feature-icon`
|
|
737
|
-
|
|
738
|
-
## Block System
|
|
739
|
-
|
|
740
|
-
Blocks are nested containers inside sections. They hold components and can nest other blocks.
|
|
741
|
-
Use `onexthm://blocks` MCP resource for full block patterns (features, testimonials, pricing, FAQ, team).
|
|
742
|
-
|
|
743
|
-
```tsx
|
|
744
|
-
// Section → Blocks → Components
|
|
745
|
-
<section> // Section: "features"
|
|
746
|
-
<div> // Block: "features-container" (layout)
|
|
747
|
-
<div> // Block: "feature-item" (repeating card)
|
|
748
|
-
<ComponentRenderer ... /> // Component: icon
|
|
749
|
-
<ComponentRenderer ... /> // Component: heading
|
|
750
|
-
<ComponentRenderer ... /> // Component: paragraph
|
|
751
|
-
</div>
|
|
752
|
-
<div> // Block: "feature-item" (another card)
|
|
753
|
-
...
|
|
754
|
-
</div>
|
|
755
|
-
</div>
|
|
756
|
-
</section>
|
|
757
|
-
```
|
|
758
|
-
|
|
759
|
-
Use `BlockRenderer` to render blocks — it handles recursive nesting automatically.
|
|
760
|
-
|
|
761
|
-
### Defining Blocks in Schema
|
|
762
|
-
|
|
763
|
-
```tsx
|
|
764
|
-
// In your-section.schema.ts
|
|
765
|
-
export const mySchema: SectionSchema = {
|
|
766
|
-
type: "features",
|
|
767
|
-
// ...settings, defaults...
|
|
768
|
-
blocks: [
|
|
769
|
-
{
|
|
770
|
-
type: "feature-item",
|
|
771
|
-
name: "Feature Item",
|
|
772
|
-
limit: 6, // Max 6 blocks of this type
|
|
773
|
-
settings: [
|
|
774
|
-
{
|
|
775
|
-
id: "backgroundColor",
|
|
776
|
-
type: "color",
|
|
777
|
-
label: "Background",
|
|
778
|
-
default: "#FFFFFF",
|
|
779
|
-
},
|
|
780
|
-
],
|
|
781
|
-
components: [
|
|
782
|
-
{ type: "icon", slot: "feature-icon", label: "Icon" },
|
|
783
|
-
{ type: "heading", slot: "block-title", label: "Title" },
|
|
784
|
-
{ type: "paragraph", slot: "block-description", label: "Description" },
|
|
785
|
-
],
|
|
786
|
-
},
|
|
787
|
-
],
|
|
788
|
-
};
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
### Rendering Blocks
|
|
792
|
-
|
|
793
|
-
```tsx
|
|
794
|
-
const blocks = (section.blocks || []).filter((b) => b.enabled !== false);
|
|
795
|
-
|
|
796
|
-
{
|
|
797
|
-
blocks.map((block) => (
|
|
798
|
-
<div
|
|
799
|
-
key={block.id}
|
|
800
|
-
data-section-id={section.id} // REQUIRED
|
|
801
|
-
data-block-id={block.id} // REQUIRED
|
|
802
|
-
data-block-type={block.type} // REQUIRED
|
|
803
|
-
className="p-6 rounded-xl border"
|
|
804
|
-
>
|
|
805
|
-
<BlockRenderer
|
|
806
|
-
block={block}
|
|
807
|
-
sectionId={section.id}
|
|
808
|
-
isEditing={isEditing}
|
|
809
|
-
/>
|
|
810
|
-
</div>
|
|
811
|
-
));
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
{
|
|
815
|
-
blocks.length === 0 && isEditing && (
|
|
816
|
-
<div className="text-center py-12 border-2 border-dashed border-gray-300 rounded-lg">
|
|
817
|
-
<p className="text-gray-500">Add blocks to populate this section</p>
|
|
818
|
-
</div>
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
## Animation System
|
|
824
|
-
|
|
825
|
-
Sections, blocks, and components support animations via framer-motion:
|
|
826
|
-
|
|
827
|
-
### Animation Types (22)
|
|
828
|
-
|
|
829
|
-
`fadeIn`, `fadeInUp`, `fadeInDown`, `fadeInLeft`, `fadeInRight`, `slideInUp`, `slideInDown`, `slideInLeft`, `slideInRight`, `scaleIn`, `scaleInUp`, `rotate`, `rotateIn`, `blur`, `bounce`, `pulse`, `shake`, `flip`, `typing`, `counter`, `parallax`, `none`
|
|
830
|
-
|
|
831
|
-
### Animation Triggers
|
|
832
|
-
|
|
833
|
-
- `onLoad` — animate immediately on mount
|
|
834
|
-
- `onScroll` — animate when section enters viewport (default, recommended)
|
|
835
|
-
- `onHover` — animate on hover
|
|
836
|
-
- `onClick` — animate on click
|
|
837
|
-
|
|
838
|
-
### Adding Animation to Schema
|
|
839
|
-
|
|
840
|
-
```tsx
|
|
841
|
-
// In schema settings
|
|
842
|
-
{ id: "animationType", type: "select", label: "Animation",
|
|
843
|
-
default: "none",
|
|
844
|
-
options: [
|
|
845
|
-
{ value: "none", label: "None" },
|
|
846
|
-
{ value: "fadeInUp", label: "Fade In Up" },
|
|
847
|
-
{ value: "scaleIn", label: "Scale In" },
|
|
848
|
-
]
|
|
849
|
-
},
|
|
850
|
-
{ id: "animationTrigger", type: "select", label: "Trigger",
|
|
851
|
-
default: "onScroll",
|
|
852
|
-
options: [
|
|
853
|
-
{ value: "onScroll", label: "On Scroll" },
|
|
854
|
-
{ value: "onLoad", label: "On Load" },
|
|
855
|
-
]
|
|
856
|
-
},
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
Animations are applied automatically by `SectionRenderer` and `BlockRenderer` via `AnimationWrapper`.
|
|
860
|
-
|
|
861
|
-
### Motion Tokens
|
|
862
|
-
|
|
863
|
-
Themes can customize animation feel via `MotionTokens`:
|
|
864
|
-
|
|
865
|
-
- `duration`: instant(0.1s), quick(0.2s), moderate(0.4s), deliberate(0.6s), slow(0.8s)
|
|
866
|
-
- `easing`: standard, entrance, exit, expressive (cubic-bezier)
|
|
867
|
-
- `intensity`: 0 (no motion/accessibility) to 1 (full motion)
|
|
868
|
-
- `staggerDelay`: delay between child animations (default 0.1s)
|
|
869
|
-
|
|
870
|
-
Access via `useMotion()` hook from `@onexapis/core/hooks`.
|
|
871
|
-
|
|
872
|
-
## Data Requirements (SSR)
|
|
873
|
-
|
|
874
|
-
Sections can declare what data they need for server-side rendering:
|
|
875
|
-
|
|
876
|
-
```tsx
|
|
877
|
-
// In schema
|
|
878
|
-
dataRequirements: {
|
|
879
|
-
products: true, // Section needs product data
|
|
880
|
-
blogs: false, // No blog data needed
|
|
881
|
-
settings: true, // Needs website settings
|
|
882
|
-
}
|
|
883
|
-
```
|
|
884
|
-
|
|
885
|
-
When `dataRequirements.products = true`:
|
|
886
|
-
|
|
887
|
-
- Server pre-fetches products before rendering
|
|
888
|
-
- Data is passed via `SectionComponentProps.data`
|
|
889
|
-
- Use `useCommerceData(data)` to access it
|
|
890
|
-
- Falls back to client-side `useProducts()` if not pre-fetched
|
|
891
|
-
|
|
892
|
-
## Dynamic Pages (Product Detail, Blog Detail)
|
|
893
|
-
|
|
894
|
-
Dynamic pages use URL parameters like `/products/[slug]` to render detail views.
|
|
895
|
-
|
|
896
|
-
### Define a dynamic page in `pages/`
|
|
897
|
-
|
|
898
|
-
```typescript
|
|
899
|
-
// pages/product-detail.ts
|
|
900
|
-
import type { PageConfig } from "@onexapis/core/types";
|
|
901
|
-
|
|
902
|
-
export const productDetailPageConfig: Omit<
|
|
903
|
-
PageConfig,
|
|
904
|
-
"id" | "createdAt" | "updatedAt"
|
|
905
|
-
> = {
|
|
906
|
-
title: "Product Detail",
|
|
907
|
-
handle: "product-detail",
|
|
908
|
-
path: "/products/[slug]", // Dynamic segment in brackets
|
|
909
|
-
isDynamic: true, // Flag as dynamic
|
|
910
|
-
dynamicSegments: ["slug"], // Parameter names
|
|
911
|
-
type: "product",
|
|
912
|
-
renderMode: "sections",
|
|
913
|
-
themeId: "my-theme",
|
|
914
|
-
editable: true,
|
|
915
|
-
published: true,
|
|
916
|
-
sections: [
|
|
917
|
-
{
|
|
918
|
-
id: "product-detail-1",
|
|
919
|
-
type: "my-product-detail",
|
|
920
|
-
template: "default",
|
|
921
|
-
order: 0,
|
|
922
|
-
enabled: true,
|
|
923
|
-
settings: {},
|
|
924
|
-
components: [],
|
|
925
|
-
blocks: [],
|
|
926
|
-
},
|
|
927
|
-
],
|
|
928
|
-
};
|
|
929
|
-
export default productDetailPageConfig;
|
|
930
|
-
```
|
|
41
|
+
---
|
|
931
42
|
|
|
932
|
-
|
|
43
|
+
## Forbidden behaviors
|
|
933
44
|
|
|
934
|
-
|
|
935
|
-
export { default as productDetailPageConfig } from "./pages/product-detail";
|
|
936
|
-
```
|
|
45
|
+
These break the editor and storefront. Never do them.
|
|
937
46
|
|
|
938
|
-
|
|
47
|
+
1. **Never hand-write a section's `.tsx`, `.schema.ts`, or `index.ts`** when adding a new section. Call `onexthm_create_section`. The tool already produces correct file structure, schema shape, data attributes, imports, and registry entry. Hand-writing one-offs causes drift.
|
|
48
|
+
2. **Never edit `sections-registry.ts` by hand** to add a new section. The tools patch it for you.
|
|
49
|
+
3. **Never use raw `<h1>`, `<p>`, `<button>`** for _content_ inside a section. Content goes through `ComponentRenderer` from `@onexapis/core/renderers`. Section components define **layout only**.
|
|
50
|
+
4. **Never skip `data-section-id`, `data-section-type`, `data-block-id`, `data-block-type`** on wrapper elements. Without them, the visual editor cannot select or edit the section.
|
|
51
|
+
5. **Never use `useEffect` for data fetching, never hardcode API URLs, never import from `@onexapis/core/internal`**. Use the documented hooks (`useProducts`, `useBlogs`, `useCart`, `useAuth`, `useCheckout`).
|
|
939
52
|
|
|
940
|
-
|
|
53
|
+
---
|
|
941
54
|
|
|
942
|
-
|
|
943
|
-
export function ProductDetail({
|
|
944
|
-
section,
|
|
945
|
-
schema,
|
|
946
|
-
isEditing,
|
|
947
|
-
data,
|
|
948
|
-
}: SectionComponentProps) {
|
|
949
|
-
const routeParams = (data?.routeParams || {}) as Record<string, string>;
|
|
950
|
-
const slug = routeParams.slug || ""; // "blue-shirt" from /products/blue-shirt
|
|
55
|
+
## Calling the tools — minimal examples
|
|
951
56
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
57
|
+
```jsonc
|
|
58
|
+
// Create a new section
|
|
59
|
+
onexthm_create_section({
|
|
60
|
+
projectDir: "<absolute path to this directory>",
|
|
61
|
+
name: "pricing-table",
|
|
62
|
+
description: "Three-tier pricing table with featured tier"
|
|
63
|
+
})
|
|
956
64
|
|
|
957
|
-
|
|
65
|
+
// Convert pasted HTML/JSX to a section
|
|
66
|
+
onexthm_from_html({
|
|
67
|
+
projectDir: "<absolute path to this directory>",
|
|
68
|
+
sectionName: "hero-banner",
|
|
69
|
+
htmlCode: "<div>...the user's markup...</div>"
|
|
70
|
+
})
|
|
958
71
|
|
|
959
|
-
|
|
72
|
+
// Convert a Figma frame (after calling figma:get_metadata first)
|
|
73
|
+
onexthm_from_figma({
|
|
74
|
+
projectDir: "<absolute path to this directory>",
|
|
75
|
+
sectionName: "feature-grid",
|
|
76
|
+
figmaMetadata: "<output of figma:get_metadata>",
|
|
77
|
+
figmaVariables: "<output of figma:get_variable_defs>"
|
|
78
|
+
})
|
|
960
79
|
|
|
961
|
-
|
|
962
|
-
|
|
80
|
+
// Create a page
|
|
81
|
+
onexthm_create_page({
|
|
82
|
+
projectDir: "<absolute path to this directory>",
|
|
963
83
|
handle: "product-detail",
|
|
964
|
-
path: "/products/[slug]",
|
|
84
|
+
path: "/products/[slug]",
|
|
965
85
|
isDynamic: true,
|
|
966
|
-
dynamicSegments: ["slug"]
|
|
967
|
-
|
|
968
|
-
// Locale-specific paths (typed by Locale enum)
|
|
969
|
-
vi: "/san-pham/[slug]", // /vi/san-pham/blue-shirt
|
|
970
|
-
en: "/products/[slug]", // /en/products/blue-shirt
|
|
971
|
-
},
|
|
972
|
-
type: "product",
|
|
973
|
-
// ...
|
|
974
|
-
};
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
The `Locale` type (`"vi" | "en"`) is from `@onexapis/core/types` — gives autocomplete.
|
|
978
|
-
|
|
979
|
-
Sections also receive `data.locale` to know the current locale:
|
|
980
|
-
|
|
981
|
-
```tsx
|
|
982
|
-
const locale = data?.locale as string; // "vi" or "en"
|
|
983
|
-
```
|
|
984
|
-
|
|
985
|
-
### Key conventions
|
|
986
|
-
|
|
987
|
-
- `path` uses bracket syntax: `/products/[slug]`, `/blog/[slug]`
|
|
988
|
-
- `localizedPaths` maps `Locale` → path for locale-specific URLs
|
|
989
|
-
- `dynamicSegments` lists parameter names extracted from the path
|
|
990
|
-
- `type: "product"` or `"blog"` indicates what kind of detail page
|
|
991
|
-
- Sections access params via `data.routeParams.slug` (or whatever parameter name)
|
|
992
|
-
- Sections access locale via `data.locale` or `useLocale()` hook
|
|
993
|
-
- Use `useProductBySlug()` / `useBlogBySlug()` hooks to fetch detail data
|
|
994
|
-
|
|
995
|
-
## Product & Blog Types
|
|
996
|
-
|
|
997
|
-
### Product
|
|
998
|
-
|
|
999
|
-
```tsx
|
|
1000
|
-
{
|
|
1001
|
-
id, title, slug, category, categoryLabel,
|
|
1002
|
-
image: { url, alt },
|
|
1003
|
-
images: [{ url, alt }],
|
|
1004
|
-
salePrice, originalPrice, discount,
|
|
1005
|
-
inStock: boolean,
|
|
1006
|
-
badge: "hot" | "new" | "sale" | null,
|
|
1007
|
-
rating, reviewCount,
|
|
1008
|
-
variants: [{ sku, options, prices }],
|
|
1009
|
-
tags: string[],
|
|
1010
|
-
}
|
|
1011
|
-
```
|
|
1012
|
-
|
|
1013
|
-
### Blog
|
|
1014
|
-
|
|
1015
|
-
```tsx
|
|
1016
|
-
{
|
|
1017
|
-
id, title, slug, description, excerpt,
|
|
1018
|
-
content: string (HTML, detail only),
|
|
1019
|
-
image, coverImage,
|
|
1020
|
-
category, categoryLabel,
|
|
1021
|
-
author: { name, avatar },
|
|
1022
|
-
createdAt, publishedAt,
|
|
1023
|
-
tags: string[],
|
|
1024
|
-
locale,
|
|
1025
|
-
}
|
|
1026
|
-
```
|
|
1027
|
-
|
|
1028
|
-
### WebsiteSettings
|
|
1029
|
-
|
|
1030
|
-
```tsx
|
|
1031
|
-
{
|
|
1032
|
-
social_connections: [{ name, icon, link, platform, enabled }],
|
|
1033
|
-
hotline_connections: [{ name, type, value, enabled }],
|
|
1034
|
-
company_info: { name, tagline, description, logo_url },
|
|
1035
|
-
contact_email: { email, enabled },
|
|
1036
|
-
}
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
## Context Provider Hierarchy
|
|
1040
|
-
|
|
1041
|
-
The rendering tree wraps sections in multiple context providers:
|
|
1042
|
-
|
|
1043
|
-
```
|
|
1044
|
-
PageDataProvider (products, blogs, settings, company)
|
|
1045
|
-
→ MotionProvider (animation tokens)
|
|
1046
|
-
→ ViewportProvider (isEditing, viewportMode)
|
|
1047
|
-
→ LocaleProvider (locale, supportedLocales)
|
|
1048
|
-
→ ThemeModeProvider (light/dark mode)
|
|
1049
|
-
→ CartProvider (shopping cart)
|
|
1050
|
-
→ SectionListRenderer
|
|
1051
|
-
→ SectionRenderer (per section)
|
|
1052
|
-
→ AnimationWrapper
|
|
1053
|
-
→ Your Section Component
|
|
1054
|
-
```
|
|
1055
|
-
|
|
1056
|
-
All contexts are accessible via hooks from `@onexapis/core/hooks`.
|
|
1057
|
-
|
|
1058
|
-
## Editor Integration
|
|
1059
|
-
|
|
1060
|
-
When `isEditing = true` (section is rendered in the editor preview):
|
|
1061
|
-
|
|
1062
|
-
```tsx
|
|
1063
|
-
export function MySection({ section, isEditing }: SectionComponentProps) {
|
|
1064
|
-
return (
|
|
1065
|
-
<section>
|
|
1066
|
-
{/* Show empty state placeholder in editor */}
|
|
1067
|
-
{items.length === 0 && isEditing && (
|
|
1068
|
-
<div className="border-2 border-dashed border-gray-300 p-8 text-center text-gray-500">
|
|
1069
|
-
Add items to this section
|
|
1070
|
-
</div>
|
|
1071
|
-
)}
|
|
1072
|
-
|
|
1073
|
-
{/* Disable navigation in editor */}
|
|
1074
|
-
{isEditing ? (
|
|
1075
|
-
<span className="cursor-default">{linkText}</span>
|
|
1076
|
-
) : (
|
|
1077
|
-
<a href={url}>{linkText}</a>
|
|
1078
|
-
)}
|
|
1079
|
-
</section>
|
|
1080
|
-
);
|
|
1081
|
-
}
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
## Page Background System
|
|
1085
|
-
|
|
1086
|
-
Every page has a background that comes from the theme by default, overridable per-page. This is a **page-level** feature (not section-level) — it wraps all sections on the page.
|
|
1087
|
-
|
|
1088
|
-
### How it works
|
|
1089
|
-
|
|
1090
|
-
Background is resolved with priority:
|
|
1091
|
-
|
|
1092
|
-
1. **Per-page override** (`PageConfig.background`) — set via editor or page config
|
|
1093
|
-
2. **Theme default** (`ThemeDesignSystem.pageBackground`) — set in `theme.layout.ts`
|
|
1094
|
-
3. **Fallback** — solid white `#FFFFFF`
|
|
1095
|
-
|
|
1096
|
-
### Setting the theme default
|
|
1097
|
-
|
|
1098
|
-
In `theme.layout.ts`, add `pageBackground` inside `designSystem`:
|
|
1099
|
-
|
|
1100
|
-
```typescript
|
|
1101
|
-
export const layoutConfig: ThemeLayoutConfig = {
|
|
1102
|
-
// ...headerSections, footerSections...
|
|
1103
|
-
designSystem: {
|
|
1104
|
-
colors: {
|
|
1105
|
-
primaryColor: "#3B82F6",
|
|
1106
|
-
secondaryColor: "#8B5CF6",
|
|
1107
|
-
colorMode: "light",
|
|
1108
|
-
},
|
|
1109
|
-
typography: { headingFont: "system-ui", bodyFont: "system-ui" },
|
|
1110
|
-
layout: { spacing: "comfortable" },
|
|
1111
|
-
pageBackground: {
|
|
1112
|
-
type: "solid", // "solid" | "gradient" | "image" | "pattern" | "none"
|
|
1113
|
-
color: "#FFFFFF", // Base background color
|
|
1114
|
-
},
|
|
1115
|
-
},
|
|
1116
|
-
};
|
|
1117
|
-
```
|
|
1118
|
-
|
|
1119
|
-
### Background types
|
|
1120
|
-
|
|
1121
|
-
| Type | Fields | Example |
|
|
1122
|
-
| ---------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
1123
|
-
| `solid` | `color` | `{ type: "solid", color: "#FFF8F0" }` |
|
|
1124
|
-
| `gradient` | `gradient`, `color` (fallback) | `{ type: "gradient", gradient: "linear-gradient(135deg, #667eea, #764ba2)" }` |
|
|
1125
|
-
| `image` | `image`, `imageSize`, `imagePosition`, `imageFixed`, `color` (fallback) | `{ type: "image", image: "/bg.jpg", imageSize: "cover", imageFixed: true }` |
|
|
1126
|
-
| `pattern` | `pattern`, `color`, `opacity`, `overlayColor` | `{ type: "pattern", pattern: "dots", color: "#FAFAFA", opacity: 0.05 }` |
|
|
1127
|
-
| `none` | — | `{ type: "none" }` (transparent, no wrapper) |
|
|
1128
|
-
|
|
1129
|
-
### Built-in CSS patterns
|
|
1130
|
-
|
|
1131
|
-
These work out of the box with `type: "pattern"`:
|
|
1132
|
-
|
|
1133
|
-
- `dots` — dot grid
|
|
1134
|
-
- `grid` — line grid
|
|
1135
|
-
- `diagonal-lines` — 45-degree stripes
|
|
1136
|
-
- `cross-dots` — offset dot grid
|
|
1137
|
-
- `noise` — fractal noise texture
|
|
1138
|
-
|
|
1139
|
-
### Custom animated patterns
|
|
1140
|
-
|
|
1141
|
-
Themes can provide custom pattern renderers (e.g., SVG-based animated backgrounds). Export the component from `bundle-entry.ts`:
|
|
1142
|
-
|
|
1143
|
-
```typescript
|
|
1144
|
-
// In bundle-entry.ts
|
|
1145
|
-
export { SenBackground } from "./assets/sen-background";
|
|
1146
|
-
```
|
|
1147
|
-
|
|
1148
|
-
The storefront will pick up exported `*Background` components and match them to pattern names. For example, `SenBackground` maps to `pattern: "sen"`.
|
|
1149
|
-
|
|
1150
|
-
Custom pattern components receive these props:
|
|
1151
|
-
|
|
1152
|
-
```typescript
|
|
1153
|
-
interface PatternProps {
|
|
1154
|
-
opacity?: number; // Pattern opacity (default: 0.05)
|
|
1155
|
-
color?: string; // Overlay color
|
|
1156
|
-
className?: string; // Additional CSS classes
|
|
1157
|
-
}
|
|
1158
|
-
```
|
|
1159
|
-
|
|
1160
|
-
### Per-page override
|
|
1161
|
-
|
|
1162
|
-
In a page config (e.g., `pages/about.ts`), set `background`:
|
|
1163
|
-
|
|
1164
|
-
```typescript
|
|
1165
|
-
export const aboutPageConfig = {
|
|
1166
|
-
title: "About",
|
|
1167
|
-
handle: "about",
|
|
1168
|
-
// ...other config...
|
|
1169
|
-
background: {
|
|
1170
|
-
type: "gradient",
|
|
1171
|
-
gradient: "linear-gradient(180deg, #EEF2FF 0%, #FFFFFF 50%)",
|
|
1172
|
-
},
|
|
1173
|
-
};
|
|
1174
|
-
```
|
|
1175
|
-
|
|
1176
|
-
### Accessing in sections via hook
|
|
1177
|
-
|
|
1178
|
-
Use `usePageBackground()` to read the resolved background in section components:
|
|
1179
|
-
|
|
1180
|
-
```tsx
|
|
1181
|
-
import { usePageBackground } from "@onexapis/core/hooks";
|
|
1182
|
-
|
|
1183
|
-
export function MySection({ section }: SectionComponentProps) {
|
|
1184
|
-
const { config, styles, hasPattern, pattern, patternOpacity } =
|
|
1185
|
-
usePageBackground();
|
|
1186
|
-
// config.type — "solid" | "gradient" | "image" | "pattern" | "none"
|
|
1187
|
-
// config.color — base color
|
|
1188
|
-
// styles — React.CSSProperties derived from config
|
|
1189
|
-
// hasPattern — boolean, true if type === "pattern" && pattern is set
|
|
1190
|
-
// pattern — pattern name (e.g., "sen", "dots")
|
|
1191
|
-
// patternOpacity — number (0-1)
|
|
1192
|
-
}
|
|
1193
|
-
```
|
|
1194
|
-
|
|
1195
|
-
### PageBackgroundConfig type reference
|
|
1196
|
-
|
|
1197
|
-
```typescript
|
|
1198
|
-
interface PageBackgroundConfig {
|
|
1199
|
-
type: "solid" | "gradient" | "image" | "pattern" | "none";
|
|
1200
|
-
color?: string; // Base background color (hex/CSS)
|
|
1201
|
-
gradient?: string; // CSS gradient value
|
|
1202
|
-
image?: string; // Background image URL
|
|
1203
|
-
imageSize?: "cover" | "contain" | "auto";
|
|
1204
|
-
imagePosition?: string; // CSS background-position (default: "center")
|
|
1205
|
-
imageFixed?: boolean; // Fixed background attachment
|
|
1206
|
-
pattern?: string; // Pattern identifier
|
|
1207
|
-
opacity?: number; // Pattern/overlay opacity (0-1, default: 0.05)
|
|
1208
|
-
overlayColor?: string; // Pattern overlay color
|
|
1209
|
-
className?: string; // Additional CSS classes
|
|
1210
|
-
}
|
|
1211
|
-
```
|
|
1212
|
-
|
|
1213
|
-
### CSS variables
|
|
1214
|
-
|
|
1215
|
-
The storefront injects these CSS variables when a page background is configured:
|
|
1216
|
-
|
|
1217
|
-
- `--page-bg-color` — base color
|
|
1218
|
-
- `--page-bg-gradient` — gradient value
|
|
1219
|
-
- `--page-bg-pattern` — pattern name
|
|
1220
|
-
- `--page-bg-pattern-opacity` — opacity value
|
|
1221
|
-
- `--page-bg-overlay-color` — overlay color
|
|
1222
|
-
|
|
1223
|
-
Use these in custom CSS if needed: `background-color: var(--page-bg-color);`
|
|
1224
|
-
|
|
1225
|
-
## Dark/Light Mode
|
|
1226
|
-
|
|
1227
|
-
Themes support light, dark, and system color modes via `useThemeMode()`:
|
|
1228
|
-
|
|
1229
|
-
```tsx
|
|
1230
|
-
import { useThemeMode } from "@onexapis/core/hooks";
|
|
1231
|
-
|
|
1232
|
-
export function MySection({ section }: SectionComponentProps) {
|
|
1233
|
-
const { mode, isDark } = useThemeMode();
|
|
1234
|
-
// mode: "light" | "dark" | "system"
|
|
1235
|
-
// isDark: boolean (resolved — accounts for system preference)
|
|
1236
|
-
|
|
1237
|
-
return (
|
|
1238
|
-
<section
|
|
1239
|
-
style={{
|
|
1240
|
-
backgroundColor: isDark ? "#1F2937" : "#FFFFFF",
|
|
1241
|
-
color: isDark ? "#F9FAFB" : "#111827",
|
|
1242
|
-
}}
|
|
1243
|
-
>
|
|
1244
|
-
{/* Content adapts to color mode */}
|
|
1245
|
-
</section>
|
|
1246
|
-
);
|
|
1247
|
-
}
|
|
1248
|
-
```
|
|
1249
|
-
|
|
1250
|
-
### How dark mode works
|
|
1251
|
-
|
|
1252
|
-
- The `ThemeModeProvider` at the app root sets a `dark` class on the HTML element
|
|
1253
|
-
- Tailwind's `dark:` variants work automatically: `className="bg-white dark:bg-gray-900"`
|
|
1254
|
-
- `useThemeMode()` gives you the current mode for JS-driven styling
|
|
1255
|
-
- The design system's `colorMode` field determines the default mode
|
|
1256
|
-
- Users can toggle via the editor's theme settings
|
|
1257
|
-
|
|
1258
|
-
### Best practices
|
|
1259
|
-
|
|
1260
|
-
- Use Tailwind's `dark:` variant for simple color swaps: `text-gray-900 dark:text-white`
|
|
1261
|
-
- Use `useThemeMode()` for complex conditional logic
|
|
1262
|
-
- Test sections in both modes — don't assume light mode only
|
|
1263
|
-
- CSS variables (`--background`, `--foreground`) auto-switch with dark mode
|
|
1264
|
-
|
|
1265
|
-
## Locale / i18n
|
|
1266
|
-
|
|
1267
|
-
Themes support multiple languages via `useLocale()`:
|
|
1268
|
-
|
|
1269
|
-
```tsx
|
|
1270
|
-
import { useLocale } from "@onexapis/core/hooks";
|
|
1271
|
-
|
|
1272
|
-
export function MySection({ section }: SectionComponentProps) {
|
|
1273
|
-
const { locale, defaultLocale, supportedLocales } = useLocale();
|
|
1274
|
-
// locale: "vi" | "en" | ...
|
|
1275
|
-
// defaultLocale: "vi"
|
|
1276
|
-
// supportedLocales: ["vi", "en"]
|
|
1277
|
-
|
|
1278
|
-
return (
|
|
1279
|
-
<section>{locale === "vi" ? <h1>Xin chào</h1> : <h1>Hello</h1>}</section>
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
```
|
|
1283
|
-
|
|
1284
|
-
### How i18n works
|
|
1285
|
-
|
|
1286
|
-
- The default locale is `"vi"` (Vietnamese)
|
|
1287
|
-
- Supported locales: `["vi", "en"]`
|
|
1288
|
-
- Page content is stored per-locale in the database
|
|
1289
|
-
- The API supports `?locale=en` query parameter to get translated content
|
|
1290
|
-
- Translation overrides are stored as diffs from the base (default locale) page
|
|
1291
|
-
- The `LocaleProvider` at the app root provides the current locale via context
|
|
1292
|
-
|
|
1293
|
-
### Best practices
|
|
1294
|
-
|
|
1295
|
-
- Section components usually DON'T need i18n logic — the content comes from the editor (already translated)
|
|
1296
|
-
- The `ComponentRenderer` renders text from `component.content.text` which is locale-specific
|
|
1297
|
-
- Only use `useLocale()` if you need locale-aware formatting (dates, numbers, currency)
|
|
1298
|
-
- Use `toLocaleString()` for numbers: `price.toLocaleString("vi-VN")`
|
|
1299
|
-
- Use `Intl.DateTimeFormat` for dates: `new Intl.DateTimeFormat(locale).format(date)`
|
|
1300
|
-
|
|
1301
|
-
## Section Registry
|
|
1302
|
-
|
|
1303
|
-
Every section must be registered in `sections-registry.ts` with lazy imports:
|
|
1304
|
-
|
|
1305
|
-
```tsx
|
|
1306
|
-
export const sectionsRegistry = {
|
|
1307
|
-
hero: () => import("./sections/hero"),
|
|
1308
|
-
features: () => import("./sections/features"),
|
|
1309
|
-
pricing: () => import("./sections/pricing"),
|
|
1310
|
-
// Add new sections here
|
|
1311
|
-
};
|
|
1312
|
-
```
|
|
1313
|
-
|
|
1314
|
-
The build system uses this registry to code-split sections into the bundle.
|
|
1315
|
-
|
|
1316
|
-
## Rules
|
|
1317
|
-
|
|
1318
|
-
### DO
|
|
1319
|
-
|
|
1320
|
-
- Use `ComponentRenderer` / `BlockRenderer` from `@onexapis/core/renderers` for nested content
|
|
1321
|
-
- Use `getSectionValues(section, schema)` to extract typed settings
|
|
1322
|
-
- Use `filterEnabledComponents()` to skip disabled components
|
|
1323
|
-
- Use `toComponentInstance()` to convert raw component data for ComponentRenderer
|
|
1324
|
-
- Include `data-section-id`, `data-block-id` attributes on wrapper elements
|
|
1325
|
-
- Handle the `isEditing` prop (show placeholders, disable navigation links)
|
|
1326
|
-
- Use `"use client"` directive at the top of section components
|
|
1327
|
-
- Use Tailwind CSS classes for layout (included in the build)
|
|
1328
|
-
- Declare `dataRequirements` in schema if section needs products/blogs/settings
|
|
1329
|
-
|
|
1330
|
-
### DON'T
|
|
1331
|
-
|
|
1332
|
-
- Don't import from `@onexapis/core/internal` — it's a private API
|
|
1333
|
-
- Don't use `document` or `window` without checking `typeof window !== "undefined"`
|
|
1334
|
-
- Don't hardcode API URLs — use hooks (`useProducts`, `useBlogs`)
|
|
1335
|
-
- Don't use `useEffect` for data fetching — use React Query hooks instead
|
|
1336
|
-
- Don't mutate `section.settings` directly — use the `onSettingsChange` callback
|
|
1337
|
-
- Don't use `require()` — themes are ES modules
|
|
1338
|
-
- Don't import React explicitly — it's auto-injected by the JSX transform
|
|
1339
|
-
|
|
1340
|
-
## CLI Commands
|
|
1341
|
-
|
|
1342
|
-
```bash
|
|
1343
|
-
onexthm init my-theme # Scaffold new theme project
|
|
1344
|
-
onexthm create:section hero # Create section (component + schema + index)
|
|
1345
|
-
onexthm create:block card # Create block
|
|
1346
|
-
onexthm create:component badge # Create component
|
|
1347
|
-
onexthm list # List all sections/blocks/components
|
|
1348
|
-
onexthm validate # Validate theme structure
|
|
1349
|
-
onexthm dev # Start dev server with live preview
|
|
1350
|
-
onexthm build # Compile theme for production
|
|
1351
|
-
onexthm upload --theme my-theme # Upload bundle.zip to S3
|
|
1352
|
-
onexthm clone simple # Clone an existing theme from S3
|
|
1353
|
-
onexthm download -t simple # Download compiled theme
|
|
1354
|
-
onexthm config # Configure AWS/API credentials
|
|
1355
|
-
```
|
|
1356
|
-
|
|
1357
|
-
## Example: Product Grid Section
|
|
1358
|
-
|
|
1359
|
-
```tsx
|
|
1360
|
-
"use client";
|
|
1361
|
-
|
|
1362
|
-
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
1363
|
-
import { useProducts, useCart } from "@onexapis/core/hooks";
|
|
1364
|
-
import coreUtils from "@onexapis/core/utils";
|
|
1365
|
-
|
|
1366
|
-
const { getSectionValues } = coreUtils;
|
|
1367
|
-
|
|
1368
|
-
export function ProductGrid({
|
|
1369
|
-
section,
|
|
1370
|
-
schema,
|
|
1371
|
-
isEditing,
|
|
1372
|
-
}: SectionComponentProps) {
|
|
1373
|
-
const { settings } = getSectionValues(section, schema);
|
|
1374
|
-
const limit = Number(settings.limit) || 8;
|
|
1375
|
-
const columns = Number(settings.columns) || 3;
|
|
1376
|
-
|
|
1377
|
-
const { data, isLoading } = useProducts({ limit });
|
|
1378
|
-
const { addItem } = useCart();
|
|
1379
|
-
const products = data?.data ?? [];
|
|
1380
|
-
|
|
1381
|
-
if (isLoading) {
|
|
1382
|
-
return (
|
|
1383
|
-
<section
|
|
1384
|
-
data-section-id={section.id}
|
|
1385
|
-
data-section-type="product-grid"
|
|
1386
|
-
className="py-16"
|
|
1387
|
-
>
|
|
1388
|
-
<div className="container mx-auto px-4">
|
|
1389
|
-
<div className="animate-pulse grid grid-cols-3 gap-4">
|
|
1390
|
-
{Array.from({ length: limit }).map((_, i) => (
|
|
1391
|
-
<div key={i} className="h-64 bg-gray-200 rounded-lg" />
|
|
1392
|
-
))}
|
|
1393
|
-
</div>
|
|
1394
|
-
</div>
|
|
1395
|
-
</section>
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
const colClass =
|
|
1400
|
-
columns === 2
|
|
1401
|
-
? "md:grid-cols-2"
|
|
1402
|
-
: columns === 4
|
|
1403
|
-
? "md:grid-cols-4"
|
|
1404
|
-
: "md:grid-cols-3";
|
|
1405
|
-
|
|
1406
|
-
return (
|
|
1407
|
-
<section
|
|
1408
|
-
data-section-id={section.id}
|
|
1409
|
-
data-section-type="product-grid"
|
|
1410
|
-
className="py-16"
|
|
1411
|
-
style={{ backgroundColor: String(settings.backgroundColor || "#FFFFFF") }}
|
|
1412
|
-
>
|
|
1413
|
-
<div className="container mx-auto px-4 max-w-6xl">
|
|
1414
|
-
<div className={`grid grid-cols-1 ${colClass} gap-6`}>
|
|
1415
|
-
{products.map((product) => (
|
|
1416
|
-
<div
|
|
1417
|
-
key={product.id}
|
|
1418
|
-
className="border rounded-lg p-4 hover:shadow-md transition-shadow"
|
|
1419
|
-
>
|
|
1420
|
-
{product.image?.url && (
|
|
1421
|
-
<img
|
|
1422
|
-
src={product.image.url}
|
|
1423
|
-
alt={product.name}
|
|
1424
|
-
className="w-full h-48 object-cover rounded mb-3"
|
|
1425
|
-
/>
|
|
1426
|
-
)}
|
|
1427
|
-
<h3 className="font-semibold text-lg">{product.name}</h3>
|
|
1428
|
-
<p className="text-gray-600 mt-1">
|
|
1429
|
-
{product.price?.toLocaleString()}đ
|
|
1430
|
-
</p>
|
|
1431
|
-
{!isEditing && (
|
|
1432
|
-
<button
|
|
1433
|
-
onClick={() => addItem(product, 1)}
|
|
1434
|
-
className="mt-3 w-full py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
|
1435
|
-
>
|
|
1436
|
-
Add to Cart
|
|
1437
|
-
</button>
|
|
1438
|
-
)}
|
|
1439
|
-
</div>
|
|
1440
|
-
))}
|
|
1441
|
-
</div>
|
|
1442
|
-
</div>
|
|
1443
|
-
</section>
|
|
1444
|
-
);
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
export default ProductGrid;
|
|
1448
|
-
```
|
|
1449
|
-
|
|
1450
|
-
## Cart & Fly-to-Cart Animation
|
|
1451
|
-
|
|
1452
|
-
The cart system uses `useCart()` for state and a flexible fly-to-cart animation triggered by `data-fly-to-cart-target`.
|
|
1453
|
-
|
|
1454
|
-
### Cart State
|
|
1455
|
-
|
|
1456
|
-
```tsx
|
|
1457
|
-
import { useCart } from "@onexapis/core/hooks";
|
|
1458
|
-
|
|
1459
|
-
const {
|
|
1460
|
-
items,
|
|
1461
|
-
addItem,
|
|
1462
|
-
removeItem,
|
|
1463
|
-
updateQuantity,
|
|
1464
|
-
clearCart,
|
|
1465
|
-
itemCount,
|
|
1466
|
-
subtotal,
|
|
1467
|
-
} = useCart();
|
|
1468
|
-
|
|
1469
|
-
// Add to cart
|
|
1470
|
-
addItem({
|
|
1471
|
-
productId: product.id,
|
|
1472
|
-
name: product.title,
|
|
1473
|
-
image: product.image,
|
|
1474
|
-
price: product.salePrice,
|
|
1475
|
-
slug: product.slug,
|
|
1476
|
-
});
|
|
1477
|
-
```
|
|
1478
|
-
|
|
1479
|
-
### Fly-to-Cart Animation
|
|
1480
|
-
|
|
1481
|
-
When a user clicks "Add to Cart", the product thumbnail flies from the button to the cart icon in the header.
|
|
1482
|
-
|
|
1483
|
-
**Step 1: Mark any element as the cart target** (in header section):
|
|
1484
|
-
|
|
1485
|
-
```tsx
|
|
1486
|
-
// Option A: Use any element — just add the data attribute
|
|
1487
|
-
<div data-fly-to-cart-target className="my-cart-icon">
|
|
1488
|
-
<ShoppingCartIcon />
|
|
1489
|
-
<span>{itemCount}</span>
|
|
1490
|
-
</div>;
|
|
1491
|
-
|
|
1492
|
-
// Option B: Use CartIcon convenience component (auto-adds data-fly-to-cart-target)
|
|
1493
|
-
import { CartIcon } from "@onexapis/core/components";
|
|
1494
|
-
<CartIcon count={itemCount} onClick={() => openCartDrawer()} />;
|
|
1495
|
-
```
|
|
1496
|
-
|
|
1497
|
-
**Step 2: ProductCard triggers animation automatically** — just pass `onAddToCart`:
|
|
1498
|
-
|
|
1499
|
-
```tsx
|
|
1500
|
-
<ProductCard
|
|
1501
|
-
product={product}
|
|
1502
|
-
onAddToCart={(p) =>
|
|
1503
|
-
addItem({
|
|
1504
|
-
productId: p.id,
|
|
1505
|
-
name: p.title,
|
|
1506
|
-
image: p.image,
|
|
1507
|
-
price: p.salePrice,
|
|
1508
|
-
})
|
|
1509
|
-
}
|
|
1510
|
-
/>
|
|
1511
|
-
```
|
|
1512
|
-
|
|
1513
|
-
**Step 3: Custom trigger** (for buttons that aren't ProductCard):
|
|
1514
|
-
|
|
1515
|
-
```tsx
|
|
1516
|
-
import { useFlyToCart } from "@onexapis/core/hooks";
|
|
1517
|
-
|
|
1518
|
-
const { flyToCart } = useFlyToCart();
|
|
1519
|
-
|
|
1520
|
-
<button onClick={(e) => {
|
|
1521
|
-
flyToCart(e.currentTarget, product.image); // animation
|
|
1522
|
-
addItem({ ... }); // cart logic
|
|
1523
|
-
}}>
|
|
1524
|
-
Buy Now
|
|
1525
|
-
</button>
|
|
1526
|
-
```
|
|
1527
|
-
|
|
1528
|
-
### How it works
|
|
1529
|
-
|
|
1530
|
-
- `data-fly-to-cart-target` on any element → animation lands there (detected via `querySelector`)
|
|
1531
|
-
- `FlyToCartProvider` renders a portal with the flying thumbnail (CSS transition)
|
|
1532
|
-
- No refs or context wiring needed — just add the data attribute
|
|
1533
|
-
- Without `data-fly-to-cart-target` → cart still works, no animation
|
|
1534
|
-
|
|
1535
|
-
## Server-Side APIs
|
|
1536
|
-
|
|
1537
|
-
Server-side data fetching is available from `@onexapis/core/server`. Use in Next.js server components, server actions, or API routes.
|
|
1538
|
-
|
|
1539
|
-
```tsx
|
|
1540
|
-
import {
|
|
1541
|
-
fetchProducts,
|
|
1542
|
-
fetchBlogs,
|
|
1543
|
-
fetchSettings,
|
|
1544
|
-
fetchCompany,
|
|
1545
|
-
fetchTheme,
|
|
1546
|
-
fetchPage,
|
|
1547
|
-
prefetchSectionData,
|
|
1548
|
-
} from "@onexapis/core/server";
|
|
1549
|
-
|
|
1550
|
-
// Fetch products (server-side with ISR caching)
|
|
1551
|
-
const { data: products, pagination } = await fetchProducts(companyId, {
|
|
1552
|
-
limit: 12,
|
|
1553
|
-
});
|
|
1554
|
-
|
|
1555
|
-
// Fetch website settings
|
|
1556
|
-
const settings = await fetchSettings(companyId);
|
|
1557
|
-
|
|
1558
|
-
// Smart prefetch — scans sections for dataRequirements, fetches in parallel
|
|
1559
|
-
const data = await prefetchSectionData({
|
|
1560
|
-
companyId,
|
|
1561
|
-
sections: allSections,
|
|
1562
|
-
dataRequirements: manifest.dataRequirements,
|
|
1563
|
-
});
|
|
1564
|
-
// data.products, data.blogs, data.settings
|
|
1565
|
-
```
|
|
1566
|
-
|
|
1567
|
-
### Advanced: Custom server client
|
|
1568
|
-
|
|
1569
|
-
```tsx
|
|
1570
|
-
import { CommerceServerClient, noopCacheAdapter } from "@onexapis/core/server";
|
|
1571
|
-
|
|
1572
|
-
const client = new CommerceServerClient({
|
|
1573
|
-
apiUrl: "https://api.example.com",
|
|
1574
|
-
companyId: "xxx",
|
|
1575
|
-
cacheAdapter: noopCacheAdapter, // For non-Next.js environments
|
|
1576
|
-
});
|
|
1577
|
-
|
|
1578
|
-
const products = await client.getProducts({ limit: 8 });
|
|
1579
|
-
const blog = await client.getBlogBySlug("my-post");
|
|
1580
|
-
```
|
|
1581
|
-
|
|
1582
|
-
Environment variables auto-detected: `NEXT_PUBLIC_COMMERCE_API_URL`, `NEXT_PUBLIC_API_URL`.
|
|
1583
|
-
|
|
1584
|
-
## MCP Servers
|
|
1585
|
-
|
|
1586
|
-
This project has THREE MCP servers. Do NOT confuse them:
|
|
1587
|
-
|
|
1588
|
-
### `onexthm` MCP (Theme Development) — USE THIS
|
|
1589
|
-
|
|
1590
|
-
Registered in `.mcp.json` in this project. Provides theme-specific tools:
|
|
1591
|
-
|
|
1592
|
-
- `onexthm_create_section` — Generate section files (component + schema + index) from structured input
|
|
1593
|
-
- `onexthm_from_html` — **Convert HTML/React/JSX code to OneX section** (parses elements, maps to components, detects blocks)
|
|
1594
|
-
- `onexthm_from_figma` — **Convert Figma design to OneX section** (see Figma Integration below)
|
|
1595
|
-
- `onexthm_generate_schema` — Generate schema from natural language description
|
|
1596
|
-
- `onexthm_validate` — Validate theme structure (7 checks: entry file, config, sections, blocks, code quality, registry)
|
|
1597
|
-
- `onexthm_list_hooks` — List available hooks with usage examples
|
|
1598
|
-
|
|
1599
|
-
Resources (auto-loaded context):
|
|
1600
|
-
|
|
1601
|
-
- `onexthm://rules` — Theme development rules (DOs/DON'Ts)
|
|
1602
|
-
- `onexthm://field-types` — All available field types and categories
|
|
1603
|
-
- `onexthm://hooks` — Hooks reference with examples
|
|
1604
|
-
- `onexthm://components` — All component types with fields, slots, examples
|
|
1605
|
-
- `onexthm://blocks` — Block system patterns and data attributes
|
|
1606
|
-
|
|
1607
|
-
Prompts (guided workflows):
|
|
1608
|
-
|
|
1609
|
-
- `create_section` — Guided section creation workflow
|
|
1610
|
-
- `html_to_section` — Convert HTML/React code to OneX section
|
|
1611
|
-
- `figma_to_section` — Full Figma-to-OneX conversion pipeline
|
|
1612
|
-
- `review_theme` — Review theme for issues
|
|
1613
|
-
|
|
1614
|
-
### `figma` MCP (Figma Design) — USE WITH `onexthm`
|
|
1615
|
-
|
|
1616
|
-
Registered in `.mcp.json`. Reads Figma designs for design-to-code conversion:
|
|
1617
|
-
|
|
1618
|
-
- `get_metadata` — Layer hierarchy (IDs, names, types, positions, sizes)
|
|
1619
|
-
- `get_design_context` — React + Tailwind code suggestion for selected layers
|
|
1620
|
-
- `get_variable_defs` — Design tokens (colors, typography, spacing variables)
|
|
1621
|
-
- `get_screenshot` — Visual screenshot of selected elements
|
|
1622
|
-
- `search_design_system` — Search components, variables, styles from libraries
|
|
1623
|
-
|
|
1624
|
-
**Setup**: Requires Figma API key in `.mcp.json`:
|
|
1625
|
-
|
|
1626
|
-
```json
|
|
1627
|
-
{
|
|
1628
|
-
"figma": {
|
|
1629
|
-
"command": "npx",
|
|
1630
|
-
"args": ["-y", "figma-developer-mcp", "--figma-api-key=YOUR_KEY", "--stdio"]
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
```
|
|
1634
|
-
|
|
1635
|
-
Get your key: Figma → Settings → Account → Personal access tokens.
|
|
1636
|
-
|
|
1637
|
-
### `onex-platform` MCP (Backend Services) — DO NOT USE FOR THEMES
|
|
1638
|
-
|
|
1639
|
-
This is for managing microservices on the OneXEOS platform. Its tools are:
|
|
1640
|
-
|
|
1641
|
-
- `onex_invoke`, `onex_status`, `onex_deploy`, `onex_logs`, `onex_test`
|
|
1642
|
-
- These manage backend services (auth, product, order, etc.)
|
|
1643
|
-
- **Do NOT use these for theme development** — they have nothing to do with sections, schemas, or theme components
|
|
1644
|
-
|
|
1645
|
-
### When to use which
|
|
1646
|
-
|
|
1647
|
-
| Task | MCP to use |
|
|
1648
|
-
| -------------------------------- | ------------------------------------ |
|
|
1649
|
-
| Create a new section | `onexthm_create_section` |
|
|
1650
|
-
| Convert HTML/React to section | `onexthm_from_html` |
|
|
1651
|
-
| Convert Figma design to section | `onexthm_from_figma` + `figma` tools |
|
|
1652
|
-
| Generate schema from description | `onexthm_generate_schema` |
|
|
1653
|
-
| Validate theme structure | `onexthm_validate` |
|
|
1654
|
-
| Look up available hooks | `onexthm_list_hooks` |
|
|
1655
|
-
| Read Figma design layers | `figma:get_metadata` |
|
|
1656
|
-
| Get Figma design tokens | `figma:get_variable_defs` |
|
|
1657
|
-
| Deploy a backend service | `onex_deploy` (platform MCP) |
|
|
1658
|
-
| Check service health | `onex_status` (platform MCP) |
|
|
1659
|
-
|
|
1660
|
-
## Figma → OneX Conversion
|
|
1661
|
-
|
|
1662
|
-
Convert Figma designs directly to OneX theme sections using the combined Figma + OneX MCP pipeline.
|
|
1663
|
-
|
|
1664
|
-
### Quick Start
|
|
1665
|
-
|
|
1666
|
-
Use the `figma_to_section` prompt for a guided workflow:
|
|
1667
|
-
|
|
1668
|
-
```
|
|
1669
|
-
"Convert the selected Figma frame to a section called 'hero'"
|
|
1670
|
-
```
|
|
1671
|
-
|
|
1672
|
-
Or manually orchestrate the steps:
|
|
86
|
+
dynamicSegments: ["slug"]
|
|
87
|
+
})
|
|
1673
88
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
2. figma:get_variable_defs → Get design tokens
|
|
1677
|
-
3. figma:get_design_context → Get React+Tailwind code
|
|
1678
|
-
4. onexthm:onexthm_from_figma → Convert to OneX section files
|
|
1679
|
-
5. Write files to sections/ directory
|
|
1680
|
-
6. onexthm:onexthm_validate → Validate
|
|
1681
|
-
7. Register in sections-registry.ts
|
|
1682
|
-
```
|
|
89
|
+
// Validate after any write
|
|
90
|
+
onexthm_validate({ projectDir: "<absolute path to this directory>" })
|
|
1683
91
|
|
|
1684
|
-
|
|
92
|
+
// Auto-fix validation errors
|
|
93
|
+
onexthm_fix({ projectDir: "<absolute path to this directory>" })
|
|
1685
94
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
────────────────────────────────────────────────
|
|
1689
|
-
Top-level Frame → Section
|
|
1690
|
-
Nested Frame/Group → Block
|
|
1691
|
-
TEXT (large >=24px, bold >=600) → heading (h1-h6)
|
|
1692
|
-
TEXT (body <24px) → paragraph
|
|
1693
|
-
Button instance → button component
|
|
1694
|
-
Image node → image component
|
|
1695
|
-
Icon instance → icon component
|
|
1696
|
-
Badge/Label → badge component
|
|
1697
|
-
Repeating children (same shape) → Block definition
|
|
1698
|
-
Background color fill → backgroundColor setting
|
|
1699
|
-
Background image fill → backgroundImage setting
|
|
1700
|
-
Auto Layout horizontal → flex flex-row (Tailwind)
|
|
1701
|
-
Auto Layout vertical → flex flex-col (Tailwind)
|
|
1702
|
-
Auto Layout gap → gap-N (Tailwind)
|
|
95
|
+
// Build the theme
|
|
96
|
+
onexthm_build({ projectDir: "<absolute path to this directory>" })
|
|
1703
97
|
```
|
|
1704
98
|
|
|
1705
|
-
|
|
99
|
+
All tools default to writing files on disk. Pass `dryRun: true` if the user
|
|
100
|
+
explicitly asks to preview without writing. Pass `force: true` to overwrite
|
|
101
|
+
existing files.
|
|
1706
102
|
|
|
1707
|
-
|
|
1708
|
-
{
|
|
1709
|
-
figmaMetadata: string, // Required: XML/JSON from figma:get_metadata
|
|
1710
|
-
figmaVariables?: string, // Optional: JSON from figma:get_variable_defs
|
|
1711
|
-
figmaCode?: string, // Optional: code from figma:get_design_context (fallback)
|
|
1712
|
-
sectionName: string, // Required: kebab-case name (e.g., "hero")
|
|
1713
|
-
category?: string, // Optional: section category
|
|
1714
|
-
themePrefix?: string, // Optional: type prefix (e.g., "my-simple")
|
|
1715
|
-
}
|
|
1716
|
-
```
|
|
1717
|
-
|
|
1718
|
-
Returns generated files: `{name}.schema.ts`, `{name}-default.tsx`, `index.ts`, plus registry entry.
|
|
103
|
+
---
|
|
1719
104
|
|
|
1720
|
-
|
|
105
|
+
## After a tool call
|
|
1721
106
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
category?: string, // Optional: section category
|
|
1727
|
-
themePrefix?: string, // Optional: type prefix (e.g., "my-simple")
|
|
1728
|
-
}
|
|
1729
|
-
```
|
|
107
|
+
1. Read the tool's response — it lists what was written and warns about anything skipped.
|
|
108
|
+
2. Run `onexthm_validate` if the tool didn't already.
|
|
109
|
+
3. Show the user the new file paths so they know where to edit further.
|
|
110
|
+
4. Only edit the generated files **after** they exist on disk — never re-generate by hand.
|
|
1730
111
|
|
|
1731
|
-
|
|
112
|
+
---
|
|
1732
113
|
|
|
1733
|
-
|
|
114
|
+
## When the answer isn't here
|
|
1734
115
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
<h1>-<h6> → heading (with tag h1-h6)
|
|
1739
|
-
<p>, <span> → paragraph
|
|
1740
|
-
<button>, <a class="btn/cta"> → button
|
|
1741
|
-
<a> → link
|
|
1742
|
-
<img> → image
|
|
1743
|
-
<svg>, icon components → icon
|
|
1744
|
-
<ul>/<ol> → list
|
|
1745
|
-
<hr> → divider
|
|
1746
|
-
<blockquote> → quote
|
|
1747
|
-
<input>, <textarea>, <select> → input/textarea/select
|
|
1748
|
-
<video> → video
|
|
1749
|
-
<table> → table
|
|
1750
|
-
<div>, <section>, <article> → layout containers (recurse)
|
|
1751
|
-
Repeating sibling structures → block definitions
|
|
1752
|
-
Tailwind flex/grid classes → layout settings
|
|
1753
|
-
Tailwind bg-* classes → backgroundColor setting
|
|
1754
|
-
Tailwind text-* classes → fontSize/textAlign
|
|
1755
|
-
React <Button>, <Card>, etc. → resolved to HTML equivalents
|
|
1756
|
-
```
|
|
116
|
+
If the user's question is about hooks, components, blocks, animation, dark
|
|
117
|
+
mode, locale, dynamic pages, payments, the cart, or any deep API question,
|
|
118
|
+
either:
|
|
1757
119
|
|
|
1758
|
-
|
|
120
|
+
- Read `THEME_REFERENCE.md` (lives next to this file), or
|
|
121
|
+
- Call `onexthm_list_hooks` for hook signatures, or
|
|
122
|
+
- Read the `onexthm://components`, `onexthm://blocks`, `onexthm://field-types`,
|
|
123
|
+
`onexthm://hooks`, or `onexthm://rules` MCP resources.
|
|
1759
124
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
Spacing: 4→1 8→2 12→3 16→4 20→5 24→6 32→8 40→10 48→12 64→16 80→20 96→24
|
|
1763
|
-
Weight: 100-400→normal 500→medium 600→semibold 700+→bold
|
|
1764
|
-
```
|
|
125
|
+
`THEME_REFERENCE.md` is the canonical reference document for everything not
|
|
126
|
+
covered by these routing rules.
|