@deneb-ui/ui 2.0.100 → 2.0.102

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.
@@ -49,7 +49,7 @@ function EditableCustomerReviews({ sectionPath = 'home', listPath = 'reviews', t
49
49
  const liveReviews = (0, SiteDataProvider_1.useReviews)();
50
50
  const siteData = (0, SiteDataProvider_1.useSiteData)();
51
51
  const contentRecord = (siteData?.content && typeof siteData.content === 'object' ? siteData.content : {});
52
- const siteReviews = contentRecord[sectionPath]?.reviews || contentRecord.reviews || (contentRecord.home && typeof contentRecord.home === 'object' ? contentRecord.home.reviews : undefined);
52
+ const siteReviews = contentRecord[sectionPath]?.[listPath] || contentRecord[listPath] || contentRecord[sectionPath]?.reviews || contentRecord.reviews || (contentRecord.home && typeof contentRecord.home === 'object' ? contentRecord.home.reviews : undefined);
53
53
  const rawReviews = (reviews && reviews.length > 0)
54
54
  ? reviews
55
55
  : (liveReviews && liveReviews.length > 0)
@@ -80,6 +80,6 @@ function EditableFilterSidebar({ basePath = 'filters', categories = ['All', 'Run
80
80
  return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => toggleSize(size), "data-preview-static": "filter-size-btn", className: `rounded-lg py-2 text-xs font-semibold transition-all border ${isSelected
81
81
  ? 'border-white bg-white text-black'
82
82
  : 'border-neutral-800 bg-neutral-800/50 text-neutral-300 hover:border-neutral-700'}`, children: size }, size));
83
- }) })] })), (0, jsx_runtime_1.jsx)("div", { className: "border-t border-neutral-800/80 pt-6", children: (0, jsx_runtime_1.jsxs)("label", { className: "flex items-center justify-between cursor-pointer group", "data-preview-static": "filter-instock-toggle", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium text-neutral-300 group-hover:text-white transition-colors", "data-preview-field-path": fieldPath('inStockLabel'), children: inStockLabel }), (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: inStockOnly, onChange: toggleInStock, className: "h-4 w-4 rounded border-neutral-700 bg-neutral-800 text-emerald-500 accent-emerald-500 cursor-pointer" })] }) })] })] })] }));
83
+ }) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "border-t border-neutral-800/80 pt-6", children: [(0, jsx_runtime_1.jsx)("h4", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-neutral-400", "data-preview-field-path": fieldPath('availabilityTitle'), children: availabilityTitle }), (0, jsx_runtime_1.jsxs)("label", { className: "flex items-center justify-between cursor-pointer group", "data-preview-static": "filter-instock-toggle", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium text-neutral-300 group-hover:text-white transition-colors", "data-preview-field-path": fieldPath('inStockLabel'), children: inStockLabel }), (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: inStockOnly, onChange: toggleInStock, className: "h-4 w-4 rounded border-neutral-700 bg-neutral-800 text-emerald-500 accent-emerald-500 cursor-pointer" })] })] })] })] })] }));
84
84
  }
85
85
  exports.FilterSidebar = EditableFilterSidebar;
@@ -38,5 +38,5 @@ function EditableGrid({ as: Component = 'div', id, 'data-preview-list-path': pre
38
38
  ...styleVars,
39
39
  ...style,
40
40
  };
41
- return ((0, jsx_runtime_1.jsx)(Component, { "data-preview-list-path": listPath, "data-preview-style-target": stylePath, "data-preview-style-type": "grid", className: `deneb-grid editable-grid ${className}`.trim(), style: gridStyle, ...(dataAttrs || {}), ...props, children: children }));
41
+ return ((0, jsx_runtime_1.jsx)(Component, { "data-preview-list-path": listPath, "data-preview-static": previewStatic, "data-preview-style-target": stylePath, "data-preview-style-type": "grid", className: `deneb-grid editable-grid ${className}`.trim(), style: gridStyle, ...(dataAttrs || {}), ...props, children: children }));
42
42
  }
@@ -73,6 +73,7 @@ function EditablePricingCard({ itemPath, item, as: Component = 'div', isPopular
73
73
  }) }))] }), (0, jsx_runtime_1.jsx)("div", { style: { marginTop: 'auto', paddingTop: '1rem' }, children: actionSlot ? (actionSlot) : ((0, jsx_runtime_1.jsx)("span", { "data-target-page": ctaTarget, children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
74
74
  if (typeof window !== 'undefined') {
75
75
  const basePath = (process.env.NEXT_PUBLIC_SITE_BASE_PATH ?? '').replace(/\/$/, '');
76
+ // eslint-disable-next-line @next/next/no-location-assign-relative-destination
76
77
  window.location.href = `${basePath}/${ctaTarget}`;
77
78
  }
78
79
  }, style: {
@@ -88,4 +88,4 @@ export interface EditableProductCardProps extends React.HTMLAttributes<HTMLEleme
88
88
  onAddToCart?: (product: ProductItem) => void;
89
89
  onWhatsAppClick?: (product: ProductItem, url: string) => void;
90
90
  }
91
- export declare function EditableProductCard({ itemPath, product: productProp, item: itemProp, cardVariant, imageFallback, as: Component, currency, showBrand, showPrice, showDescription, showCategory, whatsappNumber: whatsappNumberProp, whatsappPhone: whatsappPhoneProp, showOptions, allowInteractiveSwatches, storeName, showWhatsAppButton, showAddToCartButton, whatsappActionLabel, addToCartLabel, actionSlot, actionLabel, actionLabelPath, onAddToCart, onWhatsAppClick, className, style, ...props }: EditableProductCardProps): React.JSX.Element;
91
+ export declare function EditableProductCard({ itemPath, product: productProp, item: itemProp, cardVariant, imageFallback, as: Component, currency, showBrand, showPrice, showDescription, showCategory, whatsappNumber: whatsappNumberProp, whatsappPhone: whatsappPhoneProp, storeName, showWhatsAppButton, showAddToCartButton, whatsappActionLabel, addToCartLabel, actionSlot, onAddToCart, onWhatsAppClick, className, style, ...props }: EditableProductCardProps): React.JSX.Element;
@@ -45,8 +45,8 @@ function WhatsAppIcon({ className = '', style = {} }) {
45
45
  function CartIcon({ className = '', style = {} }) {
46
46
  return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, style: { display: 'inline-block', verticalAlign: 'middle', flexShrink: 0, ...style }, "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("circle", { cx: "9", cy: "21", r: "1" }), (0, jsx_runtime_1.jsx)("circle", { cx: "20", cy: "21", r: "1" }), (0, jsx_runtime_1.jsx)("path", { d: "M1 1h4l2.68 13.39a2 2 0 002 1.61h9.72a2 2 0 002-1.61L23 6H6" })] }));
47
47
  }
48
- function EditableProductCard({ itemPath, product: productProp, item: itemProp, cardVariant = 'modern-glass', imageFallback, as: Component = 'article', currency = 'LKR', showBrand = true, showPrice = true, showDescription = true, showCategory = true, whatsappNumber: whatsappNumberProp, whatsappPhone: whatsappPhoneProp, showOptions, allowInteractiveSwatches, storeName, showWhatsAppButton = true, showAddToCartButton = true, whatsappActionLabel = 'Inquire on WhatsApp', addToCartLabel = 'Add to Cart', actionSlot, actionLabel, actionLabelPath, onAddToCart, onWhatsAppClick, className = '', style, ...props }) {
49
- const product = (productProp || itemProp || {});
48
+ function EditableProductCard({ itemPath, product: productProp, item: itemProp, cardVariant = 'modern-glass', imageFallback, as: Component = 'article', currency = 'LKR', showBrand = true, showPrice = true, showDescription = true, showCategory = true, whatsappNumber: whatsappNumberProp, whatsappPhone: whatsappPhoneProp, storeName, showWhatsAppButton = true, showAddToCartButton = true, whatsappActionLabel = 'Inquire on WhatsApp', addToCartLabel = 'Add to Cart', actionSlot, onAddToCart, onWhatsAppClick, className = '', style, ...props }) {
49
+ const product = (0, react_1.useMemo)(() => (productProp || itemProp || {}), [productProp, itemProp]);
50
50
  const whatsappNumber = whatsappPhoneProp || whatsappNumberProp || '94770000000';
51
51
  const cart = (0, useCart_1.useOptionalCart)();
52
52
  // Dynamic Options, Measurements & Colors resolution
@@ -60,12 +60,12 @@ function EditableProductCard({ itemPath, product: productProp, item: itemProp, c
60
60
  setSelectedColor('');
61
61
  setColorImage('');
62
62
  }
63
- }, [product, resolvedOptions.colors]);
63
+ }, [product, resolvedOptions.colors, selectedColor]);
64
64
  (0, react_1.useEffect)(() => {
65
65
  if (selectedOption && !resolvedOptions.options.some((o) => o.toLowerCase() === selectedOption.toLowerCase())) {
66
66
  setSelectedOption('');
67
67
  }
68
- }, [product, resolvedOptions.options]);
68
+ }, [product, resolvedOptions.options, selectedOption]);
69
69
  // Variant & Dynamic Price Resolution
70
70
  const resolvedPrice = (0, react_1.useMemo)(() => (0, productOptions_1.resolveVariantPrice)(product, selectedOption, selectedColor, currency), [product, selectedOption, selectedColor, currency]);
71
71
  const titleKey = 'title' in (product || {}) && !('name' in (product || {}) && product.name)
@@ -22,14 +22,17 @@ const urls_1 = require("./utils/urls");
22
22
  */
23
23
  function EditableProductDetail({ sectionPath = 'product', product, sizes, colors, onAddToSelection, whatsappUrl, className = '', style, ...props }) {
24
24
  // 1. Resolve Images: Support gallery, images array, and single image fields
25
+ const productRecord = (product || {});
26
+ const productPhoto = productRecord.photo;
27
+ const productThumbnail = productRecord.thumbnail;
25
28
  const validImages = (0, react_1.useMemo)(() => {
26
29
  const rawList = (product.gallery && product.gallery.length > 0)
27
30
  ? product.gallery
28
31
  : (product.images && product.images.length > 0)
29
32
  ? product.images
30
- : [product.featuredImage || product.imageUrl || product.image || product.photo || product.thumbnail || '/products/vanta-aero-x.jpg'];
33
+ : [product.featuredImage || product.imageUrl || product.image || productPhoto || productThumbnail || '/products/vanta-aero-x.jpg'];
31
34
  return rawList.filter(Boolean);
32
- }, [product.gallery, product.images, product.featuredImage, product.imageUrl, product.image, product.photo, product.thumbnail]);
35
+ }, [product.gallery, product.images, product.featuredImage, product.imageUrl, product.image, productPhoto, productThumbnail]);
33
36
  // 2. Resolve Options (Sizes, Volumes, Weights, Counts)
34
37
  const resolved = (0, productOptions_1.resolveProductOptions)(product);
35
38
  const effectiveOptions = (0, react_1.useMemo)(() => {
@@ -105,7 +108,7 @@ function EditableProductDetail({ sectionPath = 'product', product, sizes, colors
105
108
  if (resolvedPrice.variantImage && resolvedPrice.variantImage !== activeImage) {
106
109
  setActiveImage(resolvedPrice.variantImage);
107
110
  }
108
- }, [resolvedPrice.variantImage]);
111
+ }, [resolvedPrice.variantImage, activeImage]);
109
112
  const handleSelectColor = (c) => {
110
113
  setSelectedColor(c.name);
111
114
  if (c.image) {
@@ -124,7 +124,7 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
124
124
  setInternalPage(1);
125
125
  setLoadedCount(activePageSize);
126
126
  onPageChange?.(1);
127
- }, [searchQuery, activeCategory, activePageSize]);
127
+ }, [searchQuery, activeCategory, activePageSize, onPageChange]);
128
128
  const filteredProducts = (0, react_1.useMemo)(() => {
129
129
  let result = products;
130
130
  // Filter by category
@@ -204,8 +204,10 @@ function EditableProductShowcase({ sectionPath = 'home', listPath = 'featuredPho
204
204
  e.stopPropagation();
205
205
  if (onQuickView)
206
206
  onQuickView(phone, idx);
207
- else if (typeof window !== 'undefined')
207
+ else if (typeof window !== 'undefined') {
208
+ // eslint-disable-next-line @next/next/no-location-assign-relative-destination
208
209
  window.location.href = detailUrl;
210
+ }
209
211
  }, className: "flex h-9 w-9 items-center justify-center rounded-full border border-black/10 bg-black/[0.02] text-zinc-600 hover:text-cyan-600 hover:border-cyan-400/50 hover:bg-cyan-50/50 transition-all cursor-pointer", title: "Quick View", "aria-label": "Quick View Product", children: (0, jsx_runtime_1.jsxs)("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] }) })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => handleWhatsAppOrder(phone, activeColor.name, e), className: "w-full flex items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-cyan-400 via-sky-400 to-blue-600 py-3 px-4 text-xs font-bold text-white shadow-[0_4px_15px_rgba(0,240,255,0.25)] hover:shadow-[0_6px_25px_rgba(0,240,255,0.45)] transition-all hover:scale-[1.01] active:scale-[0.99] cursor-pointer", children: [(0, jsx_runtime_1.jsx)("svg", { className: "h-4 w-4 fill-white", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { d: "M12.031 6.172c-3.181 0-5.767 2.586-5.768 5.766-.001 1.298.38 2.27 1.019 3.287l-.582 2.128 2.182-.573c.978.58 1.911.928 3.145.929 3.178 0 5.767-2.587 5.768-5.766.001-3.187-2.575-5.771-5.764-5.771zm3.392 8.244c-.144.405-.837.774-1.17.824-.312.045-.634.055-.992-.061-.635-.206-1.464-.567-2.527-1.63-1.341-1.341-1.849-2.531-2.006-2.986-.157-.455-.015-.81.212-1.037.202-.202.455-.253.606-.253.152 0 .303.004.436.012.141.008.329-.053.515.394.192.465.657 1.602.714 1.719.057.116.096.253.019.405-.076.152-.114.248-.228.38-.114.133-.24.296-.343.397-.114.114-.233.238-.1.467.133.228.591.976 1.269 1.58 1.077.959 1.83 1.157 2.058 1.271.228.114.362.096.495-.057.133-.152.571-.666.723-.894.152-.228.303-.19.515-.114.212.076 1.346.635 1.575.749.228.114.38.171.436.266.057.095.057.553-.087.958z" }) }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.whatsappOrderLabel`, "data-preview-style-target": `${sectionPath}.whatsappOrderLabel`, "data-preview-style-type": "text", children: whatsappOrderLabel })] })] })] }, phoneId));
210
212
  }) })] })] }));
211
213
  }
@@ -91,17 +91,6 @@ exports.FONT_FAMILY_MAP = {
91
91
  heading: 'var(--heading-font, Inter, sans-serif)',
92
92
  body: 'var(--body-font, Inter, sans-serif)',
93
93
  };
94
- function toCssUnit(value) {
95
- if (value === undefined || value === null || value === '')
96
- return undefined;
97
- const str = String(value).trim();
98
- if (!str)
99
- return undefined;
100
- if (/^-?\d+(\.\d+)?$/.test(str)) {
101
- return `${str}px`;
102
- }
103
- return str;
104
- }
105
94
  /**
106
95
  * EditableText provides full typography controls (color, size, weight, line-height, alignment, transform, variants)
107
96
  * alongside strict Fivora visual editing bindings.
@@ -167,3 +167,17 @@ export declare function useCommon(fallback?: GenericRecord): GenericRecord;
167
167
  * with robust fallbacks across contact, common, and shop profile.
168
168
  */
169
169
  export declare function useContact(fallback?: GenericRecord): GenericRecord;
170
+ export interface SyncedBranding {
171
+ logoUrl: string;
172
+ websiteTitle: string;
173
+ brandSubtext: string;
174
+ slogan: string;
175
+ faviconUrl: string;
176
+ }
177
+ /**
178
+ * Universal Synced Branding Hook:
179
+ * Guarantees real-time live synchronization for brand identity across visual editor preview,
180
+ * merchant DB profile, and template defaults.
181
+ * Hierarchy: Visual Editor Edits (content.common) -> Live Merchant Shop Profile -> Template Fallback.
182
+ */
183
+ export declare function useSyncedBranding(fallback?: Partial<SyncedBranding>): SyncedBranding;
@@ -29,6 +29,7 @@ exports.useReviews = useReviews;
29
29
  exports.isPreviewValidation = isPreviewValidation;
30
30
  exports.useCommon = useCommon;
31
31
  exports.useContact = useContact;
32
+ exports.useSyncedBranding = useSyncedBranding;
32
33
  const jsx_runtime_1 = require("react/jsx-runtime");
33
34
  const react_1 = require("react");
34
35
  const core_1 = require("@deneb-ui/core");
@@ -1086,3 +1087,37 @@ function useContact(fallback = {}) {
1086
1087
  googleMapLink,
1087
1088
  };
1088
1089
  }
1090
+ /**
1091
+ * Universal Synced Branding Hook:
1092
+ * Guarantees real-time live synchronization for brand identity across visual editor preview,
1093
+ * merchant DB profile, and template defaults.
1094
+ * Hierarchy: Visual Editor Edits (content.common) -> Live Merchant Shop Profile -> Template Fallback.
1095
+ */
1096
+ function useSyncedBranding(fallback = {}) {
1097
+ const siteData = useSiteData();
1098
+ const content = isRecord(siteData?.content) ? siteData.content : null;
1099
+ const common = isRecord(content?.common) ? content.common : {};
1100
+ const shop = useShop();
1101
+ const websiteTitle = (typeof common.websiteTitle === 'string' && common.websiteTitle.trim()) ||
1102
+ (typeof common.siteName === 'string' && common.siteName.trim()) ||
1103
+ (typeof shop.businessName === 'string' && shop.businessName.trim()) ||
1104
+ (typeof shop.name === 'string' && shop.name.trim()) ||
1105
+ (typeof fallback.websiteTitle === 'string' && fallback.websiteTitle.trim()) ||
1106
+ 'Storefront';
1107
+ const logoUrl = (typeof common.logoUrl === 'string' && common.logoUrl.trim()) ||
1108
+ (typeof shop.logoUrl === 'string' && shop.logoUrl.trim()) ||
1109
+ (typeof fallback.logoUrl === 'string' && fallback.logoUrl.trim()) ||
1110
+ '';
1111
+ const brandSubtext = (typeof common.brandSubtext === 'string' && common.brandSubtext.trim()) ||
1112
+ (typeof common.slogan === 'string' && common.slogan.trim()) ||
1113
+ (typeof fallback.brandSubtext === 'string' && fallback.brandSubtext.trim()) ||
1114
+ '';
1115
+ const faviconUrl = logoUrl || fallback.faviconUrl || '';
1116
+ return {
1117
+ logoUrl,
1118
+ websiteTitle,
1119
+ brandSubtext,
1120
+ slogan: brandSubtext,
1121
+ faviconUrl,
1122
+ };
1123
+ }
@@ -54,4 +54,4 @@ export interface ThemeStylesProps {
54
54
  * Automatically injects standard and custom fivora theme variables into the document.
55
55
  * Supports light-only, dark-only, and dual-mode (light & dark toggle) templates.
56
56
  */
57
- export declare function ThemeStyles({ theme, defaultPrimary, defaultSecondary, defaultAccent, defaultBg, defaultText, enableDualMode, }: ThemeStylesProps): React.JSX.Element;
57
+ export declare function ThemeStyles({ theme, enableDualMode, }: ThemeStylesProps): React.JSX.Element;
@@ -213,7 +213,7 @@ function getThemeCssProperties(theme) {
213
213
  * Automatically injects standard and custom fivora theme variables into the document.
214
214
  * Supports light-only, dark-only, and dual-mode (light & dark toggle) templates.
215
215
  */
216
- function ThemeStyles({ theme, defaultPrimary = '#2563eb', defaultSecondary = '#0f172a', defaultAccent = '#14b8a6', defaultBg = '#ffffff', defaultText = '#0f172a', enableDualMode = true, }) {
216
+ function ThemeStyles({ theme, enableDualMode = true, }) {
217
217
  const styleProps = getThemeCssProperties(theme);
218
218
  const baseLines = Object.entries(styleProps)
219
219
  .filter(([key]) => key.startsWith('--'))
@@ -6,25 +6,6 @@ exports.useComponentStyle = useComponentStyle;
6
6
  const react_1 = require("react");
7
7
  const core_1 = require("@deneb-ui/core");
8
8
  const SiteDataProvider_1 = require("../SiteDataProvider");
9
- function getNestedValue(root, dottedPath) {
10
- if (!root || !dottedPath)
11
- return undefined;
12
- const parts = (0, SiteDataProvider_1.parseFieldPath)(dottedPath);
13
- let current = root;
14
- for (const part of parts) {
15
- if (typeof part === 'number') {
16
- if (!Array.isArray(current))
17
- return undefined;
18
- current = current[part];
19
- }
20
- else {
21
- if (!(0, SiteDataProvider_1.isRecord)(current))
22
- return undefined;
23
- current = current[part];
24
- }
25
- }
26
- return current;
27
- }
28
9
  function resolveComponentStyle(siteData, stylePath, defaults) {
29
10
  const stylesTree = (0, SiteDataProvider_1.isRecord)(siteData.styles) ? siteData.styles : {};
30
11
  const fromStylesTree = stylesTree[stylePath];
@@ -177,7 +177,7 @@ function useWhatsAppForm(options = {}) {
177
177
  return `${resolvedUrl}${separator}text=${encodeURIComponent(message)}`;
178
178
  }
179
179
  return `https://wa.me/?text=${encodeURIComponent(message)}`;
180
- }, [resolvedUrl, options.formatMessage, options.formName, resolvedBusinessName]);
180
+ }, [resolvedUrl, options, resolvedBusinessName]);
181
181
  const dispatchUrl = (0, react_1.useCallback)((url, data) => {
182
182
  setIsSubmitting(true);
183
183
  setWhatsAppLink(url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.100",
3
+ "version": "2.0.102",
4
4
  "description": "Visual-first React component library for editable commerce storefronts. Built for Next.js and Fivora.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  ],
51
51
  "license": "MIT",
52
52
  "dependencies": {
53
- "@deneb-ui/core": "^2.0.100"
53
+ "@deneb-ui/core": "^2.0.102"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.0.0 || ^19.0.0",