create-nextblock 0.2.37 → 0.2.39

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.
@@ -17,7 +17,6 @@ const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = dirname(__filename);
18
18
  const TEMPLATE_DIR = resolve(__dirname, '../templates/nextblock-template');
19
19
  const REPO_ROOT = resolve(__dirname, '../../..');
20
- const require = createRequire(import.meta.url);
21
20
  const EDITOR_UTILS_SOURCE_DIR = resolve(REPO_ROOT, 'libs/editor/src/lib/utils');
22
21
  const IS_WINDOWS = process.platform === 'win32';
23
22
 
@@ -199,7 +198,7 @@ async function runSetupWizard(projectDir, projectName) {
199
198
  if (process.stdin.isTTY) {
200
199
  try {
201
200
  process.stdin.setRawMode(false);
202
- } catch (_) {
201
+ } catch {
203
202
  // ignore
204
203
  }
205
204
  process.stdin.setEncoding('utf8');
@@ -235,11 +234,12 @@ async function runSetupWizard(projectDir, projectName) {
235
234
  clack.note('Please go to your Supabase project dashboard to get the following secrets.');
236
235
  const supabaseKeys = await clack.group(
237
236
  {
238
- dbPassword: () =>
239
- clack.password({
237
+ postgresUrl: () =>
238
+ clack.text({
240
239
  message:
241
- 'What is your Database Password? (Supabase: Database > Database Settings > Reset database password | Vercel: POSTGRES_PASSWORD)',
242
- validate: (val) => (!val ? 'Password is required' : undefined),
240
+ 'What is your Connection String? (Supabase: Project Dashboard > Connect (Top Left) > Connection String > URI | Vercel: POSTGRES_URL)',
241
+ placeholder: 'postgresql://...',
242
+ validate: (val) => (!val ? 'Connection string is required' : undefined),
243
243
  }),
244
244
  anonKey: () =>
245
245
  clack.password({
@@ -261,12 +261,25 @@ async function runSetupWizard(projectDir, projectName) {
261
261
  const revalidationToken = crypto.randomBytes(32).toString('hex');
262
262
  const supabaseUrl = `https://${projectId}.supabase.co`;
263
263
 
264
- const dbHost = `db.${projectId}.supabase.co`;
265
- const dbUser = 'postgres';
266
- const dbPassword = supabaseKeys.dbPassword;
267
- const dbName = 'postgres';
264
+ const postgresUrl = supabaseKeys.postgresUrl;
265
+ let dbPassword = '';
266
+ try {
267
+ const parsedUrl = new URL(postgresUrl);
268
+ dbPassword = parsedUrl.password;
269
+ } catch {
270
+ // Fallback if URL parsing fails, though validation above checks for non-empty
271
+ }
268
272
 
269
- const postgresUrl = `postgresql://${dbUser}:${dbPassword}@${dbHost}:5432/${dbName}`;
273
+ if (!dbPassword) {
274
+ const passwordPrompt = await clack.password({
275
+ message: 'Could not extract password from URL. What is your Database Password?',
276
+ validate: (val) => (!val ? 'Password is required' : undefined),
277
+ });
278
+ if (clack.isCancel(passwordPrompt)) {
279
+ handleWizardCancel('Setup cancelled.');
280
+ }
281
+ dbPassword = passwordPrompt;
282
+ }
270
283
 
271
284
  const envPath = resolve(projectPath, '.env');
272
285
  const appendEnvBlock = async (label, lines) => {
@@ -313,8 +326,9 @@ async function runSetupWizard(projectDir, projectName) {
313
326
  }
314
327
 
315
328
  clack.note('Setting up your database...');
329
+
316
330
  const dbPushSpinner = clack.spinner();
317
- dbPushSpinner.start('Pushing database schema... (~10-15 minutes, please keep this terminal open)');
331
+ dbPushSpinner.start('Pushing database schema...');
318
332
  try {
319
333
  process.env.POSTGRES_URL = postgresUrl;
320
334
  const migrationsDir = resolve(projectPath, 'supabase', 'migrations');
@@ -331,7 +345,31 @@ async function runSetupWizard(projectDir, projectName) {
331
345
  `No migrations found in ${migrationsDir}; skipping db push. Ensure @nextblock-cms/db includes supabase/migrations.`,
332
346
  );
333
347
  } else {
334
- await execa('npx', ['supabase', 'db', 'push'], { stdio: 'inherit', cwd: projectPath });
348
+ const supabaseBin = await getSupabaseBinary(projectPath);
349
+ const command = supabaseBin === 'npx' ? 'npx' : supabaseBin;
350
+
351
+ // 1. Link the project explicitly (matches monorepo behavior)
352
+ const linkArgs = supabaseBin === 'npx' ? ['supabase', 'link'] : ['link'];
353
+ linkArgs.push('--project-ref', projectId);
354
+ linkArgs.push('--password', dbPassword);
355
+
356
+ await execa(command, linkArgs, {
357
+ stdio: 'inherit',
358
+ cwd: projectPath,
359
+ });
360
+
361
+ // 2. Push the schema using the linked state
362
+ const pushArgs = supabaseBin === 'npx' ? ['supabase', 'db', 'push'] : ['db', 'push'];
363
+ pushArgs.push('--include-all');
364
+
365
+ await execa(command, pushArgs, {
366
+ stdio: 'inherit',
367
+ cwd: projectPath,
368
+ env: {
369
+ ...process.env,
370
+ SUPABASE_DB_PASSWORD: dbPassword,
371
+ },
372
+ });
335
373
  dbPushSpinner.stop('Database schema pushed successfully!');
336
374
  }
337
375
  } catch (error) {
@@ -1350,8 +1388,12 @@ function runCommand(command, args, options = {}) {
1350
1388
 
1351
1389
  async function runSupabaseCli(args, options = {}) {
1352
1390
  const { cwd } = options;
1391
+ const supabaseBin = await getSupabaseBinary(cwd);
1392
+ const command = supabaseBin === 'npx' ? 'npx' : supabaseBin;
1393
+ const cmdArgs = supabaseBin === 'npx' ? ['supabase', ...args] : args;
1394
+
1353
1395
  return new Promise((resolve, reject) => {
1354
- const child = spawn('npx', ['supabase', ...args], {
1396
+ const child = spawn(command, cmdArgs, {
1355
1397
  cwd,
1356
1398
  shell: IS_WINDOWS,
1357
1399
  stdio: 'inherit',
@@ -1371,6 +1413,16 @@ async function runSupabaseCli(args, options = {}) {
1371
1413
  });
1372
1414
  }
1373
1415
 
1416
+ async function getSupabaseBinary(projectDir) {
1417
+ const binDir = resolve(projectDir, 'node_modules', '.bin');
1418
+ const ext = IS_WINDOWS ? '.cmd' : '';
1419
+ const binaryPath = resolve(binDir, `supabase${ext}`);
1420
+ if (await fs.pathExists(binaryPath)) {
1421
+ return binaryPath;
1422
+ }
1423
+ return 'npx';
1424
+ }
1425
+
1374
1426
  function buildNextConfigContent(editorUtilNames) {
1375
1427
  const aliasLines = [];
1376
1428
 
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "create-nextblock",
3
- "version": "0.2.37",
4
- "description": "",
5
- "main": "index.js",
6
- "bin": {
7
- "create-nextblock": "./bin/create-nextblock.js"
8
- },
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1",
11
- "sync-template": "node ./scripts/sync-template.js",
12
- "prepack": "npm run sync-template"
13
- },
14
- "keywords": [],
15
- "author": "",
16
- "license": "ISC",
17
- "type": "module",
18
- "dependencies": {
19
- "@clack/prompts": "^0.8.1",
20
- "@nextblock-cms/db": "latest",
21
- "chalk": "^5.6.2",
22
- "commander": "^14.0.1",
23
- "execa": "^9.3.0",
24
- "fs-extra": "^11.3.2",
25
- "inquirer": "^12.10.0",
26
- "open": "^10.1.0",
27
- "ora": "^8.0.1",
28
- "picocolors": "^1.1.1"
29
- }
30
- }
1
+ {
2
+ "name": "create-nextblock",
3
+ "version": "0.2.39",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "create-nextblock": "./bin/create-nextblock.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "sync-template": "node ./scripts/sync-template.js",
12
+ "prepack": "npm run sync-template"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "type": "module",
18
+ "dependencies": {
19
+ "@clack/prompts": "^0.8.1",
20
+ "@nextblock-cms/db": "latest",
21
+ "chalk": "^5.6.2",
22
+ "commander": "^14.0.1",
23
+ "execa": "^9.3.0",
24
+ "fs-extra": "^11.3.2",
25
+ "inquirer": "^12.10.0",
26
+ "open": "^10.1.0",
27
+ "ora": "^8.0.1",
28
+ "picocolors": "^1.1.1"
29
+ }
30
+ }
@@ -104,7 +104,7 @@ export default function BlockEditorArea({ parentId, parentType, initialBlocks, l
104
104
  lastSavedBlocks.current = blocks;
105
105
  } else {
106
106
  // On failure, revert the UI to the last known good state
107
- alert("Failed to save changes. Reverting.");
107
+ alert(`Failed to save changes: ${result.error}. Reverting.`);
108
108
  setBlocks(lastSavedBlocks.current);
109
109
  }
110
110
  }, [parentId, parentType, blocks]);
@@ -1,4 +1,3 @@
1
- // app/cms/blocks/components/SectionConfigPanel.tsx
2
1
  "use client";
3
2
 
4
3
  import React from 'react';
@@ -112,6 +111,25 @@ export default function SectionConfigPanel({ content, onChange }: SectionConfigP
112
111
  </SelectContent>
113
112
  </Select>
114
113
  </div>
114
+
115
+ {/* Vertical Alignment */}
116
+ <div className="space-y-2">
117
+ <Label htmlFor="vertical-alignment">Vertical Alignment</Label>
118
+ <Select
119
+ value={content.vertical_alignment || 'start'}
120
+ onValueChange={(value: any) => onChange({ ...content, vertical_alignment: value })}
121
+ >
122
+ <SelectTrigger id="vertical-alignment">
123
+ <SelectValue placeholder="Select alignment" />
124
+ </SelectTrigger>
125
+ <SelectContent>
126
+ <SelectItem value="start">Top</SelectItem>
127
+ <SelectItem value="center">Center</SelectItem>
128
+ <SelectItem value="end">Bottom</SelectItem>
129
+ <SelectItem value="stretch">Stretch</SelectItem>
130
+ </SelectContent>
131
+ </Select>
132
+ </div>
115
133
  </div>
116
134
 
117
135
  {/* Background Configuration */}
@@ -52,6 +52,7 @@ export default function SectionBlockEditor({
52
52
  responsive_columns: { mobile: 1, tablet: 2, desktop: 3 },
53
53
  column_gap: "md",
54
54
  padding: { top: "md", bottom: "md" },
55
+ vertical_alignment: "start",
55
56
  column_blocks: [],
56
57
  };
57
58
 
@@ -62,6 +63,7 @@ export default function SectionBlockEditor({
62
63
  content.responsive_columns ?? defaults.responsive_columns,
63
64
  column_gap: content.column_gap ?? defaults.column_gap,
64
65
  padding: content.padding ?? defaults.padding,
66
+ vertical_alignment: content.vertical_alignment ?? defaults.vertical_alignment,
65
67
  column_blocks: content.column_blocks ?? defaults.column_blocks,
66
68
  };
67
69
  }, [content]);
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * This component is never rendered but ensures that specific Tailwind classes
5
+ * used in the database seeds (SQL files) are generated in the CSS bundle.
6
+ *
7
+ * We are using this as a fallback because Tailwind's scanning of SQL files
8
+ * in the libs directory is proving unreliable on Windows.
9
+ */
10
+ export default function ForceStyles() {
11
+ return (
12
+ <div className="hidden">
13
+ {/* Spacing & Layout */}
14
+ <div className="mt-10 p-8 p-10 p-12 gap-4"></div>
15
+ <div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8"></div>
16
+
17
+ {/* Text colors used in blocks */}
18
+ <div className="text-slate-200 text-slate-300 text-slate-400 text-slate-600 text-slate-900"></div>
19
+ <div className="dark:text-slate-200 dark:text-slate-400 dark:text-white"></div>
20
+ <div className="text-sm font-semibold text-center text-white"></div>
21
+
22
+ {/* Backgrounds and Borders */}
23
+ <div className="bg-white/5 bg-white/10 border-white/10 border-white/20"></div>
24
+ <div className="bg-slate-50 hover:bg-slate-100 dark:bg-white/5 dark:hover:bg-white/10"></div>
25
+
26
+ {/* Gradients */}
27
+ <div className="bg-gradient-to-r bg-gradient-to-br from-blue-400 to-cyan-400"></div>
28
+ <div className="from-blue-500/10 to-purple-500/10"></div>
29
+ </div>
30
+ );
31
+ }
@@ -1,436 +1,14 @@
1
- @config "../../../../tailwind.config.js";
2
- @import "tailwindcss/preflight";
3
- @import "tailwindcss/theme";
4
- @import "tailwindcss/utilities";
5
- /*─────────────────────────────────────────────────────────────────────────────
6
- globals.css — complete rewrite
7
- ─────────────────────────────────────────────────────────────────────────────*/
8
- /*─────────────────────────────────────────────────────────────────────────────
9
- 1. Critical CSS Resets (formerly in <style id="critical-css">)
10
- ─────────────────────────────────────────────────────────────────────────────*/
11
- @layer base {
12
- *, ::before, ::after {
13
- box-sizing: border-box;
14
- border-width: 0;
15
- border-style: solid;
16
- border-color: hsl(var(--border));
17
- }
18
- ::before, ::after {
19
- --tw-content: "";
20
- }
21
-
22
- html, :host {
23
- line-height: 1.5;
24
- -webkit-text-size-adjust: 100%;
25
- -moz-tab-size: 4;
26
- tab-size: 4;
27
- font-family:
28
- ui-sans-serif,
29
- system-ui,
30
- sans-serif,
31
- "Apple Color Emoji",
32
- "Segoe UI Emoji",
33
- "Segoe UI Symbol",
34
- "Noto Color Emoji";
35
- font-feature-settings: normal;
36
- font-variation-settings: normal;
37
- -webkit-tap-highlight-color: transparent;
38
- }
39
-
40
- body {
41
- margin: 0;
42
- line-height: inherit;
43
- }
44
-
45
- hr {
46
- height: 0;
47
- color: inherit;
48
- border-top-width: 1px;
49
- }
50
-
51
- abbr:where([title]) {
52
- -webkit-text-decoration: underline dotted;
53
- text-decoration: underline dotted;
54
- }
55
-
56
- h1, h2, h3, h4, h5, h6 {
57
- font-size: inherit;
58
- font-weight: inherit;
59
- margin: 0;
60
- }
61
-
62
- a {
63
- color: inherit;
64
- text-decoration: inherit;
65
- }
66
-
67
- b, strong {
68
- font-weight: bolder;
69
- }
70
-
71
- code, kbd, pre, samp {
72
- font-family:
73
- ui-monospace,
74
- SFMono-Regular,
75
- Menlo,
76
- Monaco,
77
- Consolas,
78
- "Liberation Mono",
79
- "Courier New",
80
- monospace;
81
- font-feature-settings: normal;
82
- font-variation-settings: normal;
83
- font-size: 1em;
84
- }
85
-
86
- small {
87
- font-size: 80%;
88
- }
89
-
90
- sub, sup {
91
- font-size: 75%;
92
- line-height: 0;
93
- position: relative;
94
- vertical-align: baseline;
95
- }
96
- sub { bottom: -0.25em; }
97
- sup { top: -0.5em; }
98
-
99
- table {
100
- text-indent: 0;
101
- border-color: inherit;
102
- border-collapse: collapse;
103
- }
104
-
105
- button,
106
- input,
107
- optgroup,
108
- select,
109
- textarea {
110
- font-family: inherit;
111
- font-feature-settings: inherit;
112
- font-variation-settings: inherit;
113
- font-size: 100%;
114
- font-weight: inherit;
115
- line-height: inherit;
116
- letter-spacing: inherit;
117
- color: inherit;
118
- margin: 0;
119
- padding: 0;
120
- }
121
- button, select {
122
- text-transform: none;
123
- }
124
- button,
125
- input[type="button"],
126
- input[type="reset"],
127
- input[type="submit"] {
128
- -webkit-appearance: button;
129
- background-color: transparent;
130
- background-image: none;
131
- }
132
-
133
- :-moz-focusring { outline: auto; }
134
- :-moz-ui-invalid { box-shadow: none; }
135
- progress { vertical-align: baseline; }
136
- ::-webkit-inner-spin-button,
137
- ::-webkit-outer-spin-button {
138
- height: auto;
139
- }
140
- [type="search"] {
141
- -webkit-appearance: textfield;
142
- outline-offset: -2px;
143
- }
144
- ::-webkit-search-decoration {
145
- -webkit-appearance: none;
146
- }
147
- ::-webkit-file-upload-button {
148
- -webkit-appearance: button;
149
- font: inherit;
150
- }
151
-
152
- summary { display: list-item; }
153
- blockquote, dd, dl, figure, p, pre { margin: 0; }
154
- fieldset { margin: 0; padding: 0; }
155
- legend { padding: 0; }
156
- menu, ol, ul { list-style: none; margin: 0; padding: 0; }
157
- dialog { padding: 0; }
158
- textarea { resize: vertical; }
159
-
160
- input::-moz-placeholder,
161
- textarea::-moz-placeholder {
162
- opacity: 1;
163
- color: hsl(var(--muted-foreground));
164
- }
165
- input::placeholder,
166
- textarea::placeholder {
167
- opacity: 1;
168
- color: hsl(var(--muted-foreground));
169
- }
170
-
171
- [role="button"], button { cursor: pointer; }
172
- :disabled { cursor: default; }
173
-
174
- audio,
175
- canvas,
176
- embed,
177
- iframe,
178
- img,
179
- object,
180
- svg,
181
- video {
182
- display: block;
183
- vertical-align: middle;
184
- }
185
- img, video {
186
- max-width: 100%;
187
- height: auto;
188
- }
189
- [hidden] { display: none; }
190
-
191
- /*───────────────────────────────────────────────────────────────────────────
192
- 2. Project-specific base styles (Tailwind-powered)
193
- ───────────────────────────────────────────────────────────────────────────*/
194
- /* Apply your border color everywhere */
195
- * {
196
- border-color: hsl(var(--border));
197
- }
198
-
199
- /* Body background & text */
200
- body {
201
- background-color: hsl(var(--background));
202
- color: hsl(var(--foreground));
203
- }
204
-
205
- /* Semantic headings */
206
- ul,
207
- ol {
208
- padding-left: 1.5rem;
209
- margin-bottom: 1rem;
210
- }
211
- li {
212
- margin-bottom: 0.25rem;
213
- }
214
- blockquote {
215
- padding: 1rem;
216
- font-style: italic;
217
- border-left-width: 4px;
218
- border-left-style: solid;
219
- border-left-color: hsl(var(--border));
220
- background-color: hsl(var(--muted));
221
- color: hsl(var(--muted-foreground));
222
- margin-bottom: 1rem;
223
- }
224
- code {
225
- background-color: hsl(var(--muted));
226
- color: hsl(var(--muted-foreground));
227
- padding: 0.25rem 0.25rem;
228
- padding-top: 0.125rem;
229
- padding-bottom: 0.125rem;
230
- border-radius: 0.125rem;
231
- font-family:
232
- ui-monospace,
233
- SFMono-Regular,
234
- Menlo,
235
- Monaco,
236
- Consolas,
237
- 'Liberation Mono',
238
- 'Courier New',
239
- monospace;
240
- font-size: 0.875rem;
241
- line-height: 1.25rem;
242
- }
243
- pre {
244
- background-color: hsl(var(--muted));
245
- padding: 1rem;
246
- border-radius: 0.375rem;
247
- overflow-x: auto;
248
- margin-bottom: 1rem;
249
- }
250
-
251
- table {
252
- width: 100%;
253
- border-collapse: collapse;
254
- margin-bottom: 1rem;
255
- }
256
- thead {
257
- background-color: hsl(var(--muted));
258
- }
259
- th,
260
- td {
261
- border: 1px solid hsl(var(--border));
262
- padding: 0.5rem;
263
- text-align: left;
264
- }
265
- th {
266
- font-weight: 600;
267
- }
268
- hr {
269
- border-top: 1px solid hsl(var(--border));
270
- margin: 2rem 0;
271
- }
272
- }
273
-
274
- /* Image alignment preserved from editor output */
275
- @layer components {
276
- img[data-align='left'] {
277
- display: block;
278
- margin-left: 0;
279
- margin-right: auto;
280
- }
281
- img[data-align='right'] {
282
- display: block;
283
- margin-left: auto;
284
- margin-right: 0;
285
- }
286
- img[data-align='center'] {
287
- display: block;
288
- margin-left: auto;
289
- margin-right: auto;
290
- }
291
- }
1
+ @config "../../../../tailwind.config.js";
2
+ @import 'tailwindcss/preflight';
3
+ @import 'tailwindcss/theme';
4
+ @import 'tailwindcss/utilities';
292
5
 
293
6
  /*─────────────────────────────────────────────────────────────────────────────
294
- 3. Theme color variables
7
+ globals.css modularized
295
8
  ─────────────────────────────────────────────────────────────────────────────*/
296
- :root {
297
- --background: 220 20% 98%;
298
- --foreground: 215 30% 15%;
299
- --card: 0 0% 100%;
300
- --card-foreground: 215 30% 15%;
301
- --popover: 0 0% 100%;
302
- --popover-foreground: 215 30% 15%;
303
9
 
304
- --primary: 215 45% 30%;
305
- --primary-foreground: 0 0% 100%;
306
-
307
- --secondary: 215 45% 45%;
308
- --secondary-foreground: 0 0% 100%;
309
-
310
- --muted: 220 30% 94%;
311
- --muted-foreground: 215 25% 45%;
312
-
313
- --accent: 190 70% 50%;
314
- --accent-foreground: 210 40% 98%;
315
-
316
- --destructive: 0 70% 42%;
317
- --destructive-foreground: 0 0% 98%;
318
-
319
- --warning: 38 92% 50%;
320
- --warning-foreground: 48 96% 98%;
321
-
322
- --border: 220 20% 88%;
323
- --input: 220 20% 88%;
324
- --ring: 200 80% 60%;
325
-
326
- --radius: 0.5rem;
327
-
328
- --chart-1: 217 70% 50%;
329
- --chart-2: 190 60% 45%;
330
- --chart-3: 215 35% 40%;
331
- --chart-4: 220 15% 70%;
332
- --chart-5: 200 50% 65%;
333
- }
334
-
335
- .dark {
336
- --background: 220 25% 10%;
337
- --foreground: 220 15% 90%;
338
- --card: 220 20% 14%;
339
- --card-foreground: 220 15% 90%;
340
- --popover: 220 20% 14%;
341
- --popover-foreground: 220 15% 90%;
342
-
343
- --primary: 210 90% 65%;
344
- --primary-foreground: 220 20% 10%;
345
-
346
- --secondary: 215 50% 45%;
347
- --secondary-foreground: 220 15% 90%;
348
-
349
- --muted: 220 15% 18%;
350
- --muted-foreground: 220 10% 65%;
351
-
352
- --accent: 195 80% 55%;
353
- --accent-foreground: 220 25% 10%;
354
-
355
- --destructive: 0 65% 42%;
356
- --destructive-foreground: 0 0% 98%;
357
-
358
- --warning: 38 92% 50%;
359
- --warning-foreground: 48 96% 98%;
360
-
361
- --border: 220 15% 25%;
362
- --input: 220 15% 25%;
363
- --ring: 195 90% 60%;
364
-
365
- --chart-1: 210 80% 60%;
366
- --chart-2: 195 70% 50%;
367
- --chart-3: 215 40% 50%;
368
- --chart-4: 220 10% 55%;
369
- --chart-5: 200 60% 70%;
370
- }
371
-
372
- /*─────────────────────────────────────────────────────────────────────────────
373
- 4. Explicit heading sizes & spacing
374
- ─────────────────────────────────────────────────────────────────────────────*/
375
- h1 {
376
- font-size: 3.5rem;
377
- line-height: 1;
378
- font-weight: 700;
379
- margin: 1.5rem 0 1rem;
380
- }
381
- h2 {
382
- font-size: 2.5rem;
383
- line-height: 1;
384
- font-weight: 700;
385
- margin: 1.25rem 0 0.75rem;
386
- }
387
- h3 {
388
- font-size: 2.25rem;
389
- line-height: 2.5rem;
390
- font-weight: 700;
391
- margin: 1rem 0 0.75rem;
392
- }
393
- h4 {
394
- font-size: 1.5rem;
395
- line-height: 2rem;
396
- font-weight: 700;
397
- margin: 0.75rem 0 0.5rem;
398
- }
399
- h5 {
400
- font-size: 1.25rem;
401
- line-height: 1.75rem;
402
- font-weight: 600;
403
- margin: 0.5rem 0 0.5rem;
404
- }
405
- h6 {
406
- font-size: 1.125rem;
407
- line-height: 1.75rem;
408
- font-weight: 600;
409
- margin: 0.5rem 0 0.5rem;
410
- }
411
-
412
- /*─────────────────────────────────────────────────────────────────────────────
413
- 5. Shimmer loading animation
414
- ─────────────────────────────────────────────────────────────────────────────*/
415
- @keyframes shimmer {
416
- 100% {
417
- transform: translateX(100%);
418
- }
419
- }
420
- .shimmer::before {
421
- content: '';
422
- position: absolute;
423
- top: 0;
424
- left: 0;
425
- right: 0;
426
- bottom: 0;
427
- transform: translateX(-100%);
428
- background-image: linear-gradient(
429
- 90deg,
430
- rgba(255, 255, 255, 0) 0,
431
- rgba(255, 255, 255, 0.2) 20%,
432
- rgba(255, 255, 255, 0.5) 60%,
433
- rgba(255, 255, 255, 0)
434
- );
435
- animation: shimmer 2s infinite;
436
- }
10
+ @import './base.css';
11
+ @import './components.css';
12
+ @import './theme.css';
13
+ @import './typography.css';
14
+ @import './animations.css';
@@ -147,13 +147,10 @@ export default async function RootLayout({
147
147
  <title>{metadata.title as string}</title>
148
148
  <meta name="description" content={metadata.description as string} />
149
149
  <link rel="preconnect" href="https://ppcppwsfnrptznvbxnsz.supabase.co" />
150
- <link rel="preconnect" href="https://pub-a31e3f1a87d144898aeb489a8221f92e.r2.dev" crossOrigin="anonymous" />
151
150
  <link rel="dns-prefetch" href="https://ppcppwsfnrptznvbxnsz.supabase.co" />
152
- <link rel="dns-prefetch" href="https://pub-a31e3f1a87d144898aeb489a8221f92e.r2.dev" />
153
151
  <link rel="dns-prefetch" href="https://aws-0-us-east-1.pooler.supabase.com" />
154
152
  <link rel="dns-prefetch" href="https://db.ppcppwsfnrptznvbxnsz.supabase.co" />
155
153
  <link rel="dns-prefetch" href="https://realtime.supabase.com" />
156
- <link rel="preload" as="image" href="https://pub-a31e3f1a87d144898aeb489a8221f92e.r2.dev/hero-bg.jpg" fetchPriority="high" />
157
154
  <meta name="viewport" content="width=device-width, initial-scale=1" />
158
155
  </head>
159
156
  <body className="bg-background text-foreground min-h-screen flex flex-col">
@@ -261,7 +261,11 @@ export default function ResponsiveNav({
261
261
  >
262
262
  {logo && logo.media ? (
263
263
  <Image
264
- src={`${R2_BASE_URL}/${logo.media.object_key}`}
264
+ src={
265
+ logo.media.object_key.startsWith('/') || logo.media.object_key.startsWith('http')
266
+ ? logo.media.object_key
267
+ : `${R2_BASE_URL}/${logo.media.object_key}`
268
+ }
265
269
  alt={logo.media.alt_text || siteTitle || 'Nextblock'}
266
270
  width={logo.media.width || 100}
267
271
  height={logo.media.height || 32}
@@ -68,6 +68,14 @@ const paddingClasses = {
68
68
  xl: 'py-12'
69
69
  };
70
70
 
71
+ // Vertical alignment classes
72
+ const verticalAlignmentClasses = {
73
+ start: 'items-start',
74
+ center: 'items-center',
75
+ end: 'items-end',
76
+ stretch: 'items-stretch'
77
+ };
78
+
71
79
  // Background style generator
72
80
  function generateBackgroundStyles(background: SectionBlockContent['background']) {
73
81
  const styles: React.CSSProperties = {};
@@ -207,6 +215,7 @@ const HeroBlockRenderer: React.FC<SectionBlockRendererProps> = ({
207
215
  const gapClass = gapClasses[content.column_gap] || gapClasses.md;
208
216
  const paddingTopClass = paddingClasses[content.padding.top] || paddingClasses.md;
209
217
  const paddingBottomClass = paddingClasses[content.padding.bottom] || paddingClasses.md;
218
+ const alignmentClass = content.vertical_alignment ? verticalAlignmentClasses[content.vertical_alignment] : 'items-start';
210
219
 
211
220
  const imageProps = backgroundImage?.blur_data_url
212
221
  ? {
@@ -243,7 +252,7 @@ const HeroBlockRenderer: React.FC<SectionBlockRendererProps> = ({
243
252
  />
244
253
  )}
245
254
  <div className={`${containerClass} relative`}>
246
- <div className={`grid ${gridClass} ${gapClass}`}>
255
+ <div className={`grid ${gridClass} ${gapClass} ${alignmentClass}`}>
247
256
  {content.column_blocks.map((columnBlocks, columnIndex) => (
248
257
  <div key={`column-${columnIndex}`} className="min-h-0 space-y-4">
249
258
  {(Array.isArray(columnBlocks) ? columnBlocks : []).map((block, blockIndex) => (
@@ -46,6 +46,14 @@ const paddingClasses = {
46
46
  xl: 'py-12'
47
47
  };
48
48
 
49
+ // Vertical alignment classes
50
+ const verticalAlignmentClasses = {
51
+ start: 'items-start',
52
+ center: 'items-center',
53
+ end: 'items-end',
54
+ stretch: 'items-stretch'
55
+ };
56
+
49
57
  // Background style generator
50
58
  function generateBackgroundStyles(background: SectionBlockContent['background']) {
51
59
  const styles: React.CSSProperties = {};
@@ -161,6 +169,7 @@ const SectionBlockRenderer: React.FC<SectionBlockRendererProps> = ({
161
169
  const gapClass = gapClasses[content.column_gap] || gapClasses.md;
162
170
  const paddingTopClass = paddingClasses[content.padding.top] || paddingClasses.md;
163
171
  const paddingBottomClass = paddingClasses[content.padding.bottom] || paddingClasses.md;
172
+ const alignmentClass = content.vertical_alignment ? verticalAlignmentClasses[content.vertical_alignment] : 'items-start';
164
173
 
165
174
  return (
166
175
  <section
@@ -168,7 +177,7 @@ const SectionBlockRenderer: React.FC<SectionBlockRendererProps> = ({
168
177
  style={styles}
169
178
  >
170
179
  <div className={containerClass}>
171
- <div className={`grid ${gridClass} ${gapClass}`}>
180
+ <div className={`grid ${gridClass} ${gapClass} ${alignmentClass}`}>
172
181
  {content.column_blocks.map((columnBlocks, columnIndex) => (
173
182
  <div key={`column-${columnIndex}`} className="min-h-0 space-y-4">
174
183
  {(Array.isArray(columnBlocks) ? columnBlocks : []).map((block, blockIndex) => (
@@ -146,6 +146,8 @@ export interface SectionBlockContent {
146
146
  top: 'none' | 'sm' | 'md' | 'lg' | 'xl';
147
147
  bottom: 'none' | 'sm' | 'md' | 'lg' | 'xl';
148
148
  };
149
+ /** Vertical alignment of columns */
150
+ vertical_alignment?: 'start' | 'center' | 'end' | 'stretch';
149
151
  /** Array of blocks within columns - 2D array where each index represents a column */
150
152
  column_blocks: Array<Array<{
151
153
  block_type: BlockType;
@@ -621,6 +623,7 @@ export const blockRegistry: Record<BlockType, BlockDefinition> = {
621
623
  background: { type: "none" },
622
624
  responsive_columns: { mobile: 1, tablet: 2, desktop: 3 },
623
625
  column_gap: "md",
626
+ vertical_alignment: "start",
624
627
  padding: { top: "md", bottom: "md" },
625
628
  column_blocks: [
626
629
  [{ block_type: "text", content: { html_content: "<p>Column 1</p>" } }],
@@ -669,6 +672,16 @@ export const blockRegistry: Record<BlockType, BlockDefinition> = {
669
672
  description: 'Section padding configuration',
670
673
  default: { top: 'md', bottom: 'md' },
671
674
  },
675
+ vertical_alignment: {
676
+ type: 'union',
677
+ required: false,
678
+ description: 'Vertical alignment of columns',
679
+ default: 'start',
680
+ unionValues: ['start', 'center', 'end', 'stretch'] as const,
681
+ constraints: {
682
+ enum: ['start', 'center', 'end', 'stretch'] as const,
683
+ },
684
+ },
672
685
  column_blocks: {
673
686
  type: 'array',
674
687
  required: true,
@@ -1,54 +1,54 @@
1
- {
2
- "name": "@nextblock-cms/template",
3
- "version": "0.2.15",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "next lint"
10
- },
11
- "dependencies": {
12
- "@aws-sdk/client-s3": "^3.920.0",
13
- "@aws-sdk/s3-request-presigner": "^3.920.0",
14
- "@dnd-kit/core": "^6.3.1",
15
- "@dnd-kit/sortable": "^10.0.0",
16
- "@dnd-kit/utilities": "^3.2.2",
17
- "@supabase/ssr": "^0.6.1",
18
- "@supabase/supabase-js": "^2.47.2",
19
- "@tiptap/react": "^3.3.0",
20
- "date-fns": "^3.6.0",
21
- "dotenv": "^16.5.0",
22
- "fast-json-patch": "^3.1.1",
23
- "html-react-parser": "^5.2.6",
24
- "js-cookie": "^3.0.5",
25
- "lodash.debounce": "^4.0.8",
26
- "lucide-react": "^0.534.0",
27
- "next": "^15.5.4",
28
- "nodemailer": "^7.0.4",
29
- "plaiceholder": "^3.0.0",
30
- "react": "19.0.0",
31
- "react-dom": "19.0.0",
32
- "react-hot-toast": "^2.4.1",
33
- "sharp": "^0.34.2",
34
- "uuid": "^10.0.0",
35
- "zod": "^3.25.76",
36
- "@nextblock-cms/ui": "workspace:*",
37
- "@nextblock-cms/utils": "workspace:*",
38
- "@nextblock-cms/db": "workspace:*",
39
- "@nextblock-cms/editor": "workspace:*",
40
- "@nextblock-cms/sdk": "workspace:*"
41
- },
42
- "devDependencies": {
43
- "@types/node": "22.10.2",
44
- "@types/react": "^19.0.0",
45
- "@types/react-dom": "19.0.2",
46
- "autoprefixer": "^10.4.13",
47
- "eslint": "^9.8.0",
48
- "eslint-config-next": "^15.5.4",
49
- "postcss": "^8.4.38",
50
- "tailwindcss": "^3.4.3",
51
- "typescript": "~5.8.2",
52
- "typescript-eslint": "^8.0.0"
53
- }
54
- }
1
+ {
2
+ "name": "@nextblock-cms/template",
3
+ "version": "0.2.17",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@aws-sdk/client-s3": "^3.920.0",
13
+ "@aws-sdk/s3-request-presigner": "^3.920.0",
14
+ "@dnd-kit/core": "^6.3.1",
15
+ "@dnd-kit/sortable": "^10.0.0",
16
+ "@dnd-kit/utilities": "^3.2.2",
17
+ "@supabase/ssr": "^0.6.1",
18
+ "@supabase/supabase-js": "^2.47.2",
19
+ "@tiptap/react": "^3.3.0",
20
+ "date-fns": "^3.6.0",
21
+ "dotenv": "^16.5.0",
22
+ "fast-json-patch": "^3.1.1",
23
+ "html-react-parser": "^5.2.6",
24
+ "js-cookie": "^3.0.5",
25
+ "lodash.debounce": "^4.0.8",
26
+ "lucide-react": "^0.534.0",
27
+ "next": "^15.5.4",
28
+ "nodemailer": "^7.0.4",
29
+ "plaiceholder": "^3.0.0",
30
+ "react": "19.0.0",
31
+ "react-dom": "19.0.0",
32
+ "react-hot-toast": "^2.4.1",
33
+ "sharp": "^0.34.2",
34
+ "uuid": "^10.0.0",
35
+ "zod": "^3.25.76",
36
+ "@nextblock-cms/ui": "workspace:*",
37
+ "@nextblock-cms/utils": "workspace:*",
38
+ "@nextblock-cms/db": "workspace:*",
39
+ "@nextblock-cms/editor": "workspace:*",
40
+ "@nextblock-cms/sdk": "workspace:*"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "22.10.2",
44
+ "@types/react": "^19.0.0",
45
+ "@types/react-dom": "19.0.2",
46
+ "autoprefixer": "^10.4.13",
47
+ "eslint": "^9.8.0",
48
+ "eslint-config-next": "^15.5.4",
49
+ "postcss": "^8.4.38",
50
+ "tailwindcss": "^3.4.3",
51
+ "typescript": "~5.8.2",
52
+ "typescript-eslint": "^8.0.0"
53
+ }
54
+ }
@@ -14,7 +14,7 @@ module.exports = {
14
14
  const libsDir = join(__dirname, '../../libs');
15
15
  if (existsSync(libsDir)) {
16
16
  projectGlobs.push(
17
- join(libsDir, '**/*.{ts,tsx,js,jsx,md,mdx,html}')
17
+ join(libsDir, '**/*.{ts,tsx,js,jsx,md,mdx,html,sql}')
18
18
  );
19
19
  }
20
20
  return projectGlobs;
@@ -37,7 +37,8 @@
37
37
  "**/*.jsx",
38
38
  "next-env.d.ts",
39
39
  ".next/types/**/*.ts",
40
- "../../dist/apps/nextblock/.next/types/**/*.ts"
40
+ "../../dist/apps/nextblock/.next/types/**/*.ts",
41
+ "../../tailwind.config.js"
41
42
  ],
42
43
  "exclude": [
43
44
  "node_modules",
@@ -59,4 +60,4 @@
59
60
  "path": "../../libs/utils/tsconfig.json"
60
61
  }
61
62
  ]
62
- }
63
+ }