@sudajs/cli 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudajs/cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "suda": "./bin/suda.js"
@@ -34,7 +34,7 @@
34
34
  "react": "^19.2.7",
35
35
  "react-dom": "^19.2.7",
36
36
  "zod": "^3.24.1",
37
- "@sudajs/theme-engine": "5.1.1"
37
+ "@sudajs/theme-engine": "5.1.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@tailwindcss/postcss": "^4.3.0",
@@ -71,6 +71,28 @@ one pass:
71
71
  8. Run `pnpm typecheck`, `pnpm lint`, `pnpm build`, and `pnpm validate` before
72
72
  handoff.
73
73
 
74
+ Rendering fallback rule:
75
+
76
+ - Defaults belong in `defaultProps`, starter page data, or CMS template data.
77
+ During render, read values directly from props. Do not add fallback display
78
+ values with `||`, `??`, or ternaries unless the fallback is truly required for
79
+ runtime safety.
80
+ - This is wrong because render invents content that is not in props:
81
+
82
+ ```tsx
83
+ <div>{props.title || "title"}</div>
84
+ ```
85
+
86
+ - This is correct because render only displays the authored prop value:
87
+
88
+ ```tsx
89
+ <div>{props.title}</div>
90
+ ```
91
+
92
+ - Conditional rendering is fine when absence intentionally removes optional UI,
93
+ such as hiding an optional image, button, or eyebrow. Do not use conditional
94
+ rendering to substitute placeholder copy, labels, links, or menu items.
95
+
74
96
  Configure ordinary fields like this:
75
97
 
76
98
  ```ts
@@ -143,6 +165,145 @@ Field source of truth:
143
165
  `range`, `spacing`, `media`, `image`, `video`, and `posts`. These are valid
144
166
  in `fields` and are normalized by `@sudajs/theme-engine`.
145
167
 
168
+ ## Navigation and footer menus
169
+
170
+ Navigation and footer menus must be typed arrays. Do not use the legacy menu
171
+ field type, multiline text areas, or newline-delimited strings to model menus.
172
+ Every link destination must use a `url` field type, never a plain `text` field.
173
+ The `fields`, TypeScript props, and `defaultProps` data structures must match
174
+ exactly.
175
+
176
+ Navigation supports at most two levels:
177
+
178
+ - `navItems[]`
179
+ - `navItems[].submenu[]`
180
+
181
+ Submenu items may contain only text and link fields. Do not add another nested
182
+ submenu field inside `navItems[].submenu[]`; the field shape must make third
183
+ levels impossible. An empty `submenu` array means the item is an ordinary
184
+ top-level link.
185
+
186
+ The simplest Navigation field shape is:
187
+
188
+ ```ts
189
+ type NavSubItem = {
190
+ label: string;
191
+ href: string;
192
+ };
193
+
194
+ type NavItem = {
195
+ label: string;
196
+ href: string;
197
+ submenu: NavSubItem[];
198
+ };
199
+
200
+ const navItemsField = {
201
+ type: "array",
202
+ label: t("common.fields.navigationItems"),
203
+ getItemSummary: (item: NavItem) => item.label || "Navigation item",
204
+ defaultItemProps: {
205
+ label: "Home",
206
+ href: "/",
207
+ submenu: [],
208
+ },
209
+ arrayFields: {
210
+ label: {
211
+ type: "text",
212
+ label: t("common.fields.label"),
213
+ },
214
+ href: {
215
+ type: "url",
216
+ label: t("common.fields.link"),
217
+ },
218
+ submenu: {
219
+ type: "array",
220
+ label: t("common.fields.submenu"),
221
+ getItemSummary: (item: NavSubItem) => item.label || "Submenu item",
222
+ arrayFields: {
223
+ label: {
224
+ type: "text",
225
+ label: t("common.fields.label"),
226
+ },
227
+ href: {
228
+ type: "url",
229
+ label: t("common.fields.link"),
230
+ },
231
+ },
232
+ },
233
+ },
234
+ };
235
+
236
+ const defaultNavItems: NavItem[] = [
237
+ {
238
+ label: "Home",
239
+ href: "/",
240
+ submenu: [],
241
+ },
242
+ {
243
+ label: "Company",
244
+ href: "/company",
245
+ submenu: [
246
+ {
247
+ label: "About",
248
+ href: "/about",
249
+ },
250
+ ],
251
+ },
252
+ ];
253
+ ```
254
+
255
+ Footer grouped menus use two array levels:
256
+
257
+ - `columns[]`
258
+ - `columns[].links[]`
259
+
260
+ Use this prop shape and mirror it exactly in the field config and defaults:
261
+
262
+ ```ts
263
+ type FooterColumn = {
264
+ title: string;
265
+ links: {
266
+ text: string;
267
+ url: string;
268
+ }[];
269
+ };
270
+
271
+ const footerColumnsField = {
272
+ type: "array",
273
+ label: t("common.fields.columns"),
274
+ getItemSummary: (item: FooterColumn) => item.title || "Footer column",
275
+ defaultItemProps: {
276
+ title: "Company",
277
+ links: [{ text: "About", url: "/about" }],
278
+ },
279
+ arrayFields: {
280
+ title: {
281
+ type: "text",
282
+ label: t("common.fields.title"),
283
+ },
284
+ links: {
285
+ type: "array",
286
+ label: t("common.fields.links"),
287
+ getItemSummary: (item: FooterColumn["links"][number]) => item.text || "Footer link",
288
+ defaultItemProps: {
289
+ text: "About",
290
+ url: "/about",
291
+ },
292
+ arrayFields: {
293
+ text: {
294
+ type: "text",
295
+ label: t("common.fields.label"),
296
+ },
297
+ url: {
298
+ type: "url",
299
+ label: t("common.fields.link"),
300
+ },
301
+ },
302
+ },
303
+ },
304
+ };
305
+ ```
306
+
146
307
  Use these field patterns:
147
308
 
148
309
  ```ts
@@ -236,8 +397,6 @@ defaultProps: {
236
397
  download: "",
237
398
  icon: "sparkles",
238
399
  accentColor: "#2563eb",
239
- font: "system",
240
- links: [{ label: "Home", url: "/" }],
241
400
  spacing: "md",
242
401
  postList: { strategy: "featured", limit: 3 },
243
402
  badge: { label: "New", tone: "primary" },