@deneb-ui/ui 2.0.14 → 2.0.15

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.
@@ -23,12 +23,13 @@ function EditableAnnouncementBar({ fieldPath = 'site.announcement', defaultText
23
23
  const { siteData } = (0, SiteDataProvider_1.useSiteData)();
24
24
  const [isDismissed, setIsDismissed] = (0, react_1.useState)(false);
25
25
  // Resolve dynamic values from siteData if present
26
- const announcementData = siteData?.site?.announcement;
27
- const isEnabled = announcementData?.enabled !== false;
26
+ const announcementData = siteData?.content?.site?.announcement || siteData?.site?.announcement;
27
+ const isEnabled = announcementData ? announcementData?.enabled !== false : true;
28
28
  const text = announcementData?.text || defaultText;
29
29
  const badge = announcementData?.badge || defaultBadge;
30
30
  const linkText = announcementData?.linkText || defaultLinkText;
31
31
  const linkUrl = announcementData?.linkUrl || defaultLinkUrl;
32
+ const activeFieldPath = announcementData ? fieldPath : undefined;
32
33
  if (isDismissed || !isEnabled) {
33
34
  return null;
34
35
  }
@@ -85,7 +86,7 @@ function EditableAnnouncementBar({ fieldPath = 'site.announcement', defaultText
85
86
  borderRadius: '0.25rem',
86
87
  transition: 'color 0.15s ease',
87
88
  };
88
- return ((0, jsx_runtime_1.jsxs)("div", { className: `deneb-announcement-bar relative z-40 bg-slate-900 text-white text-xs md:text-sm font-medium px-4 py-2 flex items-center justify-center text-center ${className}`, style: containerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center flex-wrap gap-1.5 max-w-7xl mx-auto pr-6", children: [badge && ((0, jsx_runtime_1.jsx)("span", { className: "deneb-announcement-badge bg-white/20 text-white px-2 py-0.5 rounded-full text-[10px] font-bold tracking-wider uppercase inline-flex items-center", style: badgeStyle, "data-preview-field-path": fieldPath ? `${fieldPath}.badge` : undefined, children: badge })), (0, jsx_runtime_1.jsx)("span", { className: "deneb-announcement-text", "data-preview-field-path": fieldPath ? `${fieldPath}.text` : undefined, children: text }), linkText && linkUrl && ((0, jsx_runtime_1.jsxs)("a", { href: linkUrl, className: "deneb-announcement-link underline font-semibold text-white hover:opacity-90 transition-opacity ml-1 inline-flex items-center gap-1", style: linkStyle, "data-preview-field-path": fieldPath ? `${fieldPath}.linkText` : undefined, children: [linkText, " \u2192"] }))] }), dismissible && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setIsDismissed(true), style: closeButtonStyle, "aria-label": "Dismiss announcement", className: "hover:text-white transition-colors", "data-preview-static": "announcement-dismiss", children: (0, jsx_runtime_1.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), (0, jsx_runtime_1.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }))] }));
89
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `deneb-announcement-bar relative z-40 bg-slate-900 text-white text-xs md:text-sm font-medium px-4 py-2 flex items-center justify-center text-center ${className}`, style: containerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-center flex-wrap gap-1.5 max-w-7xl mx-auto pr-6", children: [badge && ((0, jsx_runtime_1.jsx)("span", { className: "deneb-announcement-badge bg-white/20 text-white px-2 py-0.5 rounded-full text-[10px] font-bold tracking-wider uppercase inline-flex items-center", style: badgeStyle, "data-preview-field-path": activeFieldPath ? `${activeFieldPath}.badge` : undefined, children: badge })), (0, jsx_runtime_1.jsx)("span", { className: "deneb-announcement-text", "data-preview-field-path": activeFieldPath ? `${activeFieldPath}.text` : undefined, children: text }), linkText && linkUrl && ((0, jsx_runtime_1.jsxs)("a", { href: linkUrl, className: "deneb-announcement-link underline font-semibold text-white hover:opacity-90 transition-opacity ml-1 inline-flex items-center gap-1", style: linkStyle, "data-preview-field-path": activeFieldPath ? `${activeFieldPath}.linkText` : undefined, children: [linkText, " \u2192"] }))] }), dismissible && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setIsDismissed(true), style: closeButtonStyle, "aria-label": "Dismiss announcement", className: "hover:text-white transition-colors", "data-preview-static": "announcement-dismiss", children: (0, jsx_runtime_1.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), (0, jsx_runtime_1.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }))] }));
89
90
  }
90
91
  // Canonical alias
91
92
  exports.AnnouncementBar = EditableAnnouncementBar;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EditableImage = EditableImage;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const utils_1 = require("./utils");
5
6
  const ASPECT_MAP = {
6
7
  square: '1 / 1',
7
8
  '1/1': '1 / 1',
@@ -34,5 +35,7 @@ function EditableImage({ id, 'data-preview-field-path': previewFieldPath, src, f
34
35
  ...(hoverZoom ? { transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)' } : {}),
35
36
  ...style,
36
37
  };
37
- return ((0, jsx_runtime_1.jsx)("img", { "data-preview-field-path": path, src: src || fallbackSrc, alt: alt, style: combinedStyle, className: `editable-image ${hoverZoom ? 'hover:scale-105' : ''} ${className}`.trim(), ...props }));
38
+ const imageSrc = src || fallbackSrc;
39
+ const finalSrc = typeof imageSrc === 'string' && imageSrc.startsWith('/') ? (0, utils_1.withBasePath)(imageSrc) : imageSrc;
40
+ return ((0, jsx_runtime_1.jsx)("img", { "data-preview-field-path": path, src: finalSrc, alt: alt, style: combinedStyle, className: `editable-image ${hoverZoom ? 'hover:scale-105' : ''} ${className}`.trim(), ...props }));
38
41
  }
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ export interface ProductDetailItem {
3
+ id?: string;
4
+ name?: string;
5
+ title?: string;
6
+ price?: string | number;
7
+ originalPrice?: string | number;
8
+ description?: string;
9
+ badge?: string;
10
+ featuredImage?: string;
11
+ imageUrl?: string;
12
+ gallery?: string[];
13
+ addToSelectionLabel?: string;
14
+ specsTitle?: string;
15
+ specs?: Array<{
16
+ label: string;
17
+ value: string;
18
+ }>;
19
+ shippingTitle?: string;
20
+ shippingSummary?: string;
21
+ shippingReturns?: string;
22
+ relatedLabel?: string;
23
+ relatedTitle?: string;
24
+ whatsappNumber?: string;
25
+ whatsappMessage?: string;
26
+ [key: string]: unknown;
27
+ }
28
+ export interface EditableProductDetailProps extends React.HTMLAttributes<HTMLElement> {
29
+ /**
30
+ * Field path prefix for live Fivora Visual Editing synchronization (e.g. "product").
31
+ */
32
+ sectionPath?: string;
33
+ /**
34
+ * Product data object.
35
+ */
36
+ product: ProductDetailItem;
37
+ /**
38
+ * Available sizes (e.g. ['40', '41', '42', '43', '44', '45', '46']).
39
+ */
40
+ sizes?: string[];
41
+ /**
42
+ * Available color swatches.
43
+ */
44
+ colors?: Array<{
45
+ name: string;
46
+ hex: string;
47
+ }>;
48
+ /**
49
+ * Callback on adding product to selection or cart.
50
+ */
51
+ onAddToSelection?: (product: ProductDetailItem, selectedSize?: string, selectedColor?: string) => void;
52
+ /**
53
+ * Optional custom WhatsApp order URL.
54
+ */
55
+ whatsappUrl?: string;
56
+ className?: string;
57
+ }
58
+ /**
59
+ * EditableProductDetail is an elite, fully calibrated Single Product View component
60
+ * engineered for high-conversion commerce and 100% compliant with Fivora Visual Editing.
61
+ *
62
+ * Features:
63
+ * - Multi-image gallery with active thumbnail selector and basePath resolution
64
+ * - Floating badges with conditional rendering (zero hidden marker contract violations)
65
+ * - Live synchronized field paths for Title, Price, Description, CTAs, and Policies
66
+ * - Interactive size & color pickers
67
+ * - Direct WhatsApp Click-to-Order integration
68
+ * - Specifications and Shipping & Returns tabs
69
+ *
70
+ * Created by Chamika Gayashan & Induranga Kawishwara
71
+ */
72
+ export declare function EditableProductDetail({ sectionPath, product, sizes, colors, onAddToSelection, whatsappUrl, className, style, ...props }: EditableProductDetailProps): React.JSX.Element;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditableProductDetail = EditableProductDetail;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const utils_1 = require("./utils");
7
+ const urls_1 = require("./utils/urls");
8
+ /**
9
+ * EditableProductDetail is an elite, fully calibrated Single Product View component
10
+ * engineered for high-conversion commerce and 100% compliant with Fivora Visual Editing.
11
+ *
12
+ * Features:
13
+ * - Multi-image gallery with active thumbnail selector and basePath resolution
14
+ * - Floating badges with conditional rendering (zero hidden marker contract violations)
15
+ * - Live synchronized field paths for Title, Price, Description, CTAs, and Policies
16
+ * - Interactive size & color pickers
17
+ * - Direct WhatsApp Click-to-Order integration
18
+ * - Specifications and Shipping & Returns tabs
19
+ *
20
+ * Created by Chamika Gayashan & Induranga Kawishwara
21
+ */
22
+ function EditableProductDetail({ sectionPath = 'product', product, sizes = ['40', '41', '42', '43', '44', '45', '46'], colors = [
23
+ { name: 'Ivory', hex: '#F0EFEB' },
24
+ { name: 'Obsidian', hex: '#1C2541' },
25
+ ], onAddToSelection, whatsappUrl, className = '', style, ...props }) {
26
+ const images = (product.gallery && product.gallery.length > 0)
27
+ ? product.gallery
28
+ : [product.featuredImage || product.imageUrl || '/products/vanta-aero-x.jpg'];
29
+ const [activeImage, setActiveImage] = (0, react_1.useState)(images[0] || '');
30
+ const [selectedSize, setSelectedSize] = (0, react_1.useState)(sizes[0] || '');
31
+ const [selectedColor, setSelectedColor] = (0, react_1.useState)(colors[0]?.name || '');
32
+ const [activeTab, setActiveTab] = (0, react_1.useState)('specs');
33
+ const name = String(product.name || product.title || 'Product Title');
34
+ const price = product.price !== undefined ? String(product.price) : 'LKR 0';
35
+ const description = String(product.description || '');
36
+ const badge = String(product.badge || '');
37
+ const addToSelectionLabel = String(product.addToSelectionLabel || 'Add to Selection');
38
+ const specsTitle = String(product.specsTitle || 'Specifications');
39
+ const shippingTitle = String(product.shippingTitle || 'Shipping & Returns');
40
+ const shippingSummary = String(product.shippingSummary || 'Free Islandwide Delivery within 2-3 business days. Cash on delivery available.');
41
+ const shippingReturns = String(product.shippingReturns || '14-day hassle-free exchanges for unworn items in original packaging.');
42
+ const resolvedWhatsappUrl = whatsappUrl || (product.whatsappNumber
43
+ ? (0, urls_1.createWhatsAppUrl)(product.whatsappNumber, product.whatsappMessage || `Hi, I would like to order ${name} (${selectedSize}, ${selectedColor}) - ${price}`)
44
+ : '');
45
+ return ((0, jsx_runtime_1.jsx)("section", { "data-preview-page-key": sectionPath, className: `editable-product-detail max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-start", children: [(0, jsx_runtime_1.jsxs)("div", { className: "lg:col-span-7 flex flex-col-reverse md:flex-row gap-4", children: [images.length > 1 && ((0, jsx_runtime_1.jsx)("div", { className: "flex md:flex-col gap-3 overflow-x-auto md:overflow-y-auto max-h-[580px] pb-2 md:pb-0 scrollbar-none", children: images.map((img, idx) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveImage(img), className: `w-20 h-20 sm:w-24 sm:h-24 rounded-2xl overflow-hidden border-2 transition-all duration-200 flex-shrink-0 bg-slate-900/40 p-2 ${activeImage === img
46
+ ? 'border-brand-primary scale-98 shadow-md'
47
+ : 'border-slate-800/80 opacity-60 hover:opacity-100 hover:border-slate-700'}`, children: (0, jsx_runtime_1.jsx)("img", { src: (0, utils_1.withBasePath)(img), alt: "", className: "w-full h-full object-contain" }) }, idx))) })), (0, jsx_runtime_1.jsxs)("div", { className: "relative flex-1 aspect-square rounded-3xl bg-gradient-to-b from-slate-900/50 to-slate-950/80 border border-slate-800/80 p-8 flex items-center justify-center overflow-hidden group", children: [badge && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.badge`, className: "absolute top-6 left-6 z-10 px-3.5 py-1 text-xs font-black tracking-wider uppercase rounded-full bg-lime-400 text-slate-950 shadow-lg", children: badge })), (0, jsx_runtime_1.jsx)("img", { "data-preview-field-path": `${sectionPath}.featuredImage`, src: (0, utils_1.withBasePath)(activeImage), alt: name, className: "w-full h-full object-contain transition-transform duration-500 group-hover:scale-105" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "lg:col-span-5 flex flex-col justify-between", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { "data-preview-field-path": `${sectionPath}.name`, className: "text-3xl sm:text-4xl font-extrabold tracking-tight text-white leading-tight", children: name }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-4 flex items-baseline gap-3", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.price`, className: "text-3xl font-black text-lime-400 tracking-tight", children: price }), product.originalPrice ? ((0, jsx_runtime_1.jsx)("span", { className: "text-lg font-medium text-slate-500 line-through", children: String(product.originalPrice) })) : null] }), description && ((0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `${sectionPath}.description`, className: "mt-6 text-base text-slate-300 leading-relaxed", children: description })), colors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-8", children: [(0, jsx_runtime_1.jsxs)("span", { className: "block text-xs font-bold uppercase tracking-wider text-slate-400 mb-3", children: ["Color \u2014 ", (0, jsx_runtime_1.jsx)("span", { className: "text-white", children: selectedColor })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex gap-3", children: colors.map((c) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSelectedColor(c.name), className: `w-9 h-9 rounded-full border-2 transition-all p-0.5 ${selectedColor === c.name
48
+ ? 'border-lime-400 ring-2 ring-lime-400/30 scale-105'
49
+ : 'border-slate-700 hover:border-slate-500'}`, style: { backgroundColor: c.hex }, title: c.name, "aria-label": c.name }, c.name))) })] })), sizes.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-8", children: [(0, jsx_runtime_1.jsx)("span", { className: "block text-xs font-bold uppercase tracking-wider text-slate-400 mb-3", children: "Select Size" }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-4 sm:grid-cols-7 gap-2", children: sizes.map((s) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSelectedSize(s), className: `py-2.5 rounded-xl font-bold text-sm transition-all duration-150 ${selectedSize === s
50
+ ? 'bg-white text-slate-950 shadow-md font-extrabold scale-102'
51
+ : 'bg-slate-900/60 text-slate-300 border border-slate-800 hover:bg-slate-800 hover:text-white'}`, children: s }, s))) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "mt-8 flex flex-col gap-3", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => onAddToSelection?.(product, selectedSize, selectedColor), className: "w-full py-4 px-6 rounded-2xl font-black text-sm uppercase tracking-wider bg-lime-400 text-slate-950 hover:bg-lime-300 active:scale-98 transition-all duration-150 shadow-lg shadow-lime-400/20 flex items-center justify-center gap-2", children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.addToSelectionLabel`, children: addToSelectionLabel }) }), resolvedWhatsappUrl && ((0, jsx_runtime_1.jsxs)("a", { href: resolvedWhatsappUrl, target: "_blank", rel: "noopener noreferrer", className: "w-full py-3.5 px-6 rounded-2xl font-bold text-sm bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30 border border-emerald-500/30 transition-all duration-150 flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5 fill-current", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { d: "M17.472 14.382c-.301-.15-1.78-.879-2.056-.98-.276-.1-.477-.15-.678.15-.201.3-.78 0.98-.956 1.18-.175.2-.351.226-.652.075-.301-.15-1.272-.469-2.423-1.496-.896-.799-1.501-1.787-1.677-2.088-.175-.301-.019-.464.132-.614.136-.135.301-.351.452-.527.15-.176.201-.301.301-.502.1-.2.05-.376-.025-.527-.075-.15-.678-1.635-.93-2.242-.244-.592-.493-.512-.678-.521-.175-.009-.376-.009-.577-.009-.201 0-.527.075-.803.376-.276.301-1.054 1.03-1.054 2.512 0 1.482 1.079 2.912 1.23 3.113.15.201 2.124 3.243 5.145 4.548.718.311 1.279.497 1.716.636.722.23 1.378.197 1.898.12.579-.087 1.78-.728 2.03-1.431.251-.703.251-1.305.176-1.431-.075-.126-.276-.201-.577-.351zM12.004 2C6.48 2 2 6.48 2 12c0 1.82.49 3.52 1.34 4.99L2 22l5.14-1.35c1.42.78 3.04 1.22 4.86 1.22 5.52 0 10-4.48 10-10S17.524 2 12.004 2z" }) }), (0, jsx_runtime_1.jsx)("span", { children: "Order Directly via WhatsApp" })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-10 border-t border-slate-800/80 pt-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-6 border-b border-slate-800 pb-3 mb-4", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab('specs'), className: `text-sm font-bold tracking-wide transition-colors ${activeTab === 'specs' ? 'text-lime-400' : 'text-slate-400 hover:text-slate-200'}`, children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.specsTitle`, children: specsTitle }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab('shipping'), className: `text-sm font-bold tracking-wide transition-colors ${activeTab === 'shipping' ? 'text-lime-400' : 'text-slate-400 hover:text-slate-200'}`, children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.shippingTitle`, children: shippingTitle }) })] }), activeTab === 'specs' ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-3", children: product.specs && product.specs.length > 0 ? (product.specs.map((spec, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-xs py-1 border-b border-slate-900", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-slate-400 font-medium", children: spec.label }), (0, jsx_runtime_1.jsx)("span", { className: "text-slate-200 font-semibold", children: spec.value })] }, i)))) : ((0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-slate-400 space-y-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between py-1 border-b border-slate-800/50", children: [(0, jsx_runtime_1.jsx)("span", { children: "Upper Material" }), (0, jsx_runtime_1.jsx)("span", { className: "text-slate-200 font-semibold", children: "Engineered breathable mesh" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between py-1 border-b border-slate-800/50", children: [(0, jsx_runtime_1.jsx)("span", { children: "Midsole Technology" }), (0, jsx_runtime_1.jsx)("span", { className: "text-slate-200 font-semibold", children: "Dual-density responsive foam" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between py-1 border-b border-slate-800/50", children: [(0, jsx_runtime_1.jsx)("span", { children: "Outsole Grip" }), (0, jsx_runtime_1.jsx)("span", { className: "text-slate-200 font-semibold", children: "High-abrasion tactical rubber" })] })] })) })) : ((0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-slate-300 space-y-3 leading-relaxed", children: [(0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `${sectionPath}.shippingSummary`, children: shippingSummary }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `${sectionPath}.shippingReturns`, className: "text-slate-400", children: shippingReturns })] }))] })] }) })] }) }));
52
+ }
@@ -12,6 +12,7 @@ export interface ProductQuickViewItem {
12
12
  inStock?: boolean;
13
13
  rating?: number;
14
14
  reviewCount?: number;
15
+ [key: string]: unknown;
15
16
  }
16
17
  export interface ProductQuickViewProps {
17
18
  product: ProductQuickViewItem | null;
@@ -19,19 +20,20 @@ export interface ProductQuickViewProps {
19
20
  onClose: () => void;
20
21
  onAddToCart?: (product: ProductQuickViewItem, quantity: number) => void;
21
22
  className?: string;
23
+ /**
24
+ * Field path prefix for live visual editing synchronization in Fivora Lab.
25
+ * e.g. "home.product1" or "product"
26
+ */
27
+ itemPath?: string;
28
+ addToCartLabel?: string;
22
29
  }
23
30
  /**
24
31
  * DENEB UI — Product Quick View Modal
25
32
  *
26
33
  * High-converting instant lightbox inspection modal for products.
27
34
  * Enables shoppers to inspect details and buy without leaving page flow.
28
- *
29
- * Features:
30
- * - Image gallery thumbnail preview
31
- * - Live quantity counter with bounds protection
32
- * - Accessible keyboard interaction (Esc key to close)
33
- * - Backdrop blur with smooth animation
35
+ * Fully synchronized with Fivora Visual Editing contract.
34
36
  *
35
37
  * Created by Chamika Gayashan & Induranga Kawishwara
36
38
  */
37
- export declare function ProductQuickView({ product, isOpen, onClose, onAddToCart, className, }: ProductQuickViewProps): React.JSX.Element | null;
39
+ export declare function ProductQuickView({ product, isOpen, onClose, onAddToCart, className, itemPath, addToCartLabel, }: ProductQuickViewProps): React.JSX.Element | null;
@@ -3,21 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ProductQuickView = ProductQuickView;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
+ const utils_1 = require("./utils");
6
7
  /**
7
8
  * DENEB UI — Product Quick View Modal
8
9
  *
9
10
  * High-converting instant lightbox inspection modal for products.
10
11
  * Enables shoppers to inspect details and buy without leaving page flow.
11
- *
12
- * Features:
13
- * - Image gallery thumbnail preview
14
- * - Live quantity counter with bounds protection
15
- * - Accessible keyboard interaction (Esc key to close)
16
- * - Backdrop blur with smooth animation
12
+ * Fully synchronized with Fivora Visual Editing contract.
17
13
  *
18
14
  * Created by Chamika Gayashan & Induranga Kawishwara
19
15
  */
20
- function ProductQuickView({ product, isOpen, onClose, onAddToCart, className = '', }) {
16
+ function ProductQuickView({ product, isOpen, onClose, onAddToCart, className = '', itemPath, addToCartLabel = 'Add to Selection', }) {
21
17
  const [quantity, setQuantity] = (0, react_1.useState)(1);
22
18
  const [selectedImage, setSelectedImage] = (0, react_1.useState)('');
23
19
  (0, react_1.useEffect)(() => {
@@ -56,8 +52,8 @@ function ProductQuickView({ product, isOpen, onClose, onAddToCart, className = '
56
52
  return ((0, jsx_runtime_1.jsx)("div", { role: "dialog", "aria-modal": "true", className: `fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-black/60 backdrop-blur-sm transition-opacity duration-200 ${className}`, onClick: (e) => {
57
53
  if (e.target === e.currentTarget)
58
54
  onClose();
59
- }, children: (0, jsx_runtime_1.jsxs)("div", { className: "relative w-full max-w-2xl overflow-hidden rounded-3xl bg-white shadow-2xl border border-slate-100 flex flex-col md:flex-row max-h-[90vh]", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, className: "absolute top-4 right-4 z-10 p-2 text-slate-400 hover:text-slate-700 bg-white/80 hover:bg-white rounded-full shadow-sm backdrop-blur transition-colors", "aria-label": "Close modal", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "md:w-1/2 p-6 flex flex-col items-center justify-center bg-slate-50 border-b md:border-b-0 md:border-r border-slate-100", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative w-full aspect-square rounded-2xl overflow-hidden bg-white shadow-sm flex items-center justify-center", children: [product.badge && ((0, jsx_runtime_1.jsx)("span", { className: "absolute top-3 left-3 px-2.5 py-1 text-xs font-bold text-white bg-red-500 rounded-lg shadow-sm", children: product.badge })), selectedImage ? ((0, jsx_runtime_1.jsx)("img", { src: selectedImage, alt: product.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-slate-300 text-sm", children: "No image available" }))] }), images.length > 1 && ((0, jsx_runtime_1.jsx)("div", { className: "flex gap-2 mt-4 overflow-x-auto max-w-full pb-1", children: images.map((img, idx) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSelectedImage(img), className: `w-12 h-12 rounded-lg overflow-hidden border-2 transition-all flex-shrink-0 ${selectedImage === img ? 'border-primary shadow-sm' : 'border-slate-200 opacity-60 hover:opacity-100'}`, children: (0, jsx_runtime_1.jsx)("img", { src: img, alt: "", className: "w-full h-full object-cover" }) }, idx))) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "md:w-1/2 p-6 flex flex-col justify-between overflow-y-auto", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold ${product.inStock !== false ? 'bg-emerald-50 text-emerald-700' : 'bg-rose-50 text-rose-700'}`, children: product.inStock !== false ? 'In Stock' : 'Out of Stock' }), product.rating && ((0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-amber-500 flex items-center gap-1 font-medium", children: ["\u2605 ", product.rating, " ", product.reviewCount ? `(${product.reviewCount})` : ''] }))] }), (0, jsx_runtime_1.jsx)("h3", { className: "text-xl font-bold text-slate-900 leading-tight", children: product.title }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-3 flex items-baseline gap-2", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-2xl font-black text-slate-900", children: [currency, product.price] }), product.originalPrice && ((0, jsx_runtime_1.jsxs)("span", { className: "text-sm font-semibold text-slate-400 line-through", children: [currency, product.originalPrice] }))] }), product.description && ((0, jsx_runtime_1.jsx)("p", { className: "mt-4 text-sm text-slate-600 leading-relaxed", children: product.description }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-6 pt-6 border-t border-slate-100", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-slate-700", children: "Quantity" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center rounded-xl border border-slate-200 bg-slate-50 p-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setQuantity((q) => Math.max(1, q - 1)), className: "w-8 h-8 flex items-center justify-center rounded-lg hover:bg-white text-slate-600 font-bold transition-colors", children: "-" }), (0, jsx_runtime_1.jsx)("span", { className: "w-10 text-center font-bold text-sm text-slate-800", children: quantity }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setQuantity((q) => q + 1), className: "w-8 h-8 flex items-center justify-center rounded-lg hover:bg-white text-slate-600 font-bold transition-colors", children: "+" })] })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", disabled: product.inStock === false, onClick: handleAddToCart, className: "w-full py-3.5 px-4 rounded-xl font-bold text-sm text-white shadow-lg transition-all duration-200 active:scale-98 disabled:opacity-50 disabled:cursor-not-allowed hover:brightness-110 flex items-center justify-center gap-2", style: {
55
+ }, children: (0, jsx_runtime_1.jsxs)("div", { className: "relative w-full max-w-2xl overflow-hidden rounded-3xl bg-white shadow-2xl border border-slate-100 flex flex-col md:flex-row max-h-[90vh]", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, className: "absolute top-4 right-4 z-10 p-2 text-slate-400 hover:text-slate-700 bg-white/80 hover:bg-white rounded-full shadow-sm backdrop-blur transition-colors", "aria-label": "Close modal", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "md:w-1/2 p-6 flex flex-col items-center justify-center bg-slate-50 border-b md:border-b-0 md:border-r border-slate-100", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative w-full aspect-square rounded-2xl overflow-hidden bg-white shadow-sm flex items-center justify-center", children: [product.badge && ((0, jsx_runtime_1.jsx)("span", { ...(itemPath ? { 'data-preview-field-path': `${itemPath}.badge` } : {}), className: "absolute top-3 left-3 px-2.5 py-1 text-xs font-bold text-white bg-red-500 rounded-lg shadow-sm", children: product.badge })), selectedImage ? ((0, jsx_runtime_1.jsx)("img", { ...(itemPath ? { 'data-preview-field-path': `${itemPath}.image` } : {}), src: (0, utils_1.withBasePath)(selectedImage), alt: product.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-slate-300 text-sm", children: "No image available" }))] }), images.length > 1 && ((0, jsx_runtime_1.jsx)("div", { className: "flex gap-2 mt-4 overflow-x-auto max-w-full pb-1", children: images.map((img, idx) => ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSelectedImage(img), className: `w-12 h-12 rounded-lg overflow-hidden border-2 transition-all flex-shrink-0 ${selectedImage === img ? 'border-primary shadow-sm' : 'border-slate-200 opacity-60 hover:opacity-100'}`, children: (0, jsx_runtime_1.jsx)("img", { src: (0, utils_1.withBasePath)(img), alt: "", className: "w-full h-full object-cover" }) }, idx))) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "md:w-1/2 p-6 flex flex-col justify-between overflow-y-auto", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold ${product.inStock !== false ? 'bg-emerald-50 text-emerald-700' : 'bg-rose-50 text-rose-700'}`, children: product.inStock !== false ? 'In Stock' : 'Out of Stock' }), product.rating && ((0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-amber-500 flex items-center gap-1 font-medium", children: ["\u2605 ", product.rating, " ", product.reviewCount ? `(${product.reviewCount})` : ''] }))] }), (0, jsx_runtime_1.jsx)("h3", { ...(itemPath ? { 'data-preview-field-path': `${itemPath}.name` } : {}), className: "text-xl font-bold text-slate-900 leading-tight", children: product.title }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-3 flex items-baseline gap-2", children: [(0, jsx_runtime_1.jsxs)("span", { ...(itemPath ? { 'data-preview-field-path': `${itemPath}.price` } : {}), className: "text-2xl font-black text-slate-900", children: [currency, product.price] }), product.originalPrice && ((0, jsx_runtime_1.jsxs)("span", { className: "text-sm font-semibold text-slate-400 line-through", children: [currency, product.originalPrice] }))] }), product.description && ((0, jsx_runtime_1.jsx)("p", { ...(itemPath ? { 'data-preview-field-path': `${itemPath}.description` } : {}), className: "mt-4 text-sm text-slate-600 leading-relaxed", children: product.description }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-6 pt-6 border-t border-slate-100", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-slate-700", children: "Quantity" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center rounded-xl border border-slate-200 bg-slate-50 p-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setQuantity((q) => Math.max(1, q - 1)), className: "w-8 h-8 flex items-center justify-center rounded-lg hover:bg-white text-slate-600 font-bold transition-colors", children: "-" }), (0, jsx_runtime_1.jsx)("span", { className: "w-10 text-center font-bold text-sm text-slate-800", children: quantity }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setQuantity((q) => q + 1), className: "w-8 h-8 flex items-center justify-center rounded-lg hover:bg-white text-slate-600 font-bold transition-colors", children: "+" })] })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", disabled: product.inStock === false, onClick: handleAddToCart, className: "w-full py-3.5 px-4 rounded-xl font-bold text-sm text-white shadow-lg transition-all duration-200 active:scale-98 disabled:opacity-50 disabled:cursor-not-allowed hover:brightness-110 flex items-center justify-center gap-2", style: {
60
56
  backgroundColor: 'var(--color-primary, #0f172a)',
61
57
  boxShadow: '0 4px 14px rgba(15, 23, 42, 0.25)',
62
- }, children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }), "Add to Cart"] })] })] })] }) }));
58
+ }, children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }), (0, jsx_runtime_1.jsx)("span", { ...(itemPath ? { 'data-preview-field-path': `${itemPath}.addToSelectionLabel` } : {}), children: addToCartLabel })] })] })] })] }) }));
63
59
  }
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from './EditableBox';
9
9
  export * from './EditableGrid';
10
10
  export * from './EditableSection';
11
11
  export * from './EditableProductCard';
12
+ export * from './EditableProductDetail';
12
13
  export * from './EditableServiceCard';
13
14
  export * from './EditableCard';
14
15
  export * from './EditablePricingCard';
@@ -47,6 +48,7 @@ export { EditableSection as Section } from './EditableSection';
47
48
  export { EditableBox as Box } from './EditableBox';
48
49
  export { EditableList as List } from './EditableList';
49
50
  export { EditableProductCard as ProductCard } from './EditableProductCard';
51
+ export { EditableProductDetail as ProductDetail } from './EditableProductDetail';
50
52
  export { EditableServiceCard as ServiceCard } from './EditableServiceCard';
51
53
  export { EditablePricingCard as PricingCard } from './EditablePricingCard';
52
54
  export { EditableTestimonialCard as TestimonialCard } from './EditableTestimonialCard';
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.CEEG_AUTHOR = exports.CEEG_FRAMEWORK_VERSION = exports.CEEG_FRAMEWORK_NAME = exports.DENEB_AUTHOR = exports.DENEB_FRAMEWORK_VERSION = exports.DENEB_FRAMEWORK_NAME = exports.FloatingContactWidget = exports.CookieConsentBanner = exports.ProductQuickView = exports.TrustBadges = exports.StickyMobileBar = exports.CategoryPills = exports.AnnouncementBar = exports.HeroSplit = exports.Hero = exports.Footer = exports.Header = exports.Navbar = exports.ContactForm = exports.FAQ = exports.Accordion = exports.TestimonialCard = exports.PricingCard = exports.ServiceCard = exports.ProductCard = exports.List = exports.Box = exports.Section = exports.Grid = exports.Image = exports.Quote = exports.Badge = exports.Paragraph = exports.Heading = exports.Text = exports.Dialog = exports.Card = exports.Button = void 0;
21
+ exports.CEEG_AUTHOR = exports.CEEG_FRAMEWORK_VERSION = exports.CEEG_FRAMEWORK_NAME = exports.DENEB_AUTHOR = exports.DENEB_FRAMEWORK_VERSION = exports.DENEB_FRAMEWORK_NAME = exports.FloatingContactWidget = exports.CookieConsentBanner = exports.ProductQuickView = exports.TrustBadges = exports.StickyMobileBar = exports.CategoryPills = exports.AnnouncementBar = exports.HeroSplit = exports.Hero = exports.Footer = exports.Header = exports.Navbar = exports.ContactForm = exports.FAQ = exports.Accordion = exports.TestimonialCard = exports.PricingCard = exports.ServiceCard = exports.ProductDetail = exports.ProductCard = exports.List = exports.Box = exports.Section = exports.Grid = exports.Image = exports.Quote = exports.Badge = exports.Paragraph = exports.Heading = exports.Text = exports.Dialog = exports.Card = exports.Button = void 0;
22
22
  __exportStar(require("./EditableText"), exports);
23
23
  __exportStar(require("./EditableImage"), exports);
24
24
  __exportStar(require("./EditableList"), exports);
@@ -26,6 +26,7 @@ __exportStar(require("./EditableBox"), exports);
26
26
  __exportStar(require("./EditableGrid"), exports);
27
27
  __exportStar(require("./EditableSection"), exports);
28
28
  __exportStar(require("./EditableProductCard"), exports);
29
+ __exportStar(require("./EditableProductDetail"), exports);
29
30
  __exportStar(require("./EditableServiceCard"), exports);
30
31
  __exportStar(require("./EditableCard"), exports);
31
32
  __exportStar(require("./EditablePricingCard"), exports);
@@ -80,6 +81,8 @@ var EditableList_1 = require("./EditableList");
80
81
  Object.defineProperty(exports, "List", { enumerable: true, get: function () { return EditableList_1.EditableList; } });
81
82
  var EditableProductCard_1 = require("./EditableProductCard");
82
83
  Object.defineProperty(exports, "ProductCard", { enumerable: true, get: function () { return EditableProductCard_1.EditableProductCard; } });
84
+ var EditableProductDetail_1 = require("./EditableProductDetail");
85
+ Object.defineProperty(exports, "ProductDetail", { enumerable: true, get: function () { return EditableProductDetail_1.EditableProductDetail; } });
83
86
  var EditableServiceCard_1 = require("./EditableServiceCard");
84
87
  Object.defineProperty(exports, "ServiceCard", { enumerable: true, get: function () { return EditableServiceCard_1.EditableServiceCard; } });
85
88
  var EditablePricingCard_1 = require("./EditablePricingCard");
@@ -30,3 +30,11 @@ export declare function createMapUrl(location?: MapLocationInput | string | null
30
30
  * Validates if a string is a safe, valid external link (prevents script injection).
31
31
  */
32
32
  export declare function isSafeExternalLink(url?: string | null): boolean;
33
+ /**
34
+ * Resolves static assets and routes with subpath prefix support (e.g. Fivora preview lab).
35
+ */
36
+ export declare function withBasePath(value?: string | null): string;
37
+ /**
38
+ * Resolves standard page route from pageKey.
39
+ */
40
+ export declare function pageRoute(pageKey: string): string;
@@ -9,6 +9,8 @@ exports.createPhoneUrl = createPhoneUrl;
9
9
  exports.createEmailUrl = createEmailUrl;
10
10
  exports.createMapUrl = createMapUrl;
11
11
  exports.isSafeExternalLink = isSafeExternalLink;
12
+ exports.withBasePath = withBasePath;
13
+ exports.pageRoute = pageRoute;
12
14
  /**
13
15
  * Creates a valid WhatsApp click-to-chat URL.
14
16
  * Automatically cleans spaces, dashes, and special characters from the phone number.
@@ -89,3 +91,20 @@ function isSafeExternalLink(url) {
89
91
  trimmed.startsWith('/') ||
90
92
  trimmed.startsWith('#'));
91
93
  }
94
+ /**
95
+ * Resolves static assets and routes with subpath prefix support (e.g. Fivora preview lab).
96
+ */
97
+ function withBasePath(value) {
98
+ const url = value?.trim() ?? '';
99
+ if (!url || /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url))
100
+ return url;
101
+ const basePath = (typeof process !== 'undefined' ? process.env?.NEXT_PUBLIC_SITE_BASE_PATH ?? '' : '').replace(/\/$/, '');
102
+ const path = url.startsWith('/') ? url : `/${url}`;
103
+ return `${basePath}${path}`;
104
+ }
105
+ /**
106
+ * Resolves standard page route from pageKey.
107
+ */
108
+ function pageRoute(pageKey) {
109
+ return pageKey === 'home' ? '/' : `/${pageKey}`;
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "DENEB UI - The premier visual-first React component framework for the modern web. Created by Chamika Gayashan & Induranga Kawishwara.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",