@deneb-ui/ui 2.0.109 → 2.0.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -17,5 +17,5 @@ export interface EditableCategoryPillsProps {
|
|
|
17
17
|
*
|
|
18
18
|
* Created by Chamika Gayashan & Induranga Kawishwara
|
|
19
19
|
*/
|
|
20
|
-
export declare function EditableCategoryPills({ categories, fieldPath, selectedCategory: controlledSelected, onSelectCategory, allLabel, className, style, }: EditableCategoryPillsProps): React.JSX.Element;
|
|
20
|
+
export declare function EditableCategoryPills({ categories, fieldPath, selectedCategory: controlledSelected, onSelectCategory, allLabel, className, style, }: EditableCategoryPillsProps): React.JSX.Element | null;
|
|
21
21
|
export declare const CategoryPills: typeof EditableCategoryPills;
|
|
@@ -54,23 +54,43 @@ function EditableCategoryPills({ categories, fieldPath = 'categories', selectedC
|
|
|
54
54
|
const activeCategory = controlledSelected !== undefined ? controlledSelected : internalSelected;
|
|
55
55
|
// Resolve list of categories
|
|
56
56
|
const resolvedCategories = react_1.default.useMemo(() => {
|
|
57
|
+
const rawCandidates = [];
|
|
57
58
|
if (categories && categories.length > 0) {
|
|
58
|
-
|
|
59
|
+
rawCandidates.push(...categories);
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
else {
|
|
62
|
+
const rawCategories = siteData?.categories;
|
|
63
|
+
if (Array.isArray(rawCategories) && rawCategories.length > 0) {
|
|
64
|
+
for (const c of rawCategories) {
|
|
65
|
+
const name = typeof c === "string" ? c : c?.name || String(c);
|
|
66
|
+
if (name)
|
|
67
|
+
rawCandidates.push(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const rawProducts = siteData?.products;
|
|
72
|
+
if (Array.isArray(rawProducts) && rawProducts.length > 0) {
|
|
73
|
+
for (const p of rawProducts) {
|
|
74
|
+
if (p?.category)
|
|
75
|
+
rawCandidates.push(p.category);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
64
79
|
}
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
const map = new Map();
|
|
81
|
+
for (const item of rawCandidates) {
|
|
82
|
+
if (typeof item !== "string")
|
|
83
|
+
continue;
|
|
84
|
+
const trimmed = item.trim();
|
|
85
|
+
if (!trimmed || trimmed.toLowerCase() === allLabel.toLowerCase())
|
|
86
|
+
continue;
|
|
87
|
+
const lower = trimmed.toLowerCase();
|
|
88
|
+
if (!map.has(lower)) {
|
|
89
|
+
map.set(lower, trimmed);
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
|
-
|
|
92
|
+
const distinct = Array.from(map.values());
|
|
93
|
+
return distinct.length > 1 ? [allLabel, ...distinct] : [];
|
|
74
94
|
}, [categories, siteData, allLabel]);
|
|
75
95
|
const handleSelect = (category) => {
|
|
76
96
|
if (controlledSelected === undefined) {
|
|
@@ -80,6 +100,9 @@ function EditableCategoryPills({ categories, fieldPath = 'categories', selectedC
|
|
|
80
100
|
onSelectCategory(category);
|
|
81
101
|
}
|
|
82
102
|
};
|
|
103
|
+
if (resolvedCategories.length <= 1) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
83
106
|
return ((0, jsx_runtime_1.jsx)("nav", { className: `deneb-category-pills flex items-center gap-2 overflow-x-auto py-2 no-scrollbar ${className}`, style: {
|
|
84
107
|
display: 'flex',
|
|
85
108
|
alignItems: 'center',
|
|
@@ -91,7 +114,7 @@ function EditableCategoryPills({ categories, fieldPath = 'categories', selectedC
|
|
|
91
114
|
msOverflowStyle: 'none',
|
|
92
115
|
...style,
|
|
93
116
|
}, "data-preview-field-path": fieldPath, "aria-label": "Product categories", children: resolvedCategories.map((cat, idx) => {
|
|
94
|
-
const isSelected = activeCategory.toLowerCase() === cat.toLowerCase();
|
|
117
|
+
const isSelected = activeCategory.trim().toLowerCase() === cat.trim().toLowerCase();
|
|
95
118
|
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleSelect(cat), className: `deneb-pill px-4 py-2 rounded-full text-xs md:text-sm font-semibold whitespace-nowrap transition-all duration-200 border ${isSelected
|
|
96
119
|
? 'bg-slate-900 text-white border-slate-900 shadow-sm scale-105'
|
|
97
120
|
: 'bg-white text-slate-600 border-slate-200 hover:border-slate-300 hover:text-slate-900 hover:bg-slate-50'}`, style: {
|
|
@@ -1,13 +1,101 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
'use client';
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
37
|
exports.FilterSidebar = void 0;
|
|
5
38
|
exports.EditableFilterSidebar = EditableFilterSidebar;
|
|
6
39
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
-
const react_1 = require("react");
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
8
41
|
const SiteDataProvider_1 = require("./SiteDataProvider");
|
|
9
|
-
function EditableFilterSidebar({ basePath = 'filters', categories = [
|
|
42
|
+
function EditableFilterSidebar({ basePath = 'filters', categories = [], sizes = [], minPrice = 0, maxPrice = 300, currency = '$', initialFilters, onFilterChange, className = '', }) {
|
|
10
43
|
const { siteData } = (0, SiteDataProvider_1.useSiteData)();
|
|
44
|
+
const sanitizedCategories = react_1.default.useMemo(() => {
|
|
45
|
+
if (!categories || !Array.isArray(categories))
|
|
46
|
+
return [];
|
|
47
|
+
const map = new Map();
|
|
48
|
+
for (const c of categories) {
|
|
49
|
+
if (typeof c !== "string")
|
|
50
|
+
continue;
|
|
51
|
+
const trimmed = c.trim();
|
|
52
|
+
if (!trimmed || trimmed.toLowerCase() === "all")
|
|
53
|
+
continue;
|
|
54
|
+
const lower = trimmed.toLowerCase();
|
|
55
|
+
if (!map.has(lower)) {
|
|
56
|
+
map.set(lower, trimmed);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return Array.from(map.values());
|
|
60
|
+
}, [categories]);
|
|
61
|
+
const sanitizedSizes = react_1.default.useMemo(() => {
|
|
62
|
+
const rawList = [];
|
|
63
|
+
if (sizes && Array.isArray(sizes) && sizes.length > 0) {
|
|
64
|
+
rawList.push(...sizes);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const rawProducts = siteData?.content?.products || siteData?.products;
|
|
68
|
+
if (Array.isArray(rawProducts)) {
|
|
69
|
+
for (const p of rawProducts) {
|
|
70
|
+
if (Array.isArray(p?.sizes)) {
|
|
71
|
+
for (const s of p.sizes) {
|
|
72
|
+
if (s != null)
|
|
73
|
+
rawList.push(String(s));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (Array.isArray(p?.options)) {
|
|
77
|
+
for (const o of p.options) {
|
|
78
|
+
if (o != null)
|
|
79
|
+
rawList.push(String(o));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const map = new Map();
|
|
86
|
+
for (const s of rawList) {
|
|
87
|
+
if (typeof s !== "string")
|
|
88
|
+
continue;
|
|
89
|
+
const trimmed = s.trim();
|
|
90
|
+
if (!trimmed)
|
|
91
|
+
continue;
|
|
92
|
+
const lower = trimmed.toLowerCase();
|
|
93
|
+
if (!map.has(lower)) {
|
|
94
|
+
map.set(lower, trimmed);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return Array.from(map.values());
|
|
98
|
+
}, [sizes, siteData]);
|
|
11
99
|
const [selectedCategories, setSelectedCategories] = (0, react_1.useState)(initialFilters?.selectedCategories || []);
|
|
12
100
|
const [currentPrice, setCurrentPrice] = (0, react_1.useState)(initialFilters?.priceRange ? initialFilters.priceRange[1] : maxPrice);
|
|
13
101
|
const [selectedSizes, setSelectedSizes] = (0, react_1.useState)(initialFilters?.selectedSizes || []);
|
|
@@ -29,16 +117,20 @@ function EditableFilterSidebar({ basePath = 'filters', categories = ['All', 'Run
|
|
|
29
117
|
}
|
|
30
118
|
};
|
|
31
119
|
const toggleCategory = (cat) => {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
120
|
+
const trimmedCat = cat.trim();
|
|
121
|
+
const exists = selectedCategories.some((c) => c.trim().toLowerCase() === trimmedCat.toLowerCase());
|
|
122
|
+
const next = exists
|
|
123
|
+
? selectedCategories.filter((c) => c.trim().toLowerCase() !== trimmedCat.toLowerCase())
|
|
124
|
+
: [...selectedCategories, trimmedCat];
|
|
35
125
|
setSelectedCategories(next);
|
|
36
126
|
triggerChange(next, currentPrice, selectedSizes, inStockOnly);
|
|
37
127
|
};
|
|
38
128
|
const toggleSize = (size) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
129
|
+
const trimmedSize = size.trim();
|
|
130
|
+
const exists = selectedSizes.some((s) => s.trim().toLowerCase() === trimmedSize.toLowerCase());
|
|
131
|
+
const next = exists
|
|
132
|
+
? selectedSizes.filter((s) => s.trim().toLowerCase() !== trimmedSize.toLowerCase())
|
|
133
|
+
: [...selectedSizes, trimmedSize];
|
|
42
134
|
setSelectedSizes(next);
|
|
43
135
|
triggerChange(selectedCategories, currentPrice, next, inStockOnly);
|
|
44
136
|
};
|
|
@@ -59,8 +151,8 @@ function EditableFilterSidebar({ basePath = 'filters', categories = ['All', 'Run
|
|
|
59
151
|
setInStockOnly(false);
|
|
60
152
|
triggerChange([], maxPrice, [], false);
|
|
61
153
|
};
|
|
62
|
-
const activeFiltersCount = selectedCategories.length +
|
|
63
|
-
selectedSizes.length +
|
|
154
|
+
const activeFiltersCount = (sanitizedCategories.length > 1 ? selectedCategories.length : 0) +
|
|
155
|
+
(sanitizedSizes.length > 1 ? selectedSizes.length : 0) +
|
|
64
156
|
(currentPrice < maxPrice ? 1 : 0) +
|
|
65
157
|
(inStockOnly ? 1 : 0);
|
|
66
158
|
const title = getContent('title', 'Filters');
|
|
@@ -70,16 +162,16 @@ function EditableFilterSidebar({ basePath = 'filters', categories = ['All', 'Run
|
|
|
70
162
|
const availabilityTitle = getContent('availabilityTitle', 'Availability');
|
|
71
163
|
const inStockLabel = getContent('inStockLabel', 'In Stock Only');
|
|
72
164
|
const clearAllText = getContent('clearAllText', 'Clear All');
|
|
73
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: `deneb-filter-sidebar-wrap w-full ${className}`, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "deneb-filter-mobile-toggle mb-3 rounded-xl border border-neutral-800 bg-neutral-900/90 px-4 py-3 text-sm font-semibold text-white", onClick: () => setMobileOpen((open) => !open), "aria-expanded": mobileOpen, "aria-controls": "deneb-filter-panel", "data-preview-static": "filter-mobile-toggle", children: [(0, jsx_runtime_1.jsx)("span", { children: title }), (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2 text-neutral-400", children: [activeFiltersCount > 0 ? ((0, jsx_runtime_1.jsx)("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500 text-xs font-bold text-black", children: activeFiltersCount })) : null, mobileOpen ? '−' : '+'] })] }), (0, jsx_runtime_1.jsxs)("aside", { id: "deneb-filter-panel", className: `deneb-filter-sidebar deneb-filter-sidebar-panel w-full rounded-2xl border border-neutral-800 bg-neutral-900/80 backdrop-blur-md p-6 text-white shadow-xl ${mobileOpen ? 'is-open' : ''}`, "aria-label": "Product Filters", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between border-b border-neutral-800 pb-4 mb-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-bold tracking-tight text-white", "data-preview-field-path": fieldPath('title'), children: title }), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500 text-xs font-bold text-black", "data-preview-static": "filter-active-count", children: activeFiltersCount }))] }), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleClearAll, className: "text-xs font-medium text-neutral-400 hover:text-white transition-colors", "data-preview-static": "filter-clear-all", children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fieldPath('clearAllText'), children: clearAllText }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400 mb-3", "data-preview-field-path": fieldPath(
|
|
74
|
-
const isSelected = selectedCategories.
|
|
165
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `deneb-filter-sidebar-wrap w-full ${className}`, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "deneb-filter-mobile-toggle mb-3 rounded-xl border border-neutral-800 bg-neutral-900/90 px-4 py-3 text-sm font-semibold text-white", onClick: () => setMobileOpen((open) => !open), "aria-expanded": mobileOpen, "aria-controls": "deneb-filter-panel", "data-preview-static": "filter-mobile-toggle", children: [(0, jsx_runtime_1.jsx)("span", { children: title }), (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2 text-neutral-400", children: [activeFiltersCount > 0 ? ((0, jsx_runtime_1.jsx)("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500 text-xs font-bold text-black", children: activeFiltersCount })) : null, mobileOpen ? '−' : '+'] })] }), (0, jsx_runtime_1.jsxs)("aside", { id: "deneb-filter-panel", className: `deneb-filter-sidebar deneb-filter-sidebar-panel w-full rounded-2xl border border-neutral-800 bg-neutral-900/80 backdrop-blur-md p-6 text-white shadow-xl ${mobileOpen ? 'is-open' : ''}`, "aria-label": "Product Filters", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between border-b border-neutral-800 pb-4 mb-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-bold tracking-tight text-white", "data-preview-field-path": fieldPath('title'), children: title }), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500 text-xs font-bold text-black", "data-preview-static": "filter-active-count", children: activeFiltersCount }))] }), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleClearAll, className: "text-xs font-medium text-neutral-400 hover:text-white transition-colors", "data-preview-static": "filter-clear-all", children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fieldPath('clearAllText'), children: clearAllText }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [sanitizedCategories.length > 1 && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400 mb-3", "data-preview-field-path": fieldPath("categoryTitle"), children: categoryTitle }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap gap-2", children: sanitizedCategories.map((cat) => {
|
|
166
|
+
const isSelected = selectedCategories.some((c) => c.trim().toLowerCase() === cat.trim().toLowerCase());
|
|
75
167
|
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => toggleCategory(cat), "data-preview-static": "filter-cat-btn", className: `rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${isSelected
|
|
76
|
-
?
|
|
77
|
-
:
|
|
78
|
-
}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "border-t border-neutral-800/80 pt-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center mb-2", children: [(0, jsx_runtime_1.jsx)("h4", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400", "data-preview-field-path": fieldPath('priceTitle'), children: priceTitle }), (0, jsx_runtime_1.jsxs)("span", { className: "text-sm font-semibold text-emerald-400", "data-preview-static": "filter-price-display", children: ["Up to ", currency, currentPrice] })] }), (0, jsx_runtime_1.jsx)("input", { type: "range", min: minPrice, max: maxPrice, step: 5, value: currentPrice, onChange: handlePriceChange, "data-preview-static": "filter-price-slider", className: "w-full accent-emerald-500 bg-neutral-800 h-1.5 rounded-lg cursor-pointer" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-xs text-neutral-500 mt-1", children: [(0, jsx_runtime_1.jsxs)("span", { children: [currency, minPrice] }), (0, jsx_runtime_1.jsxs)("span", { children: [currency, maxPrice] })] })] }),
|
|
79
|
-
const isSelected = selectedSizes.
|
|
168
|
+
? "bg-white text-black shadow-md"
|
|
169
|
+
: "bg-neutral-800 text-neutral-300 hover:bg-neutral-700 hover:text-white"}`, children: cat }, cat));
|
|
170
|
+
}) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "border-t border-neutral-800/80 pt-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center mb-2", children: [(0, jsx_runtime_1.jsx)("h4", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400", "data-preview-field-path": fieldPath('priceTitle'), children: priceTitle }), (0, jsx_runtime_1.jsxs)("span", { className: "text-sm font-semibold text-emerald-400", "data-preview-static": "filter-price-display", children: ["Up to ", currency, currentPrice] })] }), (0, jsx_runtime_1.jsx)("input", { type: "range", min: minPrice, max: maxPrice, step: 5, value: currentPrice, onChange: handlePriceChange, "data-preview-static": "filter-price-slider", className: "w-full accent-emerald-500 bg-neutral-800 h-1.5 rounded-lg cursor-pointer" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between text-xs text-neutral-500 mt-1", children: [(0, jsx_runtime_1.jsxs)("span", { children: [currency, minPrice] }), (0, jsx_runtime_1.jsxs)("span", { children: [currency, maxPrice] })] })] }), sanitizedSizes.length > 1 && ((0, jsx_runtime_1.jsxs)("div", { className: "border-t border-neutral-800/80 pt-6", children: [(0, jsx_runtime_1.jsx)("h4", { className: "text-xs font-semibold uppercase tracking-wider text-neutral-400 mb-3", "data-preview-field-path": fieldPath("sizeTitle"), children: sizeTitle }), (0, jsx_runtime_1.jsx)("div", { className: "deneb-filter-size-grid grid grid-cols-3 gap-2", children: sanitizedSizes.map((size) => {
|
|
171
|
+
const isSelected = selectedSizes.some((s) => s.trim().toLowerCase() === size.trim().toLowerCase());
|
|
80
172
|
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => toggleSize(size), "data-preview-static": "filter-size-btn", className: `rounded-lg py-2 text-xs font-semibold transition-all border ${isSelected
|
|
81
|
-
?
|
|
82
|
-
:
|
|
173
|
+
? "border-white bg-white text-black"
|
|
174
|
+
: "border-neutral-800 bg-neutral-800/50 text-neutral-300 hover:border-neutral-700"}`, children: size }, size));
|
|
83
175
|
}) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "border-t border-neutral-800/80 pt-6", children: [(0, jsx_runtime_1.jsx)("h4", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-neutral-400", "data-preview-field-path": fieldPath('availabilityTitle'), children: availabilityTitle }), (0, jsx_runtime_1.jsxs)("label", { className: "flex items-center justify-between cursor-pointer group", "data-preview-static": "filter-instock-toggle", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-medium text-neutral-300 group-hover:text-white transition-colors", "data-preview-field-path": fieldPath('inStockLabel'), children: inStockLabel }), (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: inStockOnly, onChange: toggleInStock, className: "h-4 w-4 rounded border-neutral-700 bg-neutral-800 text-emerald-500 accent-emerald-500 cursor-pointer" })] })] })] })] })] }));
|
|
84
176
|
}
|
|
85
177
|
exports.FilterSidebar = EditableFilterSidebar;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ProductItem, ProductCardVariant } from './EditableProductCard';
|
|
3
|
+
import { EditableFilterSidebarProps, FilterState } from './EditableFilterSidebar';
|
|
4
|
+
export interface ProductFilters {
|
|
5
|
+
categories?: string[];
|
|
6
|
+
priceRange?: [number, number];
|
|
7
|
+
maxPrice?: number;
|
|
8
|
+
sizes?: string[];
|
|
9
|
+
inStockOnly?: boolean;
|
|
10
|
+
}
|
|
3
11
|
export interface EditableProductGridProps extends React.HTMLAttributes<HTMLElement> {
|
|
4
12
|
/**
|
|
5
13
|
* Field path prefix for live Fivora Visual Editing synchronization (e.g. "home").
|
|
@@ -111,6 +119,26 @@ export interface EditableProductGridProps extends React.HTMLAttributes<HTMLEleme
|
|
|
111
119
|
* Total products count override when backend performs server-side pagination slicing.
|
|
112
120
|
*/
|
|
113
121
|
totalProducts?: number;
|
|
122
|
+
/**
|
|
123
|
+
* Controlled or external filters (typically from EditableFilterSidebar).
|
|
124
|
+
*/
|
|
125
|
+
filters?: ProductFilters;
|
|
126
|
+
/**
|
|
127
|
+
* Initial filters when using internal filter state.
|
|
128
|
+
*/
|
|
129
|
+
initialFilters?: ProductFilters;
|
|
130
|
+
/**
|
|
131
|
+
* Callback fired whenever filter state changes.
|
|
132
|
+
*/
|
|
133
|
+
onFilterChange?: (filters: FilterState) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Whether to display the integrated EditableFilterSidebar alongside the product grid (default: false).
|
|
136
|
+
*/
|
|
137
|
+
showFilterSidebar?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Customization props passed directly to the embedded EditableFilterSidebar.
|
|
140
|
+
*/
|
|
141
|
+
filterSidebarProps?: Partial<EditableFilterSidebarProps>;
|
|
114
142
|
currency?: string;
|
|
115
143
|
whatsappNumber?: string;
|
|
116
144
|
whatsappPhone?: string;
|
|
@@ -119,14 +147,6 @@ export interface EditableProductGridProps extends React.HTMLAttributes<HTMLEleme
|
|
|
119
147
|
detailActionLabel?: string;
|
|
120
148
|
className?: string;
|
|
121
149
|
}
|
|
122
|
-
|
|
123
|
-
* EditableProductGrid is an elite, fully responsive e-commerce showcase grid
|
|
124
|
-
* with interactive category filtering, live Fivora visual editing synchronization,
|
|
125
|
-
* smart multi-variant pagination, automatic useProducts() fallback rehydration,
|
|
126
|
-
* and built-in Quick View modal triggers.
|
|
127
|
-
*
|
|
128
|
-
* Created by Chamika Gayashan & Induranga Kawishwara
|
|
129
|
-
*/
|
|
130
|
-
export declare function EditableProductGrid({ currency, whatsappNumber, whatsappPhone, sectionPath, listPath, title, subtitle, products: userProducts, categories, cardVariant, columns, onQuickView, cardPrefix, showSearch, searchPlaceholder, enableBackendSearch, onSearchChange, enablePagination, pageSize, pageSizeOptions, showPageSizeSelector, paginationVariant, hidePaginationOnSinglePage, currentPage: controlledPage, initialPage, onPageChange, scrollToTopOnPageChange, totalProducts, showDetailLink, showDetailButton, detailActionLabel, className, style, ...props }: EditableProductGridProps): React.JSX.Element;
|
|
150
|
+
export declare function EditableProductGrid({ currency, whatsappNumber, whatsappPhone, sectionPath, listPath, title, subtitle, products: userProducts, categories, cardVariant, columns, onQuickView, cardPrefix, showSearch, searchPlaceholder, enableBackendSearch, onSearchChange, enablePagination, pageSize, pageSizeOptions, showPageSizeSelector, paginationVariant, hidePaginationOnSinglePage, currentPage: controlledPage, initialPage, onPageChange, scrollToTopOnPageChange, totalProducts, filters: controlledFilters, initialFilters, onFilterChange: onFilterChangeProp, showFilterSidebar, filterSidebarProps, showDetailLink, showDetailButton, detailActionLabel, className, style, ...props }: EditableProductGridProps): React.JSX.Element;
|
|
131
151
|
export declare const ProductGrid: typeof EditableProductGrid;
|
|
132
152
|
export type ProductGridItem = ProductItem;
|
|
@@ -6,6 +6,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const EditableProductCard_1 = require("./EditableProductCard");
|
|
8
8
|
const SiteDataProvider_1 = require("./SiteDataProvider");
|
|
9
|
+
const EditableFilterSidebar_1 = require("./EditableFilterSidebar");
|
|
9
10
|
/**
|
|
10
11
|
* EditableProductGrid is an elite, fully responsive e-commerce showcase grid
|
|
11
12
|
* with interactive category filtering, live Fivora visual editing synchronization,
|
|
@@ -14,12 +15,69 @@ const SiteDataProvider_1 = require("./SiteDataProvider");
|
|
|
14
15
|
*
|
|
15
16
|
* Created by Chamika Gayashan & Induranga Kawishwara
|
|
16
17
|
*/
|
|
17
|
-
function
|
|
18
|
+
function parseProductNumericPrice(p) {
|
|
19
|
+
if (typeof p.minPrice === "number" && !isNaN(p.minPrice))
|
|
20
|
+
return p.minPrice;
|
|
21
|
+
if (typeof p.price === "number" && !isNaN(p.price))
|
|
22
|
+
return p.price;
|
|
23
|
+
const raw = String(p.minPrice || p.price || p.amount || p.cost || "");
|
|
24
|
+
if (!raw)
|
|
25
|
+
return null;
|
|
26
|
+
const cleaned = raw.replace(/,/g, "").match(/\d+(?:\.\d+)?/);
|
|
27
|
+
if (!cleaned)
|
|
28
|
+
return null;
|
|
29
|
+
const val = parseFloat(cleaned[0]);
|
|
30
|
+
return isNaN(val) ? null : val;
|
|
31
|
+
}
|
|
32
|
+
function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone, sectionPath = 'home', listPath = 'products', title = 'Featured Collection', subtitle = 'Just Dropped', products: userProducts, categories = ['All'], cardVariant = 'modern-glass', columns = { mobile: 1, tablet: 2, desktop: 4 }, onQuickView, cardPrefix = 'product', showSearch = true, searchPlaceholder = 'Search products by name, brand, or tag...', enableBackendSearch = true, onSearchChange, enablePagination = true, pageSize = 8, pageSizeOptions = [8, 16, 24, 48], showPageSizeSelector = false, paginationVariant = 'numbers', hidePaginationOnSinglePage = true, currentPage: controlledPage, initialPage = 1, onPageChange, scrollToTopOnPageChange = true, totalProducts, filters: controlledFilters, initialFilters, onFilterChange: onFilterChangeProp, showFilterSidebar = false, filterSidebarProps, showDetailLink = true, showDetailButton, detailActionLabel, className = '', style, ...props }) {
|
|
18
33
|
const sectionRef = (0, react_1.useRef)(null);
|
|
19
34
|
const siteData = (0, SiteDataProvider_1.useSiteData)();
|
|
20
35
|
const siteApi = (0, SiteDataProvider_1.useSiteApi)();
|
|
21
36
|
const liveProducts = (0, SiteDataProvider_1.useProducts)();
|
|
22
37
|
const baseProducts = userProducts && userProducts.length > 0 ? userProducts : liveProducts;
|
|
38
|
+
const computedMaxPrice = (0, react_1.useMemo)(() => {
|
|
39
|
+
let highest = 100;
|
|
40
|
+
for (const p of baseProducts) {
|
|
41
|
+
const num = parseProductNumericPrice(p);
|
|
42
|
+
if (num !== null && num > highest) {
|
|
43
|
+
highest = Math.ceil(num);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (highest > 1000)
|
|
47
|
+
return Math.ceil(highest / 500) * 500;
|
|
48
|
+
if (highest > 100)
|
|
49
|
+
return Math.ceil(highest / 50) * 50;
|
|
50
|
+
return Math.ceil(highest / 10) * 10;
|
|
51
|
+
}, [baseProducts]);
|
|
52
|
+
const effectiveMaxPrice = filterSidebarProps?.maxPrice ?? computedMaxPrice;
|
|
53
|
+
const [internalFilters, setInternalFilters] = (0, react_1.useState)(() => ({
|
|
54
|
+
selectedCategories: initialFilters?.categories || [],
|
|
55
|
+
priceRange: [initialFilters?.priceRange ? initialFilters.priceRange[0] : 0, initialFilters?.maxPrice ?? (initialFilters?.priceRange ? initialFilters.priceRange[1] : effectiveMaxPrice)],
|
|
56
|
+
selectedSizes: initialFilters?.sizes || [],
|
|
57
|
+
inStockOnly: Boolean(initialFilters?.inStockOnly),
|
|
58
|
+
}));
|
|
59
|
+
const activeFilters = (0, react_1.useMemo)(() => {
|
|
60
|
+
return {
|
|
61
|
+
selectedCategories: controlledFilters?.categories !== undefined
|
|
62
|
+
? controlledFilters.categories
|
|
63
|
+
: internalFilters.selectedCategories,
|
|
64
|
+
maxPrice: controlledFilters?.maxPrice !== undefined
|
|
65
|
+
? controlledFilters.maxPrice
|
|
66
|
+
: controlledFilters?.priceRange !== undefined
|
|
67
|
+
? controlledFilters.priceRange[1]
|
|
68
|
+
: internalFilters.priceRange[1],
|
|
69
|
+
selectedSizes: controlledFilters?.sizes !== undefined
|
|
70
|
+
? controlledFilters.sizes
|
|
71
|
+
: internalFilters.selectedSizes,
|
|
72
|
+
inStockOnly: controlledFilters?.inStockOnly !== undefined
|
|
73
|
+
? Boolean(controlledFilters.inStockOnly)
|
|
74
|
+
: Boolean(internalFilters.inStockOnly),
|
|
75
|
+
};
|
|
76
|
+
}, [controlledFilters, internalFilters]);
|
|
77
|
+
const handleFilterChange = (nextFilters) => {
|
|
78
|
+
setInternalFilters(nextFilters);
|
|
79
|
+
onFilterChangeProp?.(nextFilters);
|
|
80
|
+
};
|
|
23
81
|
const resolvedListPath = (0, react_1.useMemo)(() => {
|
|
24
82
|
if (listPath && listPath !== 'products') {
|
|
25
83
|
return listPath;
|
|
@@ -50,6 +108,7 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
|
|
|
50
108
|
const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
|
|
51
109
|
const [isSearchingBackend, setIsSearchingBackend] = (0, react_1.useState)(false);
|
|
52
110
|
const [backendProducts, setBackendProducts] = (0, react_1.useState)(null);
|
|
111
|
+
const [backendTotalProducts, setBackendTotalProducts] = (0, react_1.useState)(null);
|
|
53
112
|
// Pagination internal state
|
|
54
113
|
const [internalPage, setInternalPage] = (0, react_1.useState)(initialPage);
|
|
55
114
|
const activePage = controlledPage !== undefined ? controlledPage : internalPage;
|
|
@@ -62,37 +121,95 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
|
|
|
62
121
|
}
|
|
63
122
|
}, [pageSize]);
|
|
64
123
|
// Use backend search results if available, else fallback to baseProducts
|
|
65
|
-
const products = backendProducts
|
|
66
|
-
// Live backend catalog search when query
|
|
124
|
+
const products = backendProducts !== null ? backendProducts : baseProducts;
|
|
125
|
+
// Live backend catalog search & filter when query or filters change
|
|
67
126
|
(0, react_1.useEffect)(() => {
|
|
68
|
-
if (!enableBackendSearch
|
|
127
|
+
if (!enableBackendSearch) {
|
|
69
128
|
setBackendProducts(null);
|
|
129
|
+
setBackendTotalProducts(null);
|
|
70
130
|
return;
|
|
71
131
|
}
|
|
132
|
+
const candidateSlug = siteData?.siteInstance?.slug ||
|
|
133
|
+
siteData?.project?.slug ||
|
|
134
|
+
siteData?.project?.id;
|
|
72
135
|
const catalogUrl = siteApi?.catalogUrl ||
|
|
73
|
-
(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
136
|
+
(candidateSlug && siteApi?.baseUrl
|
|
137
|
+
? `${siteApi.baseUrl.replace(/\/+$/, '')}/site-catalog/${candidateSlug}/live-data`
|
|
138
|
+
: candidateSlug
|
|
139
|
+
? `/site-catalog/${candidateSlug}/live-data`
|
|
140
|
+
: null);
|
|
141
|
+
const slug = siteData?.siteInstance?.slug || siteData?.project?.slug;
|
|
142
|
+
const isPreviewMode = (0, SiteDataProvider_1.isPreviewValidation)(siteData) ||
|
|
143
|
+
(typeof window !== 'undefined' &&
|
|
144
|
+
(window.parent !== window ||
|
|
145
|
+
window.location.pathname.includes('/template-preview') ||
|
|
146
|
+
window.location.pathname.includes('/preview')));
|
|
78
147
|
if (!catalogUrl ||
|
|
79
148
|
isPreviewMode ||
|
|
80
149
|
slug === 'template-validation' ||
|
|
81
|
-
catalogUrl.includes('/template-validation
|
|
150
|
+
catalogUrl.includes('/template-validation')) {
|
|
151
|
+
// In Visual Editor preview or template validation, NEVER query remote backend.
|
|
152
|
+
// All search, category filters, price sliders, sizes, and in-stock toggles
|
|
153
|
+
// operate directly in-memory on data.json / visual editor siteData without errors!
|
|
154
|
+
setBackendProducts(null);
|
|
155
|
+
setBackendTotalProducts(null);
|
|
82
156
|
return;
|
|
83
157
|
}
|
|
158
|
+
const hasSearch = Boolean(searchQuery.trim());
|
|
159
|
+
const hasCategories = Boolean(activeFilters.selectedCategories && activeFilters.selectedCategories.length > 0);
|
|
160
|
+
const hasMaxPrice = Boolean(activeFilters.maxPrice !== undefined &&
|
|
161
|
+
activeFilters.maxPrice < effectiveMaxPrice);
|
|
162
|
+
const hasSizes = Boolean(activeFilters.selectedSizes && activeFilters.selectedSizes.length > 0);
|
|
163
|
+
const hasInStock = Boolean(activeFilters.inStockOnly);
|
|
164
|
+
const hasAnyFilterOrSearch = hasSearch ||
|
|
165
|
+
hasCategories ||
|
|
166
|
+
hasMaxPrice ||
|
|
167
|
+
hasSizes ||
|
|
168
|
+
hasInStock;
|
|
169
|
+
if (!hasAnyFilterOrSearch) {
|
|
170
|
+
setBackendProducts(null);
|
|
171
|
+
setBackendTotalProducts(null);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const params = new URLSearchParams();
|
|
175
|
+
if (hasSearch)
|
|
176
|
+
params.set('q', searchQuery.trim());
|
|
177
|
+
if (hasCategories && activeFilters.selectedCategories) {
|
|
178
|
+
params.set('category', activeFilters.selectedCategories.join(','));
|
|
179
|
+
}
|
|
180
|
+
if (hasMaxPrice && activeFilters.maxPrice !== undefined) {
|
|
181
|
+
params.set('maxPrice', String(activeFilters.maxPrice));
|
|
182
|
+
}
|
|
183
|
+
if (hasSizes && activeFilters.selectedSizes) {
|
|
184
|
+
params.set('sizes', activeFilters.selectedSizes.join(','));
|
|
185
|
+
}
|
|
186
|
+
if (hasInStock) {
|
|
187
|
+
params.set('inStockOnly', 'true');
|
|
188
|
+
}
|
|
189
|
+
if (enablePagination) {
|
|
190
|
+
params.set('page', String(activePage));
|
|
191
|
+
params.set('limit', String(activePageSize));
|
|
192
|
+
}
|
|
84
193
|
let active = true;
|
|
85
194
|
const timer = setTimeout(async () => {
|
|
86
195
|
try {
|
|
87
196
|
setIsSearchingBackend(true);
|
|
88
197
|
const sep = catalogUrl.includes('?') ? '&' : '?';
|
|
89
|
-
const url = `${catalogUrl}${sep}
|
|
198
|
+
const url = `${catalogUrl}${sep}${params.toString()}`;
|
|
90
199
|
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
91
200
|
if (res.ok) {
|
|
92
201
|
const data = await res.json();
|
|
93
202
|
const items = Array.isArray(data) ? data : (data.products || data.items || []);
|
|
94
|
-
if (active && Array.isArray(items)
|
|
203
|
+
if (active && Array.isArray(items)) {
|
|
95
204
|
setBackendProducts(items);
|
|
205
|
+
const totalCount = typeof data.totalProducts === 'number'
|
|
206
|
+
? data.totalProducts
|
|
207
|
+
: typeof data.pagination?.total === 'number'
|
|
208
|
+
? data.pagination.total
|
|
209
|
+
: null;
|
|
210
|
+
if (totalCount !== null) {
|
|
211
|
+
setBackendTotalProducts(totalCount);
|
|
212
|
+
}
|
|
96
213
|
}
|
|
97
214
|
}
|
|
98
215
|
}
|
|
@@ -108,33 +225,98 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
|
|
|
108
225
|
active = false;
|
|
109
226
|
clearTimeout(timer);
|
|
110
227
|
};
|
|
111
|
-
}, [searchQuery, enableBackendSearch, siteApi, siteData]);
|
|
228
|
+
}, [searchQuery, activeFilters, effectiveMaxPrice, enableBackendSearch, activePage, activePageSize, enablePagination, siteApi, siteData]);
|
|
112
229
|
const resolvedCategories = (0, react_1.useMemo)(() => {
|
|
230
|
+
const rawList = [];
|
|
113
231
|
if (categories && categories.length > 1) {
|
|
114
|
-
|
|
232
|
+
rawList.push(...categories);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const siteCatList = siteData?.categories || siteData?.content?.categories;
|
|
236
|
+
if (Array.isArray(siteCatList) && siteCatList.length > 0) {
|
|
237
|
+
rawList.push(...siteCatList);
|
|
238
|
+
}
|
|
239
|
+
for (const p of products) {
|
|
240
|
+
if (typeof p.category === "string" && p.category.trim().length > 0) {
|
|
241
|
+
rawList.push(p.category);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const map = new Map();
|
|
246
|
+
for (const item of rawList) {
|
|
247
|
+
if (typeof item !== "string")
|
|
248
|
+
continue;
|
|
249
|
+
const trimmed = item.trim();
|
|
250
|
+
if (!trimmed || trimmed.toLowerCase() === "all")
|
|
251
|
+
continue;
|
|
252
|
+
const lower = trimmed.toLowerCase();
|
|
253
|
+
if (!map.has(lower)) {
|
|
254
|
+
map.set(lower, trimmed);
|
|
255
|
+
}
|
|
115
256
|
}
|
|
116
|
-
const distinct = Array.from(
|
|
117
|
-
|
|
118
|
-
.filter((cat) => typeof cat === 'string' && cat.trim().length > 0)));
|
|
119
|
-
return distinct.length > 0 ? ['All', ...distinct] : categories;
|
|
257
|
+
const distinct = Array.from(map.values());
|
|
258
|
+
return distinct.length > 1 ? ["All", ...distinct] : [];
|
|
120
259
|
}, [categories, products]);
|
|
121
260
|
const [activeCategory, setActiveCategory] = (0, react_1.useState)(categories[0] || 'All');
|
|
122
|
-
// Reset to page 1 whenever category or search query changes
|
|
261
|
+
// Reset to page 1 whenever category, filters, or search query changes
|
|
123
262
|
(0, react_1.useEffect)(() => {
|
|
124
263
|
setInternalPage(1);
|
|
125
264
|
setLoadedCount(activePageSize);
|
|
126
265
|
onPageChange?.(1);
|
|
127
|
-
}, [searchQuery, activeCategory, activePageSize, onPageChange]);
|
|
266
|
+
}, [searchQuery, activeCategory, activeFilters, activePageSize, onPageChange]);
|
|
128
267
|
const filteredProducts = (0, react_1.useMemo)(() => {
|
|
129
268
|
let result = products;
|
|
130
|
-
// Filter by category
|
|
131
|
-
if (activeCategory && activeCategory.toLowerCase() !==
|
|
269
|
+
// 1. Filter by top category tab (if not "All")
|
|
270
|
+
if (activeCategory && activeCategory.trim().toLowerCase() !== "all") {
|
|
132
271
|
result = result.filter((p) => {
|
|
133
|
-
const cat = String(p.category ||
|
|
134
|
-
return cat === activeCategory.toLowerCase();
|
|
272
|
+
const cat = String(p.category || "").trim().toLowerCase();
|
|
273
|
+
return cat === activeCategory.trim().toLowerCase();
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// 2. Filter by sidebar selectedCategories (multi-select)
|
|
277
|
+
if (activeFilters.selectedCategories && activeFilters.selectedCategories.length > 0) {
|
|
278
|
+
result = result.filter((p) => {
|
|
279
|
+
const cat = String(p.category || "").trim().toLowerCase();
|
|
280
|
+
return activeFilters.selectedCategories.some((c) => c.trim().toLowerCase() === cat);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// 3. Filter by maxPrice (matches numeric price against slider)
|
|
284
|
+
if (activeFilters.maxPrice !== undefined && activeFilters.maxPrice < effectiveMaxPrice) {
|
|
285
|
+
result = result.filter((p) => {
|
|
286
|
+
const num = parseProductNumericPrice(p);
|
|
287
|
+
if (num === null)
|
|
288
|
+
return true;
|
|
289
|
+
return num <= activeFilters.maxPrice;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// 4. Filter by sizes / options
|
|
293
|
+
if (activeFilters.selectedSizes && activeFilters.selectedSizes.length > 0) {
|
|
294
|
+
result = result.filter((p) => {
|
|
295
|
+
const pSizes = [];
|
|
296
|
+
if (Array.isArray(p.sizes)) {
|
|
297
|
+
pSizes.push(...p.sizes.map((s) => String(s).trim().toLowerCase()));
|
|
298
|
+
}
|
|
299
|
+
else if (typeof p.sizes === "string") {
|
|
300
|
+
pSizes.push(...p.sizes.split(",").map((s) => s.trim().toLowerCase()));
|
|
301
|
+
}
|
|
302
|
+
if (Array.isArray(p.options)) {
|
|
303
|
+
pSizes.push(...p.options.map((o) => String(o).trim().toLowerCase()));
|
|
304
|
+
}
|
|
305
|
+
else if (typeof p.options === "string") {
|
|
306
|
+
pSizes.push(...p.options.split(",").map((o) => o.trim().toLowerCase()));
|
|
307
|
+
}
|
|
308
|
+
return activeFilters.selectedSizes.some((s) => pSizes.includes(s.trim().toLowerCase()));
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
// 5. Filter by inStock availability
|
|
312
|
+
if (activeFilters.inStockOnly) {
|
|
313
|
+
result = result.filter((p) => {
|
|
314
|
+
return (p.inStock !== false &&
|
|
315
|
+
p.isAvailable !== false &&
|
|
316
|
+
p.available !== false);
|
|
135
317
|
});
|
|
136
318
|
}
|
|
137
|
-
// Filter by search query
|
|
319
|
+
// 6. Filter by search query
|
|
138
320
|
if (searchQuery.trim()) {
|
|
139
321
|
const q = searchQuery.trim().toLowerCase();
|
|
140
322
|
result = result.filter((p) => {
|
|
@@ -148,22 +330,49 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
|
|
|
148
330
|
});
|
|
149
331
|
}
|
|
150
332
|
return result;
|
|
151
|
-
}, [products, activeCategory, searchQuery]);
|
|
152
|
-
|
|
153
|
-
|
|
333
|
+
}, [products, activeCategory, activeFilters, effectiveMaxPrice, searchQuery]);
|
|
334
|
+
const activeFiltersCount = (activeCategory && activeCategory.trim().toLowerCase() !== "all" ? 1 : 0) +
|
|
335
|
+
activeFilters.selectedCategories.length +
|
|
336
|
+
(activeFilters.maxPrice < effectiveMaxPrice ? 1 : 0) +
|
|
337
|
+
activeFilters.selectedSizes.length +
|
|
338
|
+
(activeFilters.inStockOnly ? 1 : 0);
|
|
339
|
+
const handleClearAllFilters = () => {
|
|
340
|
+
setActiveCategory("All");
|
|
341
|
+
setSearchQuery("");
|
|
342
|
+
setInternalFilters({
|
|
343
|
+
selectedCategories: [],
|
|
344
|
+
priceRange: [0, effectiveMaxPrice],
|
|
345
|
+
selectedSizes: [],
|
|
346
|
+
inStockOnly: false,
|
|
347
|
+
});
|
|
348
|
+
onFilterChangeProp?.({
|
|
349
|
+
selectedCategories: [],
|
|
350
|
+
priceRange: [0, effectiveMaxPrice],
|
|
351
|
+
selectedSizes: [],
|
|
352
|
+
inStockOnly: false,
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
// Total count calculation: prefers explicit totalProducts prop, then remote backend total, else filtered list length
|
|
356
|
+
const totalItems = totalProducts !== undefined
|
|
357
|
+
? totalProducts
|
|
358
|
+
: (backendTotalProducts !== null ? backendTotalProducts : filteredProducts.length);
|
|
154
359
|
const totalPages = Math.max(1, Math.ceil(totalItems / activePageSize));
|
|
155
360
|
// Products to render based on pagination mode
|
|
156
361
|
const displayedProducts = (0, react_1.useMemo)(() => {
|
|
157
362
|
if (!enablePagination) {
|
|
158
363
|
return filteredProducts;
|
|
159
364
|
}
|
|
365
|
+
// If backend already returned server-paginated items for this activePage, render directly
|
|
366
|
+
if (backendProducts !== null && backendTotalProducts !== null) {
|
|
367
|
+
return filteredProducts;
|
|
368
|
+
}
|
|
160
369
|
if (paginationVariant === 'load-more') {
|
|
161
370
|
return filteredProducts.slice(0, loadedCount);
|
|
162
371
|
}
|
|
163
372
|
const startIndex = (activePage - 1) * activePageSize;
|
|
164
373
|
const endIndex = startIndex + activePageSize;
|
|
165
374
|
return filteredProducts.slice(startIndex, endIndex);
|
|
166
|
-
}, [filteredProducts, enablePagination, paginationVariant, loadedCount, activePage, activePageSize]);
|
|
375
|
+
}, [filteredProducts, enablePagination, paginationVariant, loadedCount, activePage, activePageSize, backendProducts, backendTotalProducts]);
|
|
167
376
|
// Smart page numbers calculation with ellipsis (e.g. 1 ... 4 5 6 ... 10)
|
|
168
377
|
const visiblePages = (0, react_1.useMemo)(() => {
|
|
169
378
|
if (totalPages <= 7) {
|
|
@@ -203,8 +412,53 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
|
|
|
203
412
|
return `${mClass} ${tClass} ${dClass}`;
|
|
204
413
|
}, [columns]);
|
|
205
414
|
const shouldShowPagination = enablePagination && !(hidePaginationOnSinglePage && totalPages <= 1);
|
|
415
|
+
const productsGridContent = ((0, jsx_runtime_1.jsx)("div", { ...(resolvedListPath ? { 'data-preview-list-path': resolvedListPath } : {}), className: displayedProducts.length > 0
|
|
416
|
+
? `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`
|
|
417
|
+
: 'deneb-product-grid-empty w-full', children: displayedProducts.length > 0 ? (displayedProducts.map((product, idx) => {
|
|
418
|
+
// Determine true index in baseProducts for exact Fivora visual editing focus targeting
|
|
419
|
+
const baseIndex = baseProducts.findIndex((bp) => (bp.id && product.id ? String(bp.id) === String(product.id) : false) ||
|
|
420
|
+
(bp.name && product.name ? bp.name === product.name : false) ||
|
|
421
|
+
(bp.title && product.title ? bp.title === product.title : false) ||
|
|
422
|
+
(bp.productName && product.productName ? bp.productName === product.productName : false));
|
|
423
|
+
const effectiveIndex = baseIndex !== -1 ? baseIndex : (activePage - 1) * activePageSize + idx;
|
|
424
|
+
const itemPath = resolvedListPath
|
|
425
|
+
? `${resolvedListPath}[${effectiveIndex}]`
|
|
426
|
+
: `${sectionPath}.${cardPrefix}${effectiveIndex + 1}`;
|
|
427
|
+
return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": itemPath, className: "relative group", children: [(0, jsx_runtime_1.jsx)(EditableProductCard_1.EditableProductCard, { itemPath: itemPath, product: product, cardVariant: cardVariant, currency: currency, whatsappPhone: whatsappPhone || whatsappNumber, showDetailLink: showDetailLink, showDetailButton: showDetailButton, actionLabel: detailActionLabel, 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));
|
|
428
|
+
})) : (
|
|
429
|
+
/* Empty State — strictly inside the listPath container */
|
|
430
|
+
(0, jsx_runtime_1.jsxs)("div", { className: "text-center py-16 px-4 rounded-3xl border border-dashed border-slate-300 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/30 w-full", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 mx-auto mb-4 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-slate-900 dark:text-white", children: "No products found" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 dark:text-slate-400 mt-1 max-w-sm mx-auto", children: searchQuery
|
|
431
|
+
? `No products matched "${searchQuery}"${activeCategory !== 'All' ? ` in category "${activeCategory}"` : ''}.`
|
|
432
|
+
: activeFiltersCount > 0
|
|
433
|
+
? "No products matched your active filters."
|
|
434
|
+
: `No products available in category "${activeCategory}".` }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-5 flex items-center justify-center gap-3", children: [searchQuery && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSearchQuery(''), className: "px-4 py-2 rounded-xl text-xs font-bold bg-slate-200 dark:bg-slate-800 text-slate-800 dark:text-white hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors cursor-pointer", children: "Clear Search" })), activeCategory !== 'All' && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory('All'), className: "px-4 py-2 rounded-xl text-xs font-bold bg-lime-400 text-slate-950 hover:bg-lime-300 transition-colors shadow-sm cursor-pointer", children: "View All Products" })), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleClearAllFilters, className: "px-4 py-2 rounded-xl text-xs font-bold bg-slate-900 text-white dark:bg-white dark:text-slate-950 hover:bg-slate-800 transition-colors shadow-sm cursor-pointer", children: "Clear All Filters" }))] })] })) }));
|
|
435
|
+
/* Pagination Controls */
|
|
436
|
+
const paginationContent = shouldShowPagination ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: paginationVariant === 'load-more' ? (
|
|
437
|
+
/* Progressive "Load More" Variant */
|
|
438
|
+
(0, jsx_runtime_1.jsxs)("div", { className: "mt-12 flex flex-col items-center justify-center gap-3 text-center", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-slate-500 dark:text-slate-400 font-medium", children: ["Showing ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: displayedProducts.length }), " of", ' ', (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: totalItems }), " products"] }), displayedProducts.length < totalItems && ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: handleLoadMore, className: "px-8 py-3 rounded-2xl font-bold text-sm bg-slate-900 text-white hover:bg-slate-800 dark:bg-lime-400 dark:text-slate-950 dark:hover:bg-lime-300 shadow-md transition-all active:scale-95 cursor-pointer", children: ["Load More Products (", totalItems - displayedProducts.length, " remaining)"] }))] })) : (
|
|
439
|
+
/* Numbered & Simple Pagination Navigation */
|
|
440
|
+
(0, jsx_runtime_1.jsxs)("nav", { role: "navigation", "aria-label": "Product pagination", className: "mt-12 pt-8 border-t border-slate-200 dark:border-slate-800/80 flex flex-col sm:flex-row items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Showing", ' ', (0, jsx_runtime_1.jsxs)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: [Math.min((activePage - 1) * activePageSize + 1, totalItems), "\u2013", Math.min(activePage * activePageSize, totalItems)] }), ' ', "of ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: totalItems }), " products"] }), showPageSizeSelector && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 ml-2 border-l border-slate-200 dark:border-slate-800 pl-3", children: [(0, jsx_runtime_1.jsx)("span", { children: "Show" }), (0, jsx_runtime_1.jsx)("select", { value: activePageSize, onChange: (e) => {
|
|
441
|
+
const newSize = Number(e.target.value);
|
|
442
|
+
setActivePageSize(newSize);
|
|
443
|
+
setInternalPage(1);
|
|
444
|
+
onPageChange?.(1);
|
|
445
|
+
}, className: "bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-lg px-2 py-1 text-xs font-semibold text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-1 focus:ring-lime-400", children: pageSizeOptions.map((size) => ((0, jsx_runtime_1.jsx)("option", { value: size, children: size }, size))) }), (0, jsx_runtime_1.jsx)("span", { children: "per page" })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 sm:gap-1.5", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage - 1), disabled: activePage <= 1, "aria-label": "Previous page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage <= 1
|
|
446
|
+
? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
|
|
447
|
+
: 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }), (0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Previous" })] }), paginationVariant === 'numbers' ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1", children: visiblePages.map((pageNum, idx) => {
|
|
448
|
+
if (pageNum === 'ellipsis') {
|
|
449
|
+
return ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", className: "px-2 text-slate-400 dark:text-slate-600 select-none text-xs font-bold", children: "\u2026" }, `ellipsis-${idx}`));
|
|
450
|
+
}
|
|
451
|
+
const isCurrent = pageNum === activePage;
|
|
452
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handlePageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": isCurrent ? 'page' : undefined, className: `min-w-[34px] h-[34px] px-2 rounded-xl text-xs font-bold transition-all flex items-center justify-center cursor-pointer ${isCurrent
|
|
453
|
+
? 'bg-slate-900 text-white dark:bg-lime-400 dark:text-slate-950 font-black shadow-md scale-105'
|
|
454
|
+
: 'bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800/80 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'}`, children: pageNum }, pageNum));
|
|
455
|
+
}) })) : (
|
|
456
|
+
/* Simple Indicator */
|
|
457
|
+
(0, jsx_runtime_1.jsxs)("span", { className: "px-3 py-1.5 text-xs font-bold text-slate-700 dark:text-slate-300", children: ["Page ", activePage, " of ", totalPages] })), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage + 1), disabled: activePage >= totalPages, "aria-label": "Next page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage >= totalPages
|
|
458
|
+
? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
|
|
459
|
+
: 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Next" }), (0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M9 5l7 7-7 7" }) })] })] })] })) })) : null;
|
|
206
460
|
return ((0, jsx_runtime_1.jsxs)("section", { id: props.id || "products", ref: sectionRef, "data-design-section": props["data-design-section"] || "products", "data-section-id": props["data-section-id"] || "products", "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-8 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-emerald-600 dark: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-slate-900 dark:text-white leading-tight", children: title })] }), resolvedCategories.length > 1 && ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap items-center gap-2", children: resolvedCategories.map((cat) => {
|
|
207
|
-
const isActive = activeCategory.toLowerCase() === cat.toLowerCase();
|
|
461
|
+
const isActive = activeCategory.trim().toLowerCase() === cat.trim().toLowerCase();
|
|
208
462
|
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
|
|
209
463
|
? 'bg-slate-900 text-white dark:bg-white dark:text-slate-950 shadow-md scale-102 font-extrabold'
|
|
210
464
|
: 'bg-slate-100 text-slate-600 hover:text-slate-900 border border-slate-200 hover:bg-slate-200/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:text-white dark:border-slate-800/80 dark:hover:bg-slate-850'}`, children: cat }, cat));
|
|
@@ -214,47 +468,7 @@ function EditableProductGrid({ currency = 'LKR', whatsappNumber, whatsappPhone,
|
|
|
214
468
|
}, placeholder: searchPlaceholder, className: "w-full pl-10 pr-10 py-2.5 rounded-2xl text-sm bg-slate-100/90 dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-lime-400 dark:focus:ring-lime-400 transition-all shadow-sm" }), searchQuery && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
|
|
215
469
|
setSearchQuery('');
|
|
216
470
|
onSearchChange?.('');
|
|
217
|
-
}, className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-white transition-colors", title: "Clear search", children: (0, jsx_runtime_1.jsx)("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: "M6 18L18 6M6 6l12 12" }) }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [isSearchingBackend && ((0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center gap-1.5 text-emerald-600 dark:text-lime-400 font-medium animate-pulse", children: [(0, jsx_runtime_1.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-lime-400" }), "Searching live catalog..."] })), (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold bg-slate-100 dark:bg-slate-900/80 px-3 py-1.5 rounded-xl border border-slate-200/80 dark:border-slate-800", children: ["Showing ", (0, jsx_runtime_1.jsx)("span", { className: "text-slate-900 dark:text-white font-bold", children: filteredProducts.length }), " of ", products.length, " products"] })] })] })), (0, jsx_runtime_1.
|
|
218
|
-
? `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`
|
|
219
|
-
: 'deneb-product-grid-empty w-full', children: displayedProducts.length > 0 ? (displayedProducts.map((product, idx) => {
|
|
220
|
-
// Determine true index in baseProducts for exact Fivora visual editing focus targeting
|
|
221
|
-
const baseIndex = baseProducts.findIndex((bp) => (bp.id && product.id ? String(bp.id) === String(product.id) : false) ||
|
|
222
|
-
(bp.name && product.name ? bp.name === product.name : false) ||
|
|
223
|
-
(bp.title && product.title ? bp.title === product.title : false) ||
|
|
224
|
-
(bp.productName && product.productName ? bp.productName === product.productName : false));
|
|
225
|
-
const effectiveIndex = baseIndex !== -1 ? baseIndex : (activePage - 1) * activePageSize + idx;
|
|
226
|
-
const itemPath = resolvedListPath
|
|
227
|
-
? `${resolvedListPath}[${effectiveIndex}]`
|
|
228
|
-
: `${sectionPath}.${cardPrefix}${effectiveIndex + 1}`;
|
|
229
|
-
return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": itemPath, className: "relative group", children: [(0, jsx_runtime_1.jsx)(EditableProductCard_1.EditableProductCard, { itemPath: itemPath, product: product, cardVariant: cardVariant, currency: currency, whatsappPhone: whatsappPhone || whatsappNumber, showDetailLink: showDetailLink, showDetailButton: showDetailButton, actionLabel: detailActionLabel, 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));
|
|
230
|
-
})) : (
|
|
231
|
-
/* Empty State — strictly inside the listPath container */
|
|
232
|
-
(0, jsx_runtime_1.jsxs)("div", { className: "text-center py-16 px-4 rounded-3xl border border-dashed border-slate-300 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/30 w-full", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 mx-auto mb-4 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400", children: (0, jsx_runtime_1.jsx)("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-slate-900 dark:text-white", children: "No products found" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-slate-500 dark:text-slate-400 mt-1 max-w-sm mx-auto", children: searchQuery
|
|
233
|
-
? `No products matched "${searchQuery}"${activeCategory !== 'All' ? ` in category "${activeCategory}"` : ''}.`
|
|
234
|
-
: `No products available in category "${activeCategory}".` }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-5 flex items-center justify-center gap-3", children: [searchQuery && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setSearchQuery(''), className: "px-4 py-2 rounded-xl text-xs font-bold bg-slate-200 dark:bg-slate-800 text-slate-800 dark:text-white hover:bg-slate-300 dark:hover:bg-slate-700 transition-colors", children: "Clear Search" })), activeCategory !== 'All' && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveCategory('All'), className: "px-4 py-2 rounded-xl text-xs font-bold bg-lime-400 text-slate-950 hover:bg-lime-300 transition-colors shadow-sm", children: "View All Products" }))] })] })) }), shouldShowPagination && ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: paginationVariant === 'load-more' ? (
|
|
235
|
-
/* Progressive "Load More" Variant */
|
|
236
|
-
(0, jsx_runtime_1.jsxs)("div", { className: "mt-12 flex flex-col items-center justify-center gap-3 text-center", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-slate-500 dark:text-slate-400 font-medium", children: ["Showing ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: displayedProducts.length }), " of", ' ', (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white", children: totalItems }), " products"] }), displayedProducts.length < totalItems && ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: handleLoadMore, className: "px-8 py-3 rounded-2xl font-bold text-sm bg-slate-900 text-white hover:bg-slate-800 dark:bg-lime-400 dark:text-slate-950 dark:hover:bg-lime-300 shadow-md transition-all active:scale-95 cursor-pointer", children: ["Load More Products (", totalItems - displayedProducts.length, " remaining)"] }))] })) : (
|
|
237
|
-
/* Numbered & Simple Pagination Navigation */
|
|
238
|
-
(0, jsx_runtime_1.jsxs)("nav", { role: "navigation", "aria-label": "Product pagination", className: "mt-12 pt-8 border-t border-slate-200 dark:border-slate-800/80 flex flex-col sm:flex-row items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [(0, jsx_runtime_1.jsxs)("span", { children: ["Showing", ' ', (0, jsx_runtime_1.jsxs)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: [Math.min((activePage - 1) * activePageSize + 1, totalItems), "\u2013", Math.min(activePage * activePageSize, totalItems)] }), ' ', "of ", (0, jsx_runtime_1.jsx)("strong", { className: "text-slate-900 dark:text-white font-semibold", children: totalItems }), " products"] }), showPageSizeSelector && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 ml-2 border-l border-slate-200 dark:border-slate-800 pl-3", children: [(0, jsx_runtime_1.jsx)("span", { children: "Show" }), (0, jsx_runtime_1.jsx)("select", { value: activePageSize, onChange: (e) => {
|
|
239
|
-
const newSize = Number(e.target.value);
|
|
240
|
-
setActivePageSize(newSize);
|
|
241
|
-
setInternalPage(1);
|
|
242
|
-
onPageChange?.(1);
|
|
243
|
-
}, className: "bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-lg px-2 py-1 text-xs font-semibold text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-1 focus:ring-lime-400", children: pageSizeOptions.map((size) => ((0, jsx_runtime_1.jsx)("option", { value: size, children: size }, size))) }), (0, jsx_runtime_1.jsx)("span", { children: "per page" })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 sm:gap-1.5", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage - 1), disabled: activePage <= 1, "aria-label": "Previous page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage <= 1
|
|
244
|
-
? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
|
|
245
|
-
: 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M15 19l-7-7 7-7" }) }), (0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Previous" })] }), paginationVariant === 'numbers' ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1", children: visiblePages.map((pageNum, idx) => {
|
|
246
|
-
if (pageNum === 'ellipsis') {
|
|
247
|
-
return ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", className: "px-2 text-slate-400 dark:text-slate-600 select-none text-xs font-bold", children: "\u2026" }, `ellipsis-${idx}`));
|
|
248
|
-
}
|
|
249
|
-
const isCurrent = pageNum === activePage;
|
|
250
|
-
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handlePageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": isCurrent ? 'page' : undefined, className: `min-w-[34px] h-[34px] px-2 rounded-xl text-xs font-bold transition-all flex items-center justify-center cursor-pointer ${isCurrent
|
|
251
|
-
? 'bg-slate-900 text-white dark:bg-lime-400 dark:text-slate-950 font-black shadow-md scale-105'
|
|
252
|
-
: 'bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800/80 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white'}`, children: pageNum }, pageNum));
|
|
253
|
-
}) })) : (
|
|
254
|
-
/* Simple Indicator */
|
|
255
|
-
(0, jsx_runtime_1.jsxs)("span", { className: "px-3 py-1.5 text-xs font-bold text-slate-700 dark:text-slate-300", children: ["Page ", activePage, " of ", totalPages] })), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handlePageChange(activePage + 1), disabled: activePage >= totalPages, "aria-label": "Next page", className: `px-3 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1 border ${activePage >= totalPages
|
|
256
|
-
? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
|
|
257
|
-
: 'border-slate-200 hover:border-slate-300 bg-white hover:bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/80 dark:text-slate-300 dark:hover:bg-slate-800 active:scale-95 shadow-xs cursor-pointer'}`, children: [(0, jsx_runtime_1.jsx)("span", { className: "hidden sm:inline", children: "Next" }), (0, jsx_runtime_1.jsx)("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M9 5l7 7-7 7" }) })] })] })] })) }))] }));
|
|
471
|
+
}, className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-white transition-colors", title: "Clear search", children: (0, jsx_runtime_1.jsx)("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: "M6 18L18 6M6 6l12 12" }) }) }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-xs text-slate-500 dark:text-slate-400", children: [isSearchingBackend && ((0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center gap-1.5 text-emerald-600 dark:text-lime-400 font-medium animate-pulse", children: [(0, jsx_runtime_1.jsx)("span", { className: "w-1.5 h-1.5 rounded-full bg-lime-400" }), "Searching live catalog..."] })), (0, jsx_runtime_1.jsxs)("span", { className: "font-semibold bg-slate-100 dark:bg-slate-900/80 px-3 py-1.5 rounded-xl border border-slate-200/80 dark:border-slate-800", children: ["Showing ", (0, jsx_runtime_1.jsx)("span", { className: "text-slate-900 dark:text-white font-bold", children: filteredProducts.length }), " of ", products.length, " products"] })] })] })), showFilterSidebar ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col lg:flex-row gap-8 items-start w-full", children: [(0, jsx_runtime_1.jsx)("aside", { className: "w-full lg:w-72 shrink-0", children: (0, jsx_runtime_1.jsx)(EditableFilterSidebar_1.EditableFilterSidebar, { categories: categories.length > 1 ? categories : resolvedCategories.filter((c) => c !== "All"), minPrice: filterSidebarProps?.minPrice ?? 0, maxPrice: effectiveMaxPrice, currency: currency, initialFilters: activeFilters, onFilterChange: handleFilterChange, ...filterSidebarProps }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 w-full min-w-0", children: [productsGridContent, paginationContent] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [productsGridContent, paginationContent] }))] }));
|
|
258
472
|
}
|
|
259
473
|
// Canonical alias
|
|
260
474
|
exports.ProductGrid = EditableProductGrid;
|
package/dist/SiteDataProvider.js
CHANGED
|
@@ -995,8 +995,12 @@ function isPreviewValidation(siteData) {
|
|
|
995
995
|
return true;
|
|
996
996
|
}
|
|
997
997
|
if (typeof window !== 'undefined') {
|
|
998
|
+
// If running in an iframe (Visual Editor preview in Shop Owner / Website Agent panel)
|
|
999
|
+
if (window.parent !== window) {
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
998
1002
|
const path = window.location.pathname || '';
|
|
999
|
-
if (path.includes('/template-validation') || path.includes('/template-preview') || path.includes('/preview/')) {
|
|
1003
|
+
if (path.includes('/template-validation') || path.includes('/template-preview') || path.includes('/preview/') || path.includes('/preview')) {
|
|
1000
1004
|
return true;
|
|
1001
1005
|
}
|
|
1002
1006
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deneb-ui/ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.110",
|
|
4
4
|
"description": "Visual-first React component library for editable commerce storefronts. Built for Next.js and Fivora.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
],
|
|
51
51
|
"license": "MIT",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@deneb-ui/core": "^2.0.
|
|
53
|
+
"@deneb-ui/core": "^2.0.110"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"react": "^18.0.0 || ^19.0.0",
|