@sudajs/cli 0.7.2 → 0.8.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudajs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"suda": "./bin/suda.js"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"react": "^19.2.7",
|
|
33
33
|
"react-dom": "^19.2.7",
|
|
34
34
|
"zod": "^3.24.1",
|
|
35
|
-
"@sudajs/theme-engine": "2.0
|
|
35
|
+
"@sudajs/theme-engine": "2.2.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@tailwindcss/postcss": "^4.3.0",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"typescript": "^5.7.2",
|
|
48
48
|
"vite": "^7.3.3",
|
|
49
49
|
"vitest": "^3.2.4",
|
|
50
|
-
"@suda/build-config": "0.0.0",
|
|
51
50
|
"@suda/eslint-config": "0.0.0",
|
|
52
|
-
"@suda/tsconfig": "0.0.0"
|
|
51
|
+
"@suda/tsconfig": "0.0.0",
|
|
52
|
+
"@suda/build-config": "0.0.0"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsup",
|
|
@@ -31,3 +31,99 @@ suda theme check
|
|
|
31
31
|
## AI metadata
|
|
32
32
|
|
|
33
33
|
Every component and layout component should declare useful `ai.instructions`. Missing component-level instructions fail `suda theme check` and block publish.
|
|
34
|
+
|
|
35
|
+
Use `visibleIf: ({ props }, { fields }) => boolean` for synchronous field visibility that depends on sibling props or resolved fields. Keep it pure and fast; use Puck `resolveFields` directly for heavier dynamic field changes. AI and MCP schemas expose `visibleIf` fields as dynamic visibility so agents know the editor surface can change with props.
|
|
36
|
+
|
|
37
|
+
## Field authoring rules
|
|
38
|
+
|
|
39
|
+
- Prefer Suda field helpers over generic text fields: use `url` for links and routes, `image` for images, `video` for videos, `media` for mixed media, `color` for colors, `icon` for icon names, `font` for font selections, `range` for bounded numbers, `spacing` for spacing tokens, and `menu` for navigation items.
|
|
40
|
+
- Use `select` or `radio` when authors must choose from a fixed set of values. Keep option values stable and serializable.
|
|
41
|
+
- Use `array` for repeatable items and `object` for grouped settings. Keep nested field names aligned with the prop shape and `defaultProps`.
|
|
42
|
+
- For card grids, feature lists, pricing tables, logo walls, gallery grids, and any multi-column component, expose a `columns` prop with a `range` or `select` field and a sensible default.
|
|
43
|
+
- Use `textarea` for multi-sentence copy and `text` only for short labels, headings, slugs, or plain strings that are not URLs/media/color/icon/font values.
|
|
44
|
+
- Keep `fields`, `defaultProps`, and `render` in sync: every editor field should have a sensible default and render should tolerate omitted optional values.
|
|
45
|
+
- Put editor-only controls in props when they change rendering, for example `showLogo`; pair dependent controls with `visibleIf` rather than hiding logic only in JSX.
|
|
46
|
+
- Keep persisted props JSON-serializable. Do not store functions, React nodes, class instances, database ids for media, or environment-specific absolute filesystem paths.
|
|
47
|
+
- Add optional field-level `ai.instructions`, `ai.required`, or `ai.exclude` when a prop needs generation guidance beyond its label and type.
|
|
48
|
+
|
|
49
|
+
## Block slot authoring rules
|
|
50
|
+
|
|
51
|
+
Prefer `blockSlots` when a section owns nested content that benefits from independent editor selection and configuration, such as hero actions, pricing cards, feature rows, stats, or timeline items. This is usually more editor-friendly than modeling those items as plain `array` / `object` fields because each local block can be selected and configured on its own in the visual editor. Keep ordinary section props in `fields`; `fields` is optional and does not need to list every prop.
|
|
52
|
+
|
|
53
|
+
- Type the slot prop as Puck `Slot` and declare `blockSlots` with the same prop key. Non-slot props should stay in `fields` or nested `array` / `object` fields.
|
|
54
|
+
- Define local block kinds under `blockSlots.<slotName>.blocks`. Local blocks support normal Suda fields, `label`, `defaultProps`, `render`, `metadata`, `inline`, `permissions`, and optional `ai`.
|
|
55
|
+
- Do not add nested `blockSlots` or `cms` to local blocks. Block slots are one level deep in the theme authoring API.
|
|
56
|
+
- Do not put the slot prop in the host component's `defaultProps`; use `defaultBlocks` on the slot instead.
|
|
57
|
+
- Do not hand-write `id` in local block `defaultProps`, `defaultBlocks[].props`, or starter page nested block props. The editor/runtime owns block ids.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import type { Slot } from "@puckeditor/core";
|
|
63
|
+
import type { SudaComponentConfig } from "@sudajs/theme-engine";
|
|
64
|
+
|
|
65
|
+
type HeroProps = {
|
|
66
|
+
title?: string;
|
|
67
|
+
actions?: Slot;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Hero: SudaComponentConfig<HeroProps> = {
|
|
71
|
+
label: "Hero",
|
|
72
|
+
ai: {
|
|
73
|
+
instructions: "Primary page introduction with editable call-to-action blocks.",
|
|
74
|
+
},
|
|
75
|
+
fields: {
|
|
76
|
+
title: { type: "text", label: "Title" },
|
|
77
|
+
},
|
|
78
|
+
blockSlots: {
|
|
79
|
+
actions: {
|
|
80
|
+
label: "Actions",
|
|
81
|
+
blocks: {
|
|
82
|
+
button: {
|
|
83
|
+
label: "Button",
|
|
84
|
+
fields: {
|
|
85
|
+
label: { type: "text", label: "Label" },
|
|
86
|
+
href: { type: "url", label: "Link" },
|
|
87
|
+
},
|
|
88
|
+
defaultProps: {
|
|
89
|
+
label: "Get started",
|
|
90
|
+
href: "/contact",
|
|
91
|
+
},
|
|
92
|
+
render: ({ label, href }) => <a href={href}>{label}</a>,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
defaultBlocks: [{ kind: "button" }],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
defaultProps: {
|
|
99
|
+
title: "Build with SudaCloud",
|
|
100
|
+
},
|
|
101
|
+
render: ({ title, actions: Actions }) => (
|
|
102
|
+
<section>
|
|
103
|
+
<h1>{title}</h1>
|
|
104
|
+
<Actions />
|
|
105
|
+
</section>
|
|
106
|
+
),
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Starter page data should use the public host component type and a standard slot array on the slot prop. Use local block `kind` values as nested `type` values.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
{
|
|
114
|
+
type: "Hero",
|
|
115
|
+
props: {
|
|
116
|
+
id: "Hero-1",
|
|
117
|
+
title: "Welcome",
|
|
118
|
+
actions: [
|
|
119
|
+
{
|
|
120
|
+
type: "button",
|
|
121
|
+
props: {
|
|
122
|
+
label: "Contact us",
|
|
123
|
+
href: "/contact",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
```
|
|
@@ -23,6 +23,10 @@ pnpm validate
|
|
|
23
23
|
|
|
24
24
|
Theme-local assets live in `src/assets/`. The starter hero exposes a Logo field that defaults to `src/assets/suda-logo.svg`, so you can replace it with your own brand image or choose a different image in the editor.
|
|
25
25
|
|
|
26
|
+
Fields can use `visibleIf: ({ props }, { fields }) => boolean` for synchronous dynamic visibility. The starter hero shows this by hiding the Logo picker when `showLogo` is false. Suda converts `visibleIf` to Puck `resolveFields` during normalization, and the generated AI/MCP section schema marks those fields as dynamically visible.
|
|
27
|
+
|
|
28
|
+
When adding fields, prefer purpose-built Suda field types over generic text inputs: use `url` for links, `image` / `video` / `media` for media, `color` for colors, `icon` for icon names, `range` for bounded numbers, `spacing` for spacing tokens, `menu` for navigation, `select` / `radio` for fixed choices, and `array` / `object` for structured values. Multi-column sections such as card grids should expose a `columns` prop.
|
|
29
|
+
|
|
26
30
|
## Publish
|
|
27
31
|
|
|
28
32
|
```bash
|
|
@@ -6,6 +6,7 @@ import { themeAsset } from "./theme-asset.js";
|
|
|
6
6
|
type FeatureItem = { title?: string; description?: string };
|
|
7
7
|
type PuckExtras = { puck?: { metadata?: ThemeRenderMetadata } };
|
|
8
8
|
type HeroProps = {
|
|
9
|
+
showLogo?: boolean;
|
|
9
10
|
logo?: string;
|
|
10
11
|
eyebrow?: string;
|
|
11
12
|
title?: string;
|
|
@@ -16,6 +17,7 @@ type HeroProps = {
|
|
|
16
17
|
type FeatureGridProps = {
|
|
17
18
|
title?: string;
|
|
18
19
|
description?: string;
|
|
20
|
+
columns?: number;
|
|
19
21
|
features?: FeatureItem[];
|
|
20
22
|
};
|
|
21
23
|
type TestimonialProps = {
|
|
@@ -40,14 +42,27 @@ export const Hero: SudaComponentConfig<HeroProps> = {
|
|
|
40
42
|
"Use at most once per page. Do not use for ordinary section headings or article content.",
|
|
41
43
|
},
|
|
42
44
|
fields: {
|
|
43
|
-
|
|
45
|
+
showLogo: {
|
|
46
|
+
type: "radio",
|
|
47
|
+
label: "Show logo",
|
|
48
|
+
options: [
|
|
49
|
+
{ label: "Show", value: true },
|
|
50
|
+
{ label: "Hide", value: false },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
logo: {
|
|
54
|
+
type: "image",
|
|
55
|
+
label: "Logo",
|
|
56
|
+
visibleIf: ({ props }) => props.showLogo !== false,
|
|
57
|
+
},
|
|
44
58
|
eyebrow: { type: "text", label: "Eyebrow" },
|
|
45
59
|
title: { type: "text", label: "Title" },
|
|
46
60
|
description: { type: "textarea", label: "Description" },
|
|
47
61
|
primaryLabel: { type: "text", label: "Primary button label" },
|
|
48
|
-
primaryHref: { type: "
|
|
62
|
+
primaryHref: { type: "url", label: "Primary button link" },
|
|
49
63
|
},
|
|
50
64
|
defaultProps: {
|
|
65
|
+
showLogo: true,
|
|
51
66
|
logo: themeAsset("assets/suda-logo.svg"),
|
|
52
67
|
eyebrow: "New theme",
|
|
53
68
|
title: "Build with SudaCloud",
|
|
@@ -55,11 +70,20 @@ export const Hero: SudaComponentConfig<HeroProps> = {
|
|
|
55
70
|
primaryLabel: "Get started",
|
|
56
71
|
primaryHref: "#contact",
|
|
57
72
|
},
|
|
58
|
-
render: ({
|
|
73
|
+
render: ({
|
|
74
|
+
showLogo = true,
|
|
75
|
+
logo,
|
|
76
|
+
eyebrow,
|
|
77
|
+
title,
|
|
78
|
+
description,
|
|
79
|
+
primaryLabel,
|
|
80
|
+
primaryHref,
|
|
81
|
+
puck,
|
|
82
|
+
}) => {
|
|
59
83
|
const logoSrc = resolveAsset(puck?.metadata, logo);
|
|
60
84
|
return (
|
|
61
85
|
<section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
|
|
62
|
-
{logoSrc ? (
|
|
86
|
+
{showLogo && logoSrc ? (
|
|
63
87
|
<img className="__SUDA_THEME_KEY__-hero-logo" src={logoSrc} alt="" />
|
|
64
88
|
) : null}
|
|
65
89
|
<p className="__SUDA_THEME_KEY__-eyebrow">{eyebrow}</p>
|
|
@@ -84,6 +108,7 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
84
108
|
fields: {
|
|
85
109
|
title: { type: "text", label: "Title" },
|
|
86
110
|
description: { type: "textarea", label: "Description" },
|
|
111
|
+
columns: { type: "range", label: "Columns", min: 2, max: 4, step: 1 },
|
|
87
112
|
features: {
|
|
88
113
|
type: "array",
|
|
89
114
|
label: "Features",
|
|
@@ -96,17 +121,21 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
96
121
|
defaultProps: {
|
|
97
122
|
title: "Everything you need to launch",
|
|
98
123
|
description: "Use this section to explain the core value of the project.",
|
|
124
|
+
columns: 3,
|
|
99
125
|
features: [
|
|
100
126
|
{ title: "Fast setup", description: "Start from a clean theme contract." },
|
|
101
127
|
{ title: "Visual editing", description: "Expose content fields through Puck." },
|
|
102
128
|
{ title: "Publish ready", description: "Build and publish with the Suda CLI." },
|
|
103
129
|
],
|
|
104
130
|
},
|
|
105
|
-
render: ({ title, description, features = [] }) => (
|
|
131
|
+
render: ({ title, description, columns = 3, features = [] }) => (
|
|
106
132
|
<section className="__SUDA_THEME_KEY__-section">
|
|
107
133
|
<h2>{title}</h2>
|
|
108
134
|
<p>{description}</p>
|
|
109
|
-
<div
|
|
135
|
+
<div
|
|
136
|
+
className="__SUDA_THEME_KEY__-grid"
|
|
137
|
+
style={{ "--__SUDA_THEME_KEY__-columns": columns }}
|
|
138
|
+
>
|
|
110
139
|
{features.map((feature: FeatureItem, index: number) => (
|
|
111
140
|
<article className="__SUDA_THEME_KEY__-card" key={index}>
|
|
112
141
|
<h3>{feature.title}</h3>
|
|
@@ -158,7 +187,7 @@ export const CallToAction: SudaComponentConfig<CallToActionProps> = {
|
|
|
158
187
|
title: { type: "text", label: "Title" },
|
|
159
188
|
description: { type: "textarea", label: "Description" },
|
|
160
189
|
buttonLabel: { type: "text", label: "Button label" },
|
|
161
|
-
buttonHref: { type: "
|
|
190
|
+
buttonHref: { type: "url", label: "Button link" },
|
|
162
191
|
},
|
|
163
192
|
defaultProps: {
|
|
164
193
|
title: "Ready to build your next page?",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
}
|
|
81
81
|
.__SUDA_THEME_KEY__-grid {
|
|
82
82
|
display: grid;
|
|
83
|
-
grid-template-columns: repeat(
|
|
83
|
+
grid-template-columns: repeat(var(--__SUDA_THEME_KEY__-columns, 3), minmax(0, 1fr));
|
|
84
84
|
gap: 16px;
|
|
85
85
|
margin-top: 28px;
|
|
86
86
|
}
|
|
@@ -106,3 +106,8 @@
|
|
|
106
106
|
background: #ffffff;
|
|
107
107
|
color: #111827;
|
|
108
108
|
}
|
|
109
|
+
@media (max-width: 760px) {
|
|
110
|
+
.__SUDA_THEME_KEY__-grid {
|
|
111
|
+
grid-template-columns: 1fr;
|
|
112
|
+
}
|
|
113
|
+
}
|