@deneb-ui/create-template 2.0.107 → 2.0.109

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": "@deneb-ui/create-template",
3
- "version": "2.0.107",
3
+ "version": "2.0.109",
4
4
  "description": "Scaffold production-ready DENEB UI storefront templates for Next.js in seconds.",
5
5
  "bin": {
6
6
  "create-deneb-template": "bin/index.js",
@@ -162,6 +162,24 @@ Edit `src/data/site-data.json` under `template.structure.theme`:
162
162
  ```
163
163
  3. Add the page content to `src/data/site-data.json`.
164
164
 
165
+ ### One-page section navigation
166
+
167
+ Use route links such as `/about` only when that route is exported. For a
168
+ one-page design, link to a stable section ID instead:
169
+
170
+ ```tsx
171
+ <a href="#about">About</a>
172
+ <section id="about" data-section-id="about" data-design-section="about">
173
+ {/* About content */}
174
+ </section>
175
+ ```
176
+
177
+ Every visible navigation item must resolve to exactly one visible section in
178
+ the exported Home document. Keep labels such as Home, About, Services, Gallery,
179
+ and Contact as neutral template UI copy rather than AI-generated marketing
180
+ text. Test direct `/#about` loading and sticky-header scrolling before
181
+ packaging.
182
+
165
183
  ---
166
184
 
167
185
  ## Packaging for Fivora
@@ -347,6 +347,21 @@
347
347
  "type": "text",
348
348
  "label": "Feature"
349
349
  }
350
+ },
351
+ {
352
+ "key": "price",
353
+ "type": "number",
354
+ "label": "Price"
355
+ },
356
+ {
357
+ "key": "priceLabel",
358
+ "type": "text",
359
+ "label": "Price Label (e.g. Starting from, per hour)"
360
+ },
361
+ {
362
+ "key": "showPrice",
363
+ "type": "boolean",
364
+ "label": "Show Price on Storefront"
350
365
  }
351
366
  ]
352
367
  },
@@ -442,8 +457,7 @@
442
457
  {
443
458
  "key": "price",
444
459
  "type": "number",
445
- "label": "Price",
446
- "required": true
460
+ "label": "Price"
447
461
  },
448
462
  {
449
463
  "key": "compareAtPrice",
@@ -469,6 +483,31 @@
469
483
  "key": "badge",
470
484
  "type": "text",
471
485
  "label": "Badge"
486
+ },
487
+ {
488
+ "key": "showPrice",
489
+ "type": "boolean",
490
+ "label": "Show Price on Storefront"
491
+ },
492
+ {
493
+ "key": "isPriceRange",
494
+ "type": "boolean",
495
+ "label": "Enable Price Range"
496
+ },
497
+ {
498
+ "key": "minPrice",
499
+ "type": "number",
500
+ "label": "Minimum Price"
501
+ },
502
+ {
503
+ "key": "maxPrice",
504
+ "type": "number",
505
+ "label": "Maximum Price"
506
+ },
507
+ {
508
+ "key": "priceRange",
509
+ "type": "text",
510
+ "label": "Price Range Display"
472
511
  }
473
512
  ]
474
513
  }
@@ -18,7 +18,7 @@
18
18
  "next": "15.5.23",
19
19
  "react": "19.2.8",
20
20
  "react-dom": "19.2.8",
21
- "@deneb-ui/ui": "^2.0.107"
21
+ "@deneb-ui/ui": "^2.0.109"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@eslint/eslintrc": "3.3.6",
@@ -28,7 +28,7 @@
28
28
  "eslint": "9.39.5",
29
29
  "eslint-config-next": "15.5.23",
30
30
  "typescript": "5.8.3",
31
- "@deneb-ui/cli": "^2.0.107",
31
+ "@deneb-ui/cli": "^2.0.109",
32
32
  "tailwindcss": "^3.4.17",
33
33
  "postcss": "8.5.26",
34
34
  "autoprefixer": "^10.4.20"
@@ -255,6 +255,28 @@ export default function HomePage() {
255
255
  const badge = contentText(product.badge);
256
256
  const category = contentText(product.category);
257
257
 
258
+ const itemShowPrice =
259
+ (product as any)?.showPrice !== undefined
260
+ ? Boolean((product as any).showPrice)
261
+ : ((product as any)?.customData?.showPrice !== undefined
262
+ ? Boolean((product as any).customData.showPrice)
263
+ : true);
264
+ const isRange = Boolean(
265
+ (product as any)?.isPriceRange ||
266
+ ((product as any)?.minPrice !== undefined && (product as any)?.maxPrice !== undefined) ||
267
+ (product as any)?.priceRange
268
+ );
269
+ let displayPrice = `${currency} ${price}`;
270
+ if (isRange) {
271
+ if ((product as any).priceRange) {
272
+ displayPrice = String((product as any).priceRange);
273
+ } else {
274
+ const min = (product as any).minPrice !== undefined ? (product as any).minPrice : price;
275
+ const max = (product as any).maxPrice !== undefined ? (product as any).maxPrice : price;
276
+ displayPrice = `${currency} ${min} – ${currency} ${max}`;
277
+ }
278
+ }
279
+
258
280
  return (
259
281
  <EditableCard
260
282
  key={productId ?? index}
@@ -266,7 +288,11 @@ export default function HomePage() {
266
288
  radius="xl"
267
289
  style={{ display: 'flex', flexDirection: 'column' }}
268
290
  >
269
- <div style={{ position: 'relative', overflow: 'hidden', borderRadius: '12px', marginBottom: '0.75rem' }}>
291
+ <a
292
+ href={platformProductDetailHref(productId)}
293
+ data-preview-static="Open the stable live product detail page"
294
+ style={{ position: 'relative', overflow: 'hidden', borderRadius: '12px', marginBottom: '0.75rem', display: 'block' }}
295
+ >
270
296
  <EditableImage
271
297
  src={contentText(product.imageUrl) || '/placeholder.svg'}
272
298
  alt={contentText(product.name)}
@@ -284,7 +310,7 @@ export default function HomePage() {
284
310
  {badge}
285
311
  </span>
286
312
  )}
287
- </div>
313
+ </a>
288
314
 
289
315
  <div style={{ display: 'flex', flexDirection: 'column', flex: 1, gap: '0.5rem' }}>
290
316
  {category && (
@@ -296,14 +322,20 @@ export default function HomePage() {
296
322
  {category}
297
323
  </span>
298
324
  )}
299
- <EditableText
300
- variant="h3"
301
- size="lg"
302
- weight="bold"
303
- color="heading"
304
- data-preview-field-path={`products[${index}].name`}
305
- defaultValue={contentText(product.name)}
306
- />
325
+ <a
326
+ href={platformProductDetailHref(productId)}
327
+ data-preview-static="Open the stable live product detail page"
328
+ style={{ textDecoration: 'none', color: 'inherit' }}
329
+ >
330
+ <EditableText
331
+ variant="h3"
332
+ size="lg"
333
+ weight="bold"
334
+ color="heading"
335
+ data-preview-field-path={`products[${index}].name`}
336
+ defaultValue={contentText(product.name)}
337
+ />
338
+ </a>
307
339
  <EditableText
308
340
  variant="p"
309
341
  color="muted"
@@ -311,25 +343,45 @@ export default function HomePage() {
311
343
  defaultValue={contentText(product.description)}
312
344
  />
313
345
 
314
- <div style={{ display: 'flex', alignItems: 'baseline', gap: '0.5rem', marginTop: 'auto', paddingTop: '0.75rem' }}>
315
- <EditableText
316
- as="span"
317
- weight="bold"
318
- size="xl"
319
- color="primary"
320
- data-preview-field-path={`products[${index}].price`}
321
- defaultValue={`${currency} ${price}`}
322
- />
323
- {compareAt > 0 && (
346
+ {itemShowPrice ? (
347
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: '0.5rem', marginTop: 'auto', paddingTop: '0.75rem', flexWrap: 'wrap' }}>
324
348
  <EditableText
325
349
  as="span"
326
- color="muted"
327
- style={{ textDecoration: 'line-through', fontSize: '0.875rem' }}
328
- data-preview-field-path={`products[${index}].compareAtPrice`}
329
- defaultValue={`${currency} ${compareAt}`}
350
+ weight="bold"
351
+ size="xl"
352
+ color="primary"
353
+ data-preview-field-path={`products[${index}].price`}
354
+ defaultValue={displayPrice}
330
355
  />
331
- )}
332
- </div>
356
+ {compareAt > 0 && !isRange && (
357
+ <EditableText
358
+ as="span"
359
+ color="muted"
360
+ style={{ textDecoration: 'line-through', fontSize: '0.875rem' }}
361
+ data-preview-field-path={`products[${index}].compareAtPrice`}
362
+ defaultValue={`${currency} ${compareAt}`}
363
+ />
364
+ )}
365
+ </div>
366
+ ) : (
367
+ <div style={{ marginTop: 'auto', paddingTop: '0.75rem' }}>
368
+ <span
369
+ style={{
370
+ display: 'inline-flex',
371
+ alignItems: 'center',
372
+ padding: '3px 8px',
373
+ borderRadius: '6px',
374
+ fontSize: '0.75rem',
375
+ fontWeight: 600,
376
+ backgroundColor: 'rgba(255, 255, 255, 0.08)',
377
+ color: 'var(--muted-text, #94a3b8)',
378
+ border: '1px solid rgba(255, 255, 255, 0.12)',
379
+ }}
380
+ >
381
+ Price on Request
382
+ </span>
383
+ </div>
384
+ )}
333
385
 
334
386
  <div style={{ marginTop: '0.75rem' }}>
335
387
  <a
@@ -93,6 +93,72 @@ export default function ServicesPage() {
93
93
  ))}
94
94
  </ul>
95
95
  )}
96
+ {(() => {
97
+ const itemShowPrice =
98
+ (service as any)?.showPrice !== undefined
99
+ ? Boolean((service as any).showPrice)
100
+ : ((service as any)?.customData?.showPrice !== undefined
101
+ ? Boolean((service as any).customData.showPrice)
102
+ : true);
103
+ const price = (service as any).price;
104
+ const priceLabel = contentText((service as any).priceLabel);
105
+ const hasNumericPrice = price !== undefined && price !== null && String(price).trim() !== '';
106
+ const formattedPrice = hasNumericPrice
107
+ ? typeof price === 'number'
108
+ ? `LKR ${price.toLocaleString()}`
109
+ : String(price).includes('LKR')
110
+ ? String(price)
111
+ : `LKR ${price}`
112
+ : '';
113
+
114
+ if (itemShowPrice && (hasNumericPrice || priceLabel)) {
115
+ return (
116
+ <div style={{ marginTop: 'auto', paddingTop: '0.75rem', display: 'flex', alignItems: 'baseline', gap: '0.375rem', flexWrap: 'wrap' }}>
117
+ {hasNumericPrice && (
118
+ <EditableText
119
+ as="span"
120
+ weight="bold"
121
+ size="lg"
122
+ color="primary"
123
+ data-preview-field-path={`services[${index}].price`}
124
+ defaultValue={formattedPrice}
125
+ />
126
+ )}
127
+ {priceLabel && (
128
+ <EditableText
129
+ as="span"
130
+ color="muted"
131
+ data-preview-field-path={`services[${index}].priceLabel`}
132
+ defaultValue={priceLabel}
133
+ style={{ fontSize: '0.8125rem' }}
134
+ />
135
+ )}
136
+ </div>
137
+ );
138
+ }
139
+ if (!itemShowPrice) {
140
+ return (
141
+ <div style={{ marginTop: 'auto', paddingTop: '0.75rem' }}>
142
+ <span
143
+ style={{
144
+ display: 'inline-flex',
145
+ alignItems: 'center',
146
+ padding: '3px 8px',
147
+ borderRadius: '6px',
148
+ fontSize: '0.75rem',
149
+ fontWeight: 600,
150
+ backgroundColor: 'rgba(255, 255, 255, 0.08)',
151
+ color: 'var(--muted-text, #94a3b8)',
152
+ border: '1px solid rgba(255, 255, 255, 0.12)',
153
+ }}
154
+ >
155
+ Quote on Request
156
+ </span>
157
+ </div>
158
+ );
159
+ }
160
+ return null;
161
+ })()}
96
162
  </EditableCard>
97
163
  );
98
164
  })}