@sudajs/cli 0.7.1 → 0.8.0

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.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "suda": "./bin/suda.js"
@@ -25,14 +25,14 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
- "@puckeditor/core": "^0.21.2",
28
+ "@puckeditor/core": "^0.21.3",
29
29
  "commander": "^12.1.0",
30
30
  "esbuild": "^0.25.12",
31
31
  "open": "^10.1.0",
32
- "react": "^19.0.0",
33
- "react-dom": "^19.0.0",
32
+ "react": "^19.2.7",
33
+ "react-dom": "^19.2.7",
34
34
  "zod": "^3.24.1",
35
- "@sudajs/theme-engine": "2.0.2"
35
+ "@sudajs/theme-engine": "2.1.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@tailwindcss/postcss": "^4.3.0",
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "all",
5
+ "printWidth": 100,
6
+ "tabWidth": 2
7
+ }
@@ -12,13 +12,16 @@ This directory is a standalone Vite + React + Tailwind Suda theme generated by `
12
12
  ## Styling
13
13
 
14
14
  - Tailwind v4 starts in `src/styles.css` with `@import "tailwindcss";` and `@source "./**/*.{ts,tsx}";`.
15
+ - Theme-local assets live in `src/assets/`; the CLI copies them to `dist/assets/` during build.
15
16
  - Customize `vite.config.ts`, `postcss.config.mjs`, and `src/styles.css` as needed.
16
17
 
17
18
  ## Commands
18
19
 
19
20
  ```bash
20
21
  pnpm install
22
+ pnpm lint
21
23
  pnpm typecheck
24
+ pnpm format
22
25
  pnpm dev # Vite-powered local preview
23
26
  pnpm build # Vite build + Suda artifact finalize + AI metadata check
24
27
  pnpm validate
@@ -28,3 +31,17 @@ suda theme check
28
31
  ## AI metadata
29
32
 
30
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.
@@ -12,13 +12,21 @@ pnpm install
12
12
 
13
13
  ```bash
14
14
  pnpm dev
15
+ pnpm lint
15
16
  pnpm typecheck
17
+ pnpm format
16
18
  pnpm build
17
19
  pnpm validate
18
20
  ```
19
21
 
20
22
  `pnpm dev` starts the Vite-powered preview. `pnpm build` produces the Suda artifact contract in `dist/`, including `dist/index.js`, `dist/runtime.client.js`, `dist/manifest.json`, and `dist/styles.css`.
21
23
 
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
+
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
+
22
30
  ## Publish
23
31
 
24
32
  ```bash
@@ -0,0 +1,48 @@
1
+ import js from "@eslint/js";
2
+ import reactHooks from "eslint-plugin-react-hooks";
3
+ import reactRefresh from "eslint-plugin-react-refresh";
4
+ import globals from "globals";
5
+ import prettier from "eslint-config-prettier";
6
+ import tseslint from "typescript-eslint";
7
+
8
+ export default tseslint.config(
9
+ {
10
+ ignores: ["dist/**", ".suda-build/**", "node_modules/**", "*.tsbuildinfo"],
11
+ },
12
+ js.configs.recommended,
13
+ {
14
+ files: ["**/*.{js,mjs,cjs}"],
15
+ languageOptions: {
16
+ ecmaVersion: 2022,
17
+ globals: globals.node,
18
+ },
19
+ },
20
+ ...tseslint.configs.recommendedTypeChecked.map((config) => ({
21
+ ...config,
22
+ files: ["**/*.{ts,tsx}"],
23
+ })),
24
+ {
25
+ files: ["**/*.{ts,tsx}"],
26
+ languageOptions: {
27
+ ecmaVersion: 2022,
28
+ globals: globals.browser,
29
+ parserOptions: {
30
+ projectService: true,
31
+ tsconfigRootDir: import.meta.dirname,
32
+ },
33
+ },
34
+ plugins: {
35
+ "react-hooks": reactHooks,
36
+ "react-refresh": reactRefresh,
37
+ },
38
+ rules: {
39
+ ...reactHooks.configs.recommended.rules,
40
+ "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
41
+ "react-refresh/only-export-components": [
42
+ "warn",
43
+ { allowConstantExport: true },
44
+ ],
45
+ },
46
+ },
47
+ prettier,
48
+ );
@@ -10,29 +10,40 @@
10
10
  "build": "suda theme build --theme-root .",
11
11
  "dev": "suda theme dev --theme-root .",
12
12
  "finalize": "suda theme finalize --theme-root .",
13
- "typecheck": "tsc -p tsconfig.json --noEmit",
13
+ "format": "prettier --write .",
14
+ "lint": "eslint .",
15
+ "lint:fix": "eslint . --fix",
16
+ "typecheck": "tsc -p tsconfig.json",
14
17
  "validate": "suda theme validate --theme-root ."
15
18
  },
16
19
  "devDependencies": {
20
+ "@eslint/js": "^9.39.4",
17
21
  "@tailwindcss/postcss": "^4.3.0",
18
- "@puckeditor/core": "^0.21.2",
22
+ "@puckeditor/core": "^0.21.3",
19
23
  "@sudajs/cli": "*",
20
24
  "@sudajs/theme-engine": "*",
21
- "@types/node": "^22.0.0",
22
- "@types/react": "^19.0.0",
23
- "@types/react-dom": "^19.0.0",
24
- "@vitejs/plugin-react": "^5.1.2",
25
+ "@types/node": "^24.13.2",
26
+ "@types/react": "^19.2.17",
27
+ "@types/react-dom": "^19.2.3",
28
+ "@vitejs/plugin-react": "^6.0.2",
29
+ "eslint": "^9.39.4",
30
+ "eslint-config-prettier": "^9.1.2",
31
+ "eslint-plugin-react-hooks": "^7.1.1",
32
+ "eslint-plugin-react-refresh": "^0.4.26",
33
+ "globals": "^16.5.0",
25
34
  "postcss": "^8.5.15",
26
- "react": "^19.0.0",
27
- "react-dom": "^19.0.0",
35
+ "prettier": "^3.8.4",
36
+ "react": "^19.2.7",
37
+ "react-dom": "^19.2.7",
28
38
  "tailwindcss": "^4.3.0",
29
- "typescript": "^5.6.0",
30
- "vite": "^7.3.3"
39
+ "typescript": "~6.0.2",
40
+ "typescript-eslint": "^8.62.0",
41
+ "vite": "^8.1.0"
31
42
  },
32
43
  "peerDependencies": {
33
- "@puckeditor/core": "^0.21.2",
44
+ "@puckeditor/core": "^0.21.3",
34
45
  "@sudajs/theme-engine": "*",
35
- "react": "^19.0.0",
36
- "react-dom": "^19.0.0"
46
+ "react": "^19.2.7",
47
+ "react-dom": "^19.2.7"
37
48
  }
38
49
  }
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 2048 2048">
3
+ <defs>
4
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
5
+ <stop offset="0%" stop-color="#00B6F6" />
6
+ <stop offset="100%" stop-color="#0538A1" />
7
+ </linearGradient>
8
+ </defs>
9
+
10
+ <rect x="0" y="0" width="2048" height="2048" fill="url(#bg)" />
11
+
12
+ <polygon fill="#FFFFFF" points="1508,503 989,503 934,710 867,764 398,764 328,1022 847,1022 902,816 969,761 1439,761" />
13
+
14
+ <polygon fill="#FFFFFF" points="929,894 860,1153 1118,1153 1187,894" />
15
+
16
+ <polygon fill="#FFFFFF" points="1719,1025 1200,1024 1144,1233 1079,1285 608,1285 539,1544 1058,1544 1114,1335 1179,1283 1650,1283" />
17
+ </svg>
@@ -1,19 +1,19 @@
1
- import type { Config, Data } from "@puckeditor/core";
1
+ import type { LayoutData, SudaConfig } from "@sudajs/theme-engine";
2
2
 
3
3
  import { LAYOUT_COMPONENTS, rootConfig } from "./layout.js";
4
4
  import { SECTION_COMPONENTS } from "./sections.js";
5
5
 
6
- export const pageConfig: Config = {
6
+ export const pageConfig: SudaConfig = {
7
7
  components: SECTION_COMPONENTS,
8
8
  };
9
9
 
10
- export const layoutConfig: Config = {
10
+ export const layoutConfig: SudaConfig = {
11
11
  root: rootConfig,
12
12
  components: LAYOUT_COMPONENTS,
13
13
  };
14
14
 
15
- export const defaultLayout: Data = {
16
- root: { props: { siteName: "__SUDA_THEME_KEY__" } },
15
+ export const defaultLayout: LayoutData = {
16
+ root: { props: { title: "__SUDA_THEME_KEY__" } },
17
17
  content: [
18
18
  { type: "Header", props: { id: "Header-1", siteName: "__SUDA_THEME_KEY__" } },
19
19
  { type: "PageOutlet", props: { id: "PageOutlet-1" } },
@@ -1,21 +1,26 @@
1
- import type { Config } from "@puckeditor/core";
2
- import type { SudaComponentConfig } from "@sudajs/theme-engine";
1
+ import type { SudaComponentConfig, SudaRootConfig } from "@sudajs/theme-engine";
3
2
  import { getIcpRecord, getPageSlot, getWhiteLabel } from "@sudajs/theme-engine/runtime";
4
3
  import type { ReactElement, ReactNode } from "react";
5
4
 
6
5
  type PuckExtras = { puck?: { metadata?: Record<string, unknown> } };
6
+ type RootProps = { children?: ReactNode; title?: string };
7
+ type HeaderProps = { siteName?: string };
8
+ type PageOutletProps = PuckExtras;
9
+ type FooterProps = { text?: string } & PuckExtras;
7
10
 
8
- export const rootConfig: NonNullable<Config["root"]> = {
11
+ export const rootConfig: SudaRootConfig<RootProps> = {
9
12
  fields: {
10
- siteName: { type: "text", label: "Site name" },
13
+ title: { type: "text", label: "Site title" },
11
14
  },
12
- defaultProps: { siteName: "__SUDA_THEME_KEY__" },
13
- render: ({ children, siteName }: { children?: ReactNode; siteName?: string }) => (
14
- <div className="__SUDA_THEME_KEY__-root" data-site-name={siteName}>{children}</div>
15
+ defaultProps: { title: "__SUDA_THEME_KEY__" },
16
+ render: ({ children, title }) => (
17
+ <div className="__SUDA_THEME_KEY__-root" data-site-name={title}>
18
+ {children}
19
+ </div>
15
20
  ),
16
21
  };
17
22
 
18
- export const Header: SudaComponentConfig = {
23
+ export const Header: SudaComponentConfig<HeaderProps> = {
19
24
  label: "Header",
20
25
  ai: {
21
26
  instructions:
@@ -28,7 +33,7 @@ export const Header: SudaComponentConfig = {
28
33
  render: ({ siteName }) => <header className="__SUDA_THEME_KEY__-header">{siteName}</header>,
29
34
  };
30
35
 
31
- export const PageOutlet: SudaComponentConfig = {
36
+ export const PageOutlet: SudaComponentConfig<PageOutletProps> = {
32
37
  label: "Page outlet",
33
38
  ai: {
34
39
  exclude: true,
@@ -38,10 +43,10 @@ export const PageOutlet: SudaComponentConfig = {
38
43
  },
39
44
  fields: {},
40
45
  defaultProps: {},
41
- render: (props: PuckExtras): ReactElement => <>{getPageSlot(props.puck?.metadata)}</>,
46
+ render: (props): ReactElement => <>{getPageSlot(props.puck?.metadata)}</>,
42
47
  };
43
48
 
44
- export const Footer: SudaComponentConfig = {
49
+ export const Footer: SudaComponentConfig<FooterProps> = {
45
50
  label: "Footer",
46
51
  ai: {
47
52
  instructions:
@@ -51,7 +56,7 @@ export const Footer: SudaComponentConfig = {
51
56
  },
52
57
  fields: { text: { type: "text", label: "Text" } },
53
58
  defaultProps: { text: "© __SUDA_THEME_KEY__" },
54
- render: ({ text, puck }: { text?: string } & PuckExtras) => {
59
+ render: ({ text, puck }) => {
55
60
  const whiteLabel = getWhiteLabel(puck?.metadata);
56
61
  const icp = getIcpRecord(puck?.metadata);
57
62
  return (
@@ -1,16 +1,23 @@
1
1
  import type { SudaComponentConfig } from "@sudajs/theme-engine";
2
+ import { resolveAsset, type ThemeRenderMetadata } from "@sudajs/theme-engine/runtime";
3
+
4
+ import { themeAsset } from "./theme-asset.js";
2
5
 
3
6
  type FeatureItem = { title?: string; description?: string };
7
+ type PuckExtras = { puck?: { metadata?: ThemeRenderMetadata } };
4
8
  type HeroProps = {
9
+ showLogo?: boolean;
10
+ logo?: string;
5
11
  eyebrow?: string;
6
12
  title?: string;
7
13
  description?: string;
8
14
  primaryLabel?: string;
9
15
  primaryHref?: string;
10
- };
16
+ } & PuckExtras;
11
17
  type FeatureGridProps = {
12
18
  title?: string;
13
19
  description?: string;
20
+ columns?: number;
14
21
  features?: FeatureItem[];
15
22
  };
16
23
  type TestimonialProps = {
@@ -25,7 +32,7 @@ type CallToActionProps = {
25
32
  buttonHref?: string;
26
33
  };
27
34
 
28
- export const Hero: SudaComponentConfig = {
35
+ export const Hero: SudaComponentConfig<HeroProps> = {
29
36
  label: "Hero",
30
37
  ai: {
31
38
  instructions:
@@ -35,30 +42,62 @@ export const Hero: SudaComponentConfig = {
35
42
  "Use at most once per page. Do not use for ordinary section headings or article content.",
36
43
  },
37
44
  fields: {
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
+ },
38
58
  eyebrow: { type: "text", label: "Eyebrow" },
39
59
  title: { type: "text", label: "Title" },
40
60
  description: { type: "textarea", label: "Description" },
41
61
  primaryLabel: { type: "text", label: "Primary button label" },
42
- primaryHref: { type: "text", label: "Primary button link" },
62
+ primaryHref: { type: "url", label: "Primary button link" },
43
63
  },
44
64
  defaultProps: {
65
+ showLogo: true,
66
+ logo: themeAsset("assets/suda-logo.svg"),
45
67
  eyebrow: "New theme",
46
68
  title: "Build with SudaCloud",
47
69
  description: "Edit this starter section in the visual editor.",
48
70
  primaryLabel: "Get started",
49
71
  primaryHref: "#contact",
50
72
  },
51
- render: ({ eyebrow, title, description, primaryLabel, primaryHref }: HeroProps) => (
52
- <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
53
- <p className="__SUDA_THEME_KEY__-eyebrow">{eyebrow}</p>
54
- <h1>{title}</h1>
55
- <p>{description}</p>
56
- <a className="__SUDA_THEME_KEY__-button" href={primaryHref}>{primaryLabel}</a>
57
- </section>
58
- ),
73
+ render: ({
74
+ showLogo = true,
75
+ logo,
76
+ eyebrow,
77
+ title,
78
+ description,
79
+ primaryLabel,
80
+ primaryHref,
81
+ puck,
82
+ }) => {
83
+ const logoSrc = resolveAsset(puck?.metadata, logo);
84
+ return (
85
+ <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
86
+ {showLogo && logoSrc ? (
87
+ <img className="__SUDA_THEME_KEY__-hero-logo" src={logoSrc} alt="" />
88
+ ) : null}
89
+ <p className="__SUDA_THEME_KEY__-eyebrow">{eyebrow}</p>
90
+ <h1>{title}</h1>
91
+ <p>{description}</p>
92
+ <a className="__SUDA_THEME_KEY__-button" href={primaryHref}>
93
+ {primaryLabel}
94
+ </a>
95
+ </section>
96
+ );
97
+ },
59
98
  };
60
99
 
61
- export const FeatureGrid: SudaComponentConfig = {
100
+ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
62
101
  label: "Feature grid",
63
102
  ai: {
64
103
  instructions:
@@ -69,6 +108,7 @@ export const FeatureGrid: SudaComponentConfig = {
69
108
  fields: {
70
109
  title: { type: "text", label: "Title" },
71
110
  description: { type: "textarea", label: "Description" },
111
+ columns: { type: "range", label: "Columns", min: 2, max: 4, step: 1 },
72
112
  features: {
73
113
  type: "array",
74
114
  label: "Features",
@@ -81,17 +121,21 @@ export const FeatureGrid: SudaComponentConfig = {
81
121
  defaultProps: {
82
122
  title: "Everything you need to launch",
83
123
  description: "Use this section to explain the core value of the project.",
124
+ columns: 3,
84
125
  features: [
85
126
  { title: "Fast setup", description: "Start from a clean theme contract." },
86
127
  { title: "Visual editing", description: "Expose content fields through Puck." },
87
128
  { title: "Publish ready", description: "Build and publish with the Suda CLI." },
88
129
  ],
89
130
  },
90
- render: ({ title, description, features = [] }: FeatureGridProps) => (
131
+ render: ({ title, description, columns = 3, features = [] }) => (
91
132
  <section className="__SUDA_THEME_KEY__-section">
92
133
  <h2>{title}</h2>
93
134
  <p>{description}</p>
94
- <div className="__SUDA_THEME_KEY__-grid">
135
+ <div
136
+ className="__SUDA_THEME_KEY__-grid"
137
+ style={{ "--__SUDA_THEME_KEY__-columns": columns }}
138
+ >
95
139
  {features.map((feature: FeatureItem, index: number) => (
96
140
  <article className="__SUDA_THEME_KEY__-card" key={index}>
97
141
  <h3>{feature.title}</h3>
@@ -103,7 +147,7 @@ export const FeatureGrid: SudaComponentConfig = {
103
147
  ),
104
148
  };
105
149
 
106
- export const Testimonial: SudaComponentConfig = {
150
+ export const Testimonial: SudaComponentConfig<TestimonialProps> = {
107
151
  label: "Testimonial",
108
152
  ai: {
109
153
  instructions:
@@ -121,15 +165,17 @@ export const Testimonial: SudaComponentConfig = {
121
165
  author: "Alex Chen",
122
166
  role: "Founder",
123
167
  },
124
- render: ({ quote, author, role }: TestimonialProps) => (
168
+ render: ({ quote, author, role }) => (
125
169
  <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-quote">
126
170
  <blockquote>{quote}</blockquote>
127
- <p>{author} · {role}</p>
171
+ <p>
172
+ {author} · {role}
173
+ </p>
128
174
  </section>
129
175
  ),
130
176
  };
131
177
 
132
- export const CallToAction: SudaComponentConfig = {
178
+ export const CallToAction: SudaComponentConfig<CallToActionProps> = {
133
179
  label: "Call to action",
134
180
  ai: {
135
181
  instructions:
@@ -141,7 +187,7 @@ export const CallToAction: SudaComponentConfig = {
141
187
  title: { type: "text", label: "Title" },
142
188
  description: { type: "textarea", label: "Description" },
143
189
  buttonLabel: { type: "text", label: "Button label" },
144
- buttonHref: { type: "text", label: "Button link" },
190
+ buttonHref: { type: "url", label: "Button link" },
145
191
  },
146
192
  defaultProps: {
147
193
  title: "Ready to build your next page?",
@@ -149,11 +195,13 @@ export const CallToAction: SudaComponentConfig = {
149
195
  buttonLabel: "Contact us",
150
196
  buttonHref: "#contact",
151
197
  },
152
- render: ({ title, description, buttonLabel, buttonHref }: CallToActionProps) => (
198
+ render: ({ title, description, buttonLabel, buttonHref }) => (
153
199
  <section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-cta" id="contact">
154
200
  <h2>{title}</h2>
155
201
  <p>{description}</p>
156
- <a className="__SUDA_THEME_KEY__-button" href={buttonHref}>{buttonLabel}</a>
202
+ <a className="__SUDA_THEME_KEY__-button" href={buttonHref}>
203
+ {buttonLabel}
204
+ </a>
157
205
  </section>
158
206
  ),
159
207
  };
@@ -37,6 +37,12 @@
37
37
  .__SUDA_THEME_KEY__-hero {
38
38
  background: #f8fafc;
39
39
  }
40
+ .__SUDA_THEME_KEY__-hero-logo {
41
+ width: 64px;
42
+ height: 64px;
43
+ border-radius: 14px;
44
+ margin-bottom: 24px;
45
+ }
40
46
  .__SUDA_THEME_KEY__-eyebrow {
41
47
  text-transform: uppercase;
42
48
  letter-spacing: 0.08em;
@@ -74,7 +80,7 @@
74
80
  }
75
81
  .__SUDA_THEME_KEY__-grid {
76
82
  display: grid;
77
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
83
+ grid-template-columns: repeat(var(--__SUDA_THEME_KEY__-columns, 3), minmax(0, 1fr));
78
84
  gap: 16px;
79
85
  margin-top: 28px;
80
86
  }
@@ -100,3 +106,8 @@
100
106
  background: #ffffff;
101
107
  color: #111827;
102
108
  }
109
+ @media (max-width: 760px) {
110
+ .__SUDA_THEME_KEY__-grid {
111
+ grid-template-columns: 1fr;
112
+ }
113
+ }
@@ -1,5 +1,7 @@
1
1
  import type { ThemeStarterPage } from "@sudajs/theme-engine";
2
2
 
3
+ import { themeAsset } from "./theme-asset.js";
4
+
3
5
  export const starterPages: ThemeStarterPage[] = [
4
6
  {
5
7
  slug: "home",
@@ -12,6 +14,7 @@ export const starterPages: ThemeStarterPage[] = [
12
14
  type: "Hero",
13
15
  props: {
14
16
  id: "Hero-1",
17
+ logo: themeAsset("assets/suda-logo.svg"),
15
18
  eyebrow: "Starter page",
16
19
  title: "Welcome to __SUDA_THEME_KEY__",
17
20
  description: "This page was generated by suda theme init.",
@@ -0,0 +1,3 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare const __SUDA_THEME_VERSION__: string;
@@ -1,22 +1,27 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
6
  "module": "NodeNext",
6
7
  "moduleResolution": "NodeNext",
8
+ "types": ["node", "vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
7
15
  "jsx": "react-jsx",
8
- "rootDir": "src",
9
- "outDir": "dist",
10
- "declaration": true,
11
- "declarationMap": true,
12
- "sourceMap": true,
16
+ "resolveJsonModule": true,
17
+
13
18
  "strict": true,
14
19
  "noUncheckedIndexedAccess": true,
15
- "esModuleInterop": true,
16
- "skipLibCheck": true,
17
- "forceConsistentCasingInFileNames": true,
18
- "resolveJsonModule": true,
19
- "types": ["node"]
20
+ "exactOptionalPropertyTypes": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true
20
25
  },
21
- "include": ["src", "package.json"]
26
+ "include": ["src", "vite.config.ts", "postcss.config.mjs", "eslint.config.js"]
22
27
  }
@@ -2,9 +2,27 @@ import react from "@vitejs/plugin-react";
2
2
  import { defineConfig } from "vite";
3
3
  import pkg from "./package.json" with { type: "json" };
4
4
 
5
+ function assetFileNames(assetInfo: { names?: string[] | undefined }): string {
6
+ const names = assetInfo.names ?? [];
7
+ const isCss = names.some((name) => name.endsWith(".css"));
8
+ if (isCss) {
9
+ return "styles.css";
10
+ }
11
+ return "assets/[name]-[hash][extname]";
12
+ }
13
+
5
14
  export default defineConfig({
6
15
  define: {
7
16
  __SUDA_THEME_VERSION__: JSON.stringify(pkg.version),
8
17
  },
9
18
  plugins: [react()],
19
+ build: {
20
+ assetsInlineLimit: 0,
21
+ cssCodeSplit: false,
22
+ rollupOptions: {
23
+ output: {
24
+ assetFileNames,
25
+ },
26
+ },
27
+ },
10
28
  });