@deneb-ui/ui 2.0.14 → 2.0.16

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;
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ export interface CustomerReviewItem {
3
+ id?: string | number;
4
+ author: string;
5
+ rating: number;
6
+ date?: string;
7
+ comment: string;
8
+ verified?: boolean;
9
+ productName?: string;
10
+ helpfulCount?: number;
11
+ [key: string]: unknown;
12
+ }
13
+ export interface EditableCustomerReviewsProps extends React.HTMLAttributes<HTMLElement> {
14
+ /**
15
+ * Field path prefix for live Fivora Visual Editing synchronization (e.g. "home").
16
+ */
17
+ sectionPath?: string;
18
+ /**
19
+ * Title of the reviews section.
20
+ */
21
+ title?: string;
22
+ /**
23
+ * Subtitle tag.
24
+ */
25
+ subtitle?: string;
26
+ /**
27
+ * Average score (e.g. 4.9).
28
+ */
29
+ averageRating?: number | string;
30
+ /**
31
+ * Total review count display.
32
+ */
33
+ totalReviews?: number | string;
34
+ /**
35
+ * Array of reviews.
36
+ */
37
+ reviews?: CustomerReviewItem[];
38
+ className?: string;
39
+ }
40
+ /**
41
+ * EditableCustomerReviews is an elite social-proof component featuring
42
+ * star breakdowns, verified buyer authentication badges, helpful counters,
43
+ * and live Fivora visual editing synchronization.
44
+ *
45
+ * Created by Chamika Gayashan & Induranga Kawishwara
46
+ */
47
+ export declare function EditableCustomerReviews({ sectionPath, title, subtitle, averageRating, totalReviews, reviews, className, style, ...props }: EditableCustomerReviewsProps): React.JSX.Element;
48
+ export declare const CustomerReviews: typeof EditableCustomerReviews;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CustomerReviews = void 0;
4
+ exports.EditableCustomerReviews = EditableCustomerReviews;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const DEFAULT_REVIEWS = [
8
+ {
9
+ id: 1,
10
+ author: 'Liam Vance',
11
+ rating: 5,
12
+ date: '2 days ago',
13
+ comment: 'The cushioning and responsiveness are unbelievable. Best performance shoe I have owned for tempo runs.',
14
+ verified: true,
15
+ productName: 'VANTA Aero X',
16
+ helpfulCount: 14,
17
+ },
18
+ {
19
+ id: 2,
20
+ author: 'Elena Rostova',
21
+ rating: 5,
22
+ date: '1 week ago',
23
+ comment: 'Sleek design, fits true to size, and the breathability is noticeable on 10k morning runs. Highly recommended!',
24
+ verified: true,
25
+ productName: 'VANTA Flux 01',
26
+ helpfulCount: 9,
27
+ },
28
+ {
29
+ id: 3,
30
+ author: 'Marcus Chen',
31
+ rating: 4,
32
+ date: '2 weeks ago',
33
+ comment: 'Exceptional build quality and premium materials. Fast shipping and very responsive WhatsApp support.',
34
+ verified: true,
35
+ productName: 'VANTA Stealth Pro',
36
+ helpfulCount: 6,
37
+ },
38
+ ];
39
+ /**
40
+ * EditableCustomerReviews is an elite social-proof component featuring
41
+ * star breakdowns, verified buyer authentication badges, helpful counters,
42
+ * and live Fivora visual editing synchronization.
43
+ *
44
+ * Created by Chamika Gayashan & Induranga Kawishwara
45
+ */
46
+ function EditableCustomerReviews({ sectionPath = 'home', title = 'Loved by Athletes & Runners Worldwide', subtitle = 'Verified Customer Reviews', averageRating = '4.9', totalReviews = '1,420+', reviews = DEFAULT_REVIEWS, className = '', style, ...props }) {
47
+ const [selectedStarFilter, setSelectedStarFilter] = (0, react_1.useState)(null);
48
+ const filteredReviews = (0, react_1.useMemo)(() => {
49
+ if (selectedStarFilter === null)
50
+ return reviews;
51
+ return reviews.filter((r) => r.rating === selectedStarFilter);
52
+ }, [reviews, selectedStarFilter]);
53
+ return ((0, jsx_runtime_1.jsxs)("section", { "data-preview-page-key": sectionPath, className: `editable-customer-reviews max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 ${className}`.trim(), style: style, ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center max-w-2xl mx-auto mb-12", children: [subtitle && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.reviewsSubtitle`, className: "text-xs font-black tracking-widest uppercase text-lime-400 block mb-2", children: subtitle })), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `${sectionPath}.reviewsTitle`, className: "text-3xl sm:text-4xl font-extrabold tracking-tight text-white leading-tight", children: title }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-4 flex items-center justify-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex text-amber-400 text-lg", children: '★★★★★' }), (0, jsx_runtime_1.jsxs)("span", { className: "text-white font-extrabold text-lg", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.reviewsAverage`, children: averageRating }), " / 5.0"] }), (0, jsx_runtime_1.jsxs)("span", { className: "text-slate-400 text-sm", children: ["(", (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.reviewsCount`, children: totalReviews }), " reviews)"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-center gap-2 mb-8", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSelectedStarFilter(null), className: `px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all ${selectedStarFilter === null
54
+ ? 'bg-lime-400 text-slate-950 font-extrabold shadow-sm'
55
+ : 'bg-slate-900/60 text-slate-400 hover:text-white border border-slate-800'}`, children: "All Reviews" }), [5, 4, 3].map((stars) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setSelectedStarFilter(stars), className: `px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-1 ${selectedStarFilter === stars
56
+ ? 'bg-lime-400 text-slate-950 font-extrabold shadow-sm'
57
+ : 'bg-slate-900/60 text-slate-400 hover:text-white border border-slate-800'}`, children: [(0, jsx_runtime_1.jsxs)("span", { children: [stars, " Stars"] }), (0, jsx_runtime_1.jsx)("span", { className: "text-amber-400", children: "\u2605" })] }, stars)))] }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-6", children: filteredReviews.map((review, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "p-6 rounded-2xl bg-gradient-to-b from-slate-900/60 to-slate-950/80 border border-slate-800/80 flex flex-col justify-between hover:border-slate-700 transition-all duration-200", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex text-amber-400 text-sm", children: Array.from({ length: review.rating }).map((_, idx) => ((0, jsx_runtime_1.jsx)("span", { children: "\u2605" }, idx))) }), review.verified !== false && ((0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3 h-3 fill-current", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }), "Verified Buyer"] }))] }), (0, jsx_runtime_1.jsxs)("p", { "data-preview-field-path": `${sectionPath}.review${i + 1}Comment`, className: "text-sm text-slate-300 leading-relaxed italic mb-4", children: ["\u201C", review.comment, "\u201D"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pt-4 border-t border-slate-800/60 flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.review${i + 1}Author`, className: "block text-xs font-bold text-white", children: review.author }), review.productName && ((0, jsx_runtime_1.jsxs)("span", { className: "text-[11px] text-slate-400 block", children: ["Purchased: ", review.productName] }))] }), review.date && ((0, jsx_runtime_1.jsx)("span", { className: "text-[11px] text-slate-500 font-mono", children: review.date }))] })] }, review.id || i))) })] }));
58
+ }
59
+ // Canonical alias
60
+ exports.CustomerReviews = EditableCustomerReviews;
@@ -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
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { ProductItem, ProductCardVariant } from './EditableProductCard';
3
+ export interface EditableProductGridProps extends React.HTMLAttributes<HTMLElement> {
4
+ /**
5
+ * Field path prefix for live Fivora Visual Editing synchronization (e.g. "home").
6
+ */
7
+ sectionPath?: string;
8
+ /**
9
+ * Title of the product grid section.
10
+ */
11
+ title?: string;
12
+ /**
13
+ * Subtitle or category tag for the grid section.
14
+ */
15
+ subtitle?: string;
16
+ /**
17
+ * Array of product items.
18
+ */
19
+ products: ProductItem[];
20
+ /**
21
+ * Optional category filter tabs (e.g. ['All', 'Sneakers', 'Performance', 'Lifestyle']).
22
+ */
23
+ categories?: string[];
24
+ /**
25
+ * Card visual variant: 'modern-glass' | 'classic' | 'minimal' | 'horizontal'.
26
+ */
27
+ cardVariant?: ProductCardVariant;
28
+ /**
29
+ * Responsive columns configuration.
30
+ */
31
+ columns?: {
32
+ mobile?: number;
33
+ tablet?: number;
34
+ desktop?: number;
35
+ };
36
+ /**
37
+ * Callback when a user clicks the quick view action on a card.
38
+ */
39
+ onQuickView?: (product: ProductItem, itemPath: string) => void;
40
+ /**
41
+ * Custom field path prefix for individual cards (default: "product").
42
+ * e.g. "home.product1", "home.product2", etc.
43
+ */
44
+ cardPrefix?: string;
45
+ className?: string;
46
+ }
47
+ /**
48
+ * EditableProductGrid is an elite, fully responsive e-commerce showcase grid
49
+ * with interactive category filtering, live Fivora visual editing synchronization,
50
+ * and built-in Quick View modal triggers.
51
+ *
52
+ * Created by Chamika Gayashan & Induranga Kawishwara
53
+ */
54
+ export declare function EditableProductGrid({ sectionPath, title, subtitle, products, categories, cardVariant, columns, onQuickView, cardPrefix, className, style, ...props }: EditableProductGridProps): React.JSX.Element;
55
+ export declare const ProductGrid: typeof EditableProductGrid;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProductGrid = void 0;
4
+ exports.EditableProductGrid = EditableProductGrid;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const EditableProductCard_1 = require("./EditableProductCard");
8
+ /**
9
+ * EditableProductGrid is an elite, fully responsive e-commerce showcase grid
10
+ * with interactive category filtering, live Fivora visual editing synchronization,
11
+ * and built-in Quick View modal triggers.
12
+ *
13
+ * Created by Chamika Gayashan & Induranga Kawishwara
14
+ */
15
+ function EditableProductGrid({ sectionPath = 'home', title = 'Featured Collection', subtitle = 'Just Dropped', products = [], categories = ['All'], cardVariant = 'modern-glass', columns = { mobile: 1, tablet: 2, desktop: 4 }, onQuickView, cardPrefix = 'product', className = '', style, ...props }) {
16
+ const [activeCategory, setActiveCategory] = (0, react_1.useState)(categories[0] || 'All');
17
+ const filteredProducts = (0, react_1.useMemo)(() => {
18
+ if (!activeCategory || activeCategory.toLowerCase() === 'all') {
19
+ return products;
20
+ }
21
+ return products.filter((p) => {
22
+ const cat = String(p.category || '').toLowerCase();
23
+ return cat === activeCategory.toLowerCase();
24
+ });
25
+ }, [products, activeCategory]);
26
+ // Map column config to CSS grid class names
27
+ const gridColClasses = (0, react_1.useMemo)(() => {
28
+ const m = columns.mobile || 1;
29
+ const t = columns.tablet || 2;
30
+ const d = columns.desktop || 4;
31
+ const mClass = m === 2 ? 'grid-cols-2' : 'grid-cols-1';
32
+ const tClass = t === 3 ? 'sm:grid-cols-3' : 'sm:grid-cols-2';
33
+ const dClass = d === 3 ? 'lg:grid-cols-3' : d === 2 ? 'lg:grid-cols-2' : 'lg:grid-cols-4';
34
+ return `${mClass} ${tClass} ${dClass}`;
35
+ }, [columns]);
36
+ return ((0, jsx_runtime_1.jsxs)("section", { "data-preview-page-key": sectionPath, className: `editable-product-grid max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 ${className}`.trim(), style: style, ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col md:flex-row md:items-end justify-between mb-10 gap-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [subtitle && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${sectionPath}.gridSubtitle`, className: "text-xs font-black tracking-widest uppercase text-lime-400 block mb-2", children: subtitle })), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `${sectionPath}.gridTitle`, className: "text-3xl sm:text-4xl font-extrabold tracking-tight text-white leading-tight", children: title })] }), categories.length > 1 && ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap items-center gap-2", children: categories.map((cat) => {
37
+ const isActive = activeCategory.toLowerCase() === cat.toLowerCase();
38
+ return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory(cat), className: `px-4 py-2 rounded-xl text-xs font-bold transition-all duration-200 ${isActive
39
+ ? 'bg-white text-slate-950 shadow-md scale-102 font-extrabold'
40
+ : 'bg-slate-900/60 text-slate-400 hover:text-white border border-slate-800/80 hover:bg-slate-850'}`, children: cat }, cat));
41
+ }) }))] }), filteredProducts.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: `grid ${gridColClasses} gap-6 sm:gap-8`, children: filteredProducts.map((product, idx) => {
42
+ const itemPath = `${sectionPath}.${cardPrefix}${idx + 1}`;
43
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "relative group", children: [(0, jsx_runtime_1.jsx)(EditableProductCard_1.EditableProductCard, { itemPath: itemPath, product: product, cardVariant: cardVariant, className: "h-full" }), onQuickView && ((0, jsx_runtime_1.jsx)("div", { className: "absolute top-4 right-4 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200", children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => onQuickView(product, itemPath), className: "p-2.5 rounded-xl bg-slate-950/80 backdrop-blur-md text-white hover:bg-lime-400 hover:text-slate-950 shadow-lg transition-all active:scale-95", title: "Quick View", "aria-label": `Quick View ${product.name || product.title}`, children: (0, jsx_runtime_1.jsxs)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", 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" })] }) }) }))] }, product.id || idx));
44
+ }) })) : (
45
+ /* Empty State */
46
+ (0, jsx_runtime_1.jsxs)("div", { className: "text-center py-20 px-4 rounded-3xl border border-dashed border-slate-800 bg-slate-900/30", children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-sm font-medium text-slate-400", children: ["No products found matching category \"", activeCategory, "\"."] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory('All'), className: "mt-4 px-4 py-2 rounded-xl text-xs font-bold bg-lime-400 text-slate-950 hover:bg-lime-300 transition-colors", children: "View All Products" })] }))] }));
47
+ }
48
+ // Canonical alias
49
+ exports.ProductGrid = EditableProductGrid;
@@ -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,9 @@ export * from './EditableBox';
9
9
  export * from './EditableGrid';
10
10
  export * from './EditableSection';
11
11
  export * from './EditableProductCard';
12
+ export * from './EditableProductGrid';
13
+ export * from './EditableProductDetail';
14
+ export * from './EditableCustomerReviews';
12
15
  export * from './EditableServiceCard';
13
16
  export * from './EditableCard';
14
17
  export * from './EditablePricingCard';
@@ -47,6 +50,9 @@ export { EditableSection as Section } from './EditableSection';
47
50
  export { EditableBox as Box } from './EditableBox';
48
51
  export { EditableList as List } from './EditableList';
49
52
  export { EditableProductCard as ProductCard } from './EditableProductCard';
53
+ export { EditableProductGrid as ProductGrid } from './EditableProductGrid';
54
+ export { EditableProductDetail as ProductDetail } from './EditableProductDetail';
55
+ export { EditableCustomerReviews as CustomerReviews } from './EditableCustomerReviews';
50
56
  export { EditableServiceCard as ServiceCard } from './EditableServiceCard';
51
57
  export { EditablePricingCard as PricingCard } from './EditablePricingCard';
52
58
  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.CustomerReviews = exports.ProductDetail = exports.ProductGrid = 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,9 @@ __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("./EditableProductGrid"), exports);
30
+ __exportStar(require("./EditableProductDetail"), exports);
31
+ __exportStar(require("./EditableCustomerReviews"), exports);
29
32
  __exportStar(require("./EditableServiceCard"), exports);
30
33
  __exportStar(require("./EditableCard"), exports);
31
34
  __exportStar(require("./EditablePricingCard"), exports);
@@ -80,6 +83,12 @@ var EditableList_1 = require("./EditableList");
80
83
  Object.defineProperty(exports, "List", { enumerable: true, get: function () { return EditableList_1.EditableList; } });
81
84
  var EditableProductCard_1 = require("./EditableProductCard");
82
85
  Object.defineProperty(exports, "ProductCard", { enumerable: true, get: function () { return EditableProductCard_1.EditableProductCard; } });
86
+ var EditableProductGrid_1 = require("./EditableProductGrid");
87
+ Object.defineProperty(exports, "ProductGrid", { enumerable: true, get: function () { return EditableProductGrid_1.EditableProductGrid; } });
88
+ var EditableProductDetail_1 = require("./EditableProductDetail");
89
+ Object.defineProperty(exports, "ProductDetail", { enumerable: true, get: function () { return EditableProductDetail_1.EditableProductDetail; } });
90
+ var EditableCustomerReviews_1 = require("./EditableCustomerReviews");
91
+ Object.defineProperty(exports, "CustomerReviews", { enumerable: true, get: function () { return EditableCustomerReviews_1.EditableCustomerReviews; } });
83
92
  var EditableServiceCard_1 = require("./EditableServiceCard");
84
93
  Object.defineProperty(exports, "ServiceCard", { enumerable: true, get: function () { return EditableServiceCard_1.EditableServiceCard; } });
85
94
  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.16",
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",