@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.7.2",
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.3"
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
- logo: { type: "image", label: "Logo" },
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: "text", label: "Primary button link" },
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: ({ logo, eyebrow, title, description, primaryLabel, primaryHref, puck }) => {
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 className="__SUDA_THEME_KEY__-grid">
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: "text", label: "Button link" },
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(auto-fit, minmax(220px, 1fr));
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
+ }