@deneb-ui/ui 2.0.66 → 2.0.68

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.
@@ -101,7 +101,7 @@ function FeedbackCard({ item, index, maxStars, badgeIcon, badgeTitle, cardClassN
101
101
  'https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=300&auto=format&fit=crop');
102
102
  const reviewerComment = String(currentItem.comment || '');
103
103
  const reviewerDate = String(currentItem.date || 'Recent review');
104
- return ((0, jsx_runtime_1.jsx)("div", { "data-preview-item-path": `feedback.feedbacks[${index}]`, className: `p-6 sm:p-7 rounded-2xl sm:rounded-3xl bg-white border border-slate-200 hover:border-slate-300 shadow-sm hover:shadow-md dark:bg-gradient-to-b dark:from-slate-900/70 dark:to-slate-950/90 dark:border-slate-800/80 dark:hover:border-slate-700 dark:shadow-xl transition-all duration-300 flex flex-col justify-between space-y-4 ${cardClassName || ''}`.trim(), children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-3.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("img", { src: reviewerAvatar, alt: reviewerName, "data-preview-field-path": `feedback.feedbacks[${index}].avatar`, className: "w-11 h-11 rounded-full object-cover border border-slate-200 dark:border-slate-700 shadow-sm" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.feedbacks[${index}].name`, className: "block text-sm font-black text-slate-900 dark:text-white", children: reviewerName }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.feedbacks[${index}].date`, className: "block text-xs text-slate-500 dark:text-slate-400", children: reviewerDate })] })] }), (() => {
104
+ return ((0, jsx_runtime_1.jsx)("div", { "data-preview-item-path": `feedback.feedbacks[${index}]`, className: `p-6 sm:p-7 rounded-2xl sm:rounded-3xl bg-white border border-slate-200 hover:border-slate-300 shadow-sm hover:shadow-md dark:bg-gradient-to-b dark:from-slate-900/70 dark:to-slate-950/90 dark:border-slate-800/80 dark:hover:border-slate-700 dark:shadow-xl transition-[transform,box-shadow] duration-200 flex flex-col justify-between space-y-4 ${cardClassName || ''}`.trim(), children: (0, jsx_runtime_1.jsxs)("div", { className: "space-y-3.5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("img", { src: reviewerAvatar, alt: reviewerName, "data-preview-field-path": `feedback.feedbacks[${index}].avatar`, className: "w-11 h-11 rounded-full object-cover border border-slate-200 dark:border-slate-700 shadow-sm" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.feedbacks[${index}].name`, className: "block text-sm font-black text-slate-900 dark:text-white", children: reviewerName }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.feedbacks[${index}].date`, className: "block text-xs text-slate-500 dark:text-slate-400", children: reviewerDate })] })] }), (() => {
105
105
  const itemCustomIcon = currentItem?.badgeIcon || currentItem?.platformIcon;
106
106
  const finalCardIcon = typeof itemCustomIcon === 'string' && itemCustomIcon.trim() ? itemCustomIcon : (badgeIcon || DEFAULT_GOOGLE_ICON);
107
107
  const cardIconFieldPath = typeof itemCustomIcon === 'string' && itemCustomIcon.trim()
@@ -119,5 +119,7 @@ function FeedbackCard({ item, index, maxStars, badgeIcon, badgeTitle, cardClassN
119
119
  }
120
120
  function EditableGoogleFeedback({ heading = 'Loved by Customers Worldwide', subheading = 'Real stories and verified 5-star reviews from our official Google Business profile.', badgeTitle = 'Google Reviews', badgeRating = '4.9', badgeReviewsCount = '128 verified reviews', badgeIcon, feedbacks = DEFAULT_FEEDBACKS, maxStars = 5, className = '', cardClassName = '', style, basePath = 'feedback', ...props }) {
121
121
  const items = Array.isArray(feedbacks) && feedbacks.length > 0 ? feedbacks : DEFAULT_FEEDBACKS;
122
- return ((0, jsx_runtime_1.jsx)("section", { "data-preview-page-key": basePath, className: `editable-google-feedback w-full py-12 sm:py-20 ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-10", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col md:flex-row md:items-end justify-between gap-6 mb-10 sm:mb-14", children: [(0, jsx_runtime_1.jsxs)("div", { className: "max-w-2xl space-y-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-2.5 px-3.5 py-1.5 rounded-full bg-slate-100 border border-slate-200 dark:bg-slate-900/80 dark:border-slate-800 shadow-sm", children: [(0, jsx_runtime_1.jsx)("img", { src: badgeIcon || DEFAULT_GOOGLE_ICON, alt: "Google", "data-preview-field-path": `feedback.badgeIcon`, className: "w-4 h-4 object-contain" }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeTitle`, className: "text-xs uppercase tracking-widest font-black text-slate-800 dark:text-slate-200", children: badgeTitle }), (0, jsx_runtime_1.jsx)("span", { className: "w-1 h-1 rounded-full bg-slate-400 dark:bg-slate-600" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeRating`, className: "text-xs font-black text-amber-600 dark:text-amber-400", children: badgeRating }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-[2px]", children: Array.from({ length: 5 }).map((_, sIdx) => ((0, jsx_runtime_1.jsx)(GoogleStarSvg, { filled: true, size: 14 }, sIdx))) })] }), (0, jsx_runtime_1.jsxs)("span", { className: "text-[11px] text-slate-500 dark:text-slate-400 font-medium hidden sm:inline", children: ["(", (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeReviewsCount`, children: badgeReviewsCount }), ")"] })] }), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `feedback.heading`, className: "font-heading text-2xl xs:text-3xl sm:text-4xl lg:text-5xl font-black text-slate-900 dark:text-white tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `feedback.subheading`, className: "text-xs sm:text-base text-slate-600 dark:text-slate-400 leading-relaxed", children: subheading })] }), (0, jsx_runtime_1.jsx)("div", { className: "hidden md:flex flex-col items-end text-right", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeRating`, className: "font-heading text-4xl font-black text-amber-500 dark:text-amber-400", children: badgeRating }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-start gap-0.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-[2px]", children: Array.from({ length: 5 }).map((_, sIdx) => ((0, jsx_runtime_1.jsx)(GoogleStarSvg, { filled: true, size: 16 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeReviewsCount`, className: "text-xs text-slate-500 dark:text-slate-400 font-bold", children: badgeReviewsCount })] })] }) })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `feedback.feedbacks`, className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(FeedbackCard, { item: item, index: index, maxStars: maxStars, badgeIcon: badgeIcon, badgeTitle: badgeTitle, cardClassName: cardClassName }, item.id || String(index)))) })] }) }));
122
+ const hasCustomPy = /(^|\s)(p|py|pt)-/.test(className);
123
+ const defaultPadding = hasCustomPy ? '' : 'py-6 sm:py-8';
124
+ return ((0, jsx_runtime_1.jsx)("section", { "data-preview-page-key": basePath, className: `editable-google-feedback w-full ${defaultPadding} ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-10", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col md:flex-row md:items-end justify-between gap-6 mb-6 sm:mb-8", children: [(0, jsx_runtime_1.jsxs)("div", { className: "max-w-2xl space-y-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-2.5 px-3.5 py-1.5 rounded-full bg-slate-100 border border-slate-200 dark:bg-slate-900/80 dark:border-slate-800 shadow-sm", children: [(0, jsx_runtime_1.jsx)("img", { src: badgeIcon || DEFAULT_GOOGLE_ICON, alt: "Google", "data-preview-field-path": `feedback.badgeIcon`, className: "w-4 h-4 object-contain" }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeTitle`, className: "text-xs uppercase tracking-widest font-black text-slate-800 dark:text-slate-200", children: badgeTitle }), (0, jsx_runtime_1.jsx)("span", { className: "w-1 h-1 rounded-full bg-slate-400 dark:bg-slate-600" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeRating`, className: "text-xs font-black text-amber-600 dark:text-amber-400", children: badgeRating }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-[2px]", children: Array.from({ length: 5 }).map((_, sIdx) => ((0, jsx_runtime_1.jsx)(GoogleStarSvg, { filled: true, size: 14 }, sIdx))) })] }), (0, jsx_runtime_1.jsxs)("span", { className: "text-[11px] text-slate-500 dark:text-slate-400 font-medium hidden sm:inline", children: ["(", (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeReviewsCount`, children: badgeReviewsCount }), ")"] })] }), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `feedback.heading`, className: "font-heading text-2xl xs:text-3xl sm:text-4xl lg:text-5xl font-black text-slate-900 dark:text-white tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `feedback.subheading`, className: "text-xs sm:text-base text-slate-600 dark:text-slate-400 leading-relaxed", children: subheading })] }), (0, jsx_runtime_1.jsx)("div", { className: "hidden md:flex flex-col items-end text-right", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeRating`, className: "font-heading text-4xl font-black text-amber-500 dark:text-amber-400", children: badgeRating }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-start gap-0.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-[2px]", children: Array.from({ length: 5 }).map((_, sIdx) => ((0, jsx_runtime_1.jsx)(GoogleStarSvg, { filled: true, size: 16 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.badgeReviewsCount`, className: "text-xs text-slate-500 dark:text-slate-400 font-bold", children: badgeReviewsCount })] })] }) })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `feedback.feedbacks`, className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(FeedbackCard, { item: item, index: index, maxStars: maxStars, badgeIcon: badgeIcon, badgeTitle: badgeTitle, cardClassName: cardClassName }, item.id || String(index)))) })] }) }));
123
125
  }
@@ -48,14 +48,78 @@ export interface EditableProductGridProps extends React.HTMLAttributes<HTMLEleme
48
48
  * e.g. "home.product1", "home.product2", etc.
49
49
  */
50
50
  cardPrefix?: string;
51
+ /**
52
+ * Whether to display the live search bar above the product grid (default: true).
53
+ */
54
+ showSearch?: boolean;
55
+ /**
56
+ * Custom placeholder text for the search input.
57
+ */
58
+ searchPlaceholder?: string;
59
+ /**
60
+ * Whether to enable live backend catalog search when query is typed (default: true).
61
+ */
62
+ enableBackendSearch?: boolean;
63
+ /**
64
+ * Callback fired whenever the search query changes.
65
+ */
66
+ onSearchChange?: (query: string) => void;
67
+ /**
68
+ * Whether to enable pagination for the product grid (default: true).
69
+ */
70
+ enablePagination?: boolean;
71
+ /**
72
+ * Number of products to display per page (default: 8).
73
+ */
74
+ pageSize?: number;
75
+ /**
76
+ * Selectable page size options for the dropdown selector (e.g. [8, 16, 24, 48]).
77
+ */
78
+ pageSizeOptions?: number[];
79
+ /**
80
+ * Whether to show the page size selector dropdown (default: false).
81
+ */
82
+ showPageSizeSelector?: boolean;
83
+ /**
84
+ * Pagination visual display variant:
85
+ * - 'numbers': Standard numbered buttons with smart ellipsis and Prev/Next (default)
86
+ * - 'simple': Minimal Previous / Next controls with page indicator
87
+ * - 'load-more': Progressive infinite-style "Load More Products" button
88
+ */
89
+ paginationVariant?: 'numbers' | 'simple' | 'load-more';
90
+ /**
91
+ * Whether to automatically hide pagination controls when all products fit on a single page (default: true).
92
+ */
93
+ hidePaginationOnSinglePage?: boolean;
94
+ /**
95
+ * Controlled active page number (1-indexed).
96
+ */
97
+ currentPage?: number;
98
+ /**
99
+ * Initial page number when uncontrolled (default: 1).
100
+ */
101
+ initialPage?: number;
102
+ /**
103
+ * Callback fired when the active page changes.
104
+ */
105
+ onPageChange?: (page: number) => void;
106
+ /**
107
+ * Whether to smoothly scroll to the top of the grid section when the page changes (default: true).
108
+ */
109
+ scrollToTopOnPageChange?: boolean;
110
+ /**
111
+ * Total products count override when backend performs server-side pagination slicing.
112
+ */
113
+ totalProducts?: number;
51
114
  className?: string;
52
115
  }
53
116
  /**
54
117
  * EditableProductGrid is an elite, fully responsive e-commerce showcase grid
55
118
  * with interactive category filtering, live Fivora visual editing synchronization,
56
- * automatic useProducts() fallback rehydration, and built-in Quick View modal triggers.
119
+ * smart multi-variant pagination, automatic useProducts() fallback rehydration,
120
+ * and built-in Quick View modal triggers.
57
121
  *
58
122
  * Created by Chamika Gayashan & Induranga Kawishwara
59
123
  */
60
- export declare function EditableProductGrid({ sectionPath, listPath, title, subtitle, products: userProducts, categories, cardVariant, columns, onQuickView, cardPrefix, className, style, ...props }: EditableProductGridProps): React.JSX.Element;
124
+ export declare function EditableProductGrid({ 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, className, style, ...props }: EditableProductGridProps): React.JSX.Element;
61
125
  export declare const ProductGrid: typeof EditableProductGrid;
@@ -9,13 +9,71 @@ const SiteDataProvider_1 = require("./SiteDataProvider");
9
9
  /**
10
10
  * EditableProductGrid is an elite, fully responsive e-commerce showcase grid
11
11
  * with interactive category filtering, live Fivora visual editing synchronization,
12
- * automatic useProducts() fallback rehydration, and built-in Quick View modal triggers.
12
+ * smart multi-variant pagination, automatic useProducts() fallback rehydration,
13
+ * and built-in Quick View modal triggers.
13
14
  *
14
15
  * Created by Chamika Gayashan & Induranga Kawishwara
15
16
  */
16
- function EditableProductGrid({ 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', className = '', style, ...props }) {
17
+ function EditableProductGrid({ 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, className = '', style, ...props }) {
18
+ const sectionRef = (0, react_1.useRef)(null);
19
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
20
+ const siteApi = (0, SiteDataProvider_1.useSiteApi)();
17
21
  const liveProducts = (0, SiteDataProvider_1.useProducts)();
18
- const products = userProducts && userProducts.length > 0 ? userProducts : liveProducts;
22
+ const baseProducts = userProducts && userProducts.length > 0 ? userProducts : liveProducts;
23
+ const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
24
+ const [isSearchingBackend, setIsSearchingBackend] = (0, react_1.useState)(false);
25
+ const [backendProducts, setBackendProducts] = (0, react_1.useState)(null);
26
+ // Pagination internal state
27
+ const [internalPage, setInternalPage] = (0, react_1.useState)(initialPage);
28
+ const activePage = controlledPage !== undefined ? controlledPage : internalPage;
29
+ const [activePageSize, setActivePageSize] = (0, react_1.useState)(pageSize);
30
+ const [loadedCount, setLoadedCount] = (0, react_1.useState)(pageSize);
31
+ // Sync internal page size if prop changes
32
+ (0, react_1.useEffect)(() => {
33
+ if (pageSize) {
34
+ setActivePageSize(pageSize);
35
+ }
36
+ }, [pageSize]);
37
+ // Use backend search results if available, else fallback to baseProducts
38
+ const products = backendProducts && backendProducts.length > 0 ? backendProducts : baseProducts;
39
+ // Live backend catalog search when query changes
40
+ (0, react_1.useEffect)(() => {
41
+ if (!enableBackendSearch || !searchQuery.trim()) {
42
+ setBackendProducts(null);
43
+ return;
44
+ }
45
+ const catalogUrl = siteApi?.catalogUrl ||
46
+ (siteData?.siteInstance?.slug ? `/site-catalog/${siteData.siteInstance.slug}` : null);
47
+ if (!catalogUrl)
48
+ return;
49
+ let active = true;
50
+ const timer = setTimeout(async () => {
51
+ try {
52
+ setIsSearchingBackend(true);
53
+ const sep = catalogUrl.includes('?') ? '&' : '?';
54
+ const url = `${catalogUrl}${sep}q=${encodeURIComponent(searchQuery.trim())}`;
55
+ const res = await fetch(url, { headers: { Accept: 'application/json' } });
56
+ if (res.ok) {
57
+ const data = await res.json();
58
+ const items = Array.isArray(data) ? data : (data.products || data.items || []);
59
+ if (active && Array.isArray(items) && items.length > 0) {
60
+ setBackendProducts(items);
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Fallback to client-side filtering cleanly
66
+ }
67
+ finally {
68
+ if (active)
69
+ setIsSearchingBackend(false);
70
+ }
71
+ }, 300);
72
+ return () => {
73
+ active = false;
74
+ clearTimeout(timer);
75
+ };
76
+ }, [searchQuery, enableBackendSearch, siteApi, siteData]);
19
77
  const resolvedCategories = (0, react_1.useMemo)(() => {
20
78
  if (categories && categories.length > 1) {
21
79
  return categories;
@@ -26,15 +84,78 @@ function EditableProductGrid({ sectionPath = 'home', listPath = 'products', titl
26
84
  return distinct.length > 0 ? ['All', ...distinct] : categories;
27
85
  }, [categories, products]);
28
86
  const [activeCategory, setActiveCategory] = (0, react_1.useState)(categories[0] || 'All');
87
+ // Reset to page 1 whenever category or search query changes
88
+ (0, react_1.useEffect)(() => {
89
+ setInternalPage(1);
90
+ setLoadedCount(activePageSize);
91
+ onPageChange?.(1);
92
+ }, [searchQuery, activeCategory, activePageSize]);
29
93
  const filteredProducts = (0, react_1.useMemo)(() => {
30
- if (!activeCategory || activeCategory.toLowerCase() === 'all') {
31
- return products;
32
- }
33
- return products.filter((p) => {
34
- const cat = String(p.category || '').toLowerCase();
35
- return cat === activeCategory.toLowerCase();
36
- });
37
- }, [products, activeCategory]);
94
+ let result = products;
95
+ // Filter by category
96
+ if (activeCategory && activeCategory.toLowerCase() !== 'all') {
97
+ result = result.filter((p) => {
98
+ const cat = String(p.category || '').toLowerCase();
99
+ return cat === activeCategory.toLowerCase();
100
+ });
101
+ }
102
+ // Filter by search query
103
+ if (searchQuery.trim()) {
104
+ const q = searchQuery.trim().toLowerCase();
105
+ result = result.filter((p) => {
106
+ const name = String(p.name || p.title || '').toLowerCase();
107
+ const desc = String(p.description || '').toLowerCase();
108
+ const brand = String(p.brand || '').toLowerCase();
109
+ const cat = String(p.category || '').toLowerCase();
110
+ const tags = Array.isArray(p.tags) ? p.tags.join(' ').toLowerCase() : '';
111
+ return name.includes(q) || desc.includes(q) || brand.includes(q) || cat.includes(q) || tags.includes(q);
112
+ });
113
+ }
114
+ return result;
115
+ }, [products, activeCategory, searchQuery]);
116
+ // Total count calculation
117
+ const totalItems = totalProducts !== undefined ? totalProducts : filteredProducts.length;
118
+ const totalPages = Math.max(1, Math.ceil(totalItems / activePageSize));
119
+ // Products to render based on pagination mode
120
+ const displayedProducts = (0, react_1.useMemo)(() => {
121
+ if (!enablePagination) {
122
+ return filteredProducts;
123
+ }
124
+ if (paginationVariant === 'load-more') {
125
+ return filteredProducts.slice(0, loadedCount);
126
+ }
127
+ const startIndex = (activePage - 1) * activePageSize;
128
+ const endIndex = startIndex + activePageSize;
129
+ return filteredProducts.slice(startIndex, endIndex);
130
+ }, [filteredProducts, enablePagination, paginationVariant, loadedCount, activePage, activePageSize]);
131
+ // Smart page numbers calculation with ellipsis (e.g. 1 ... 4 5 6 ... 10)
132
+ const visiblePages = (0, react_1.useMemo)(() => {
133
+ if (totalPages <= 7) {
134
+ return Array.from({ length: totalPages }, (_, i) => i + 1);
135
+ }
136
+ if (activePage <= 4) {
137
+ return [1, 2, 3, 4, 5, 'ellipsis', totalPages];
138
+ }
139
+ if (activePage >= totalPages - 3) {
140
+ return [1, 'ellipsis', totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages];
141
+ }
142
+ return [1, 'ellipsis', activePage - 1, activePage, activePage + 1, 'ellipsis', totalPages];
143
+ }, [activePage, totalPages]);
144
+ // Page navigation handler
145
+ const handlePageChange = (newPage) => {
146
+ if (newPage < 1 || newPage > totalPages || newPage === activePage)
147
+ return;
148
+ if (controlledPage === undefined) {
149
+ setInternalPage(newPage);
150
+ }
151
+ onPageChange?.(newPage);
152
+ if (scrollToTopOnPageChange && sectionRef.current) {
153
+ sectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
154
+ }
155
+ };
156
+ const handleLoadMore = () => {
157
+ setLoadedCount((prev) => Math.min(prev + activePageSize, filteredProducts.length));
158
+ };
38
159
  // Map column config to CSS grid class names
39
160
  const gridColClasses = (0, react_1.useMemo)(() => {
40
161
  const m = columns.mobile || 1;
@@ -45,19 +166,58 @@ function EditableProductGrid({ sectionPath = 'home', listPath = 'products', titl
45
166
  const dClass = d === 3 ? 'lg:grid-cols-3' : d === 2 ? 'lg:grid-cols-2' : 'lg:grid-cols-4';
46
167
  return `${mClass} ${tClass} ${dClass}`;
47
168
  }, [columns]);
48
- 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-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) => {
169
+ const shouldShowPagination = enablePagination && !(hidePaginationOnSinglePage && totalPages <= 1);
170
+ return ((0, jsx_runtime_1.jsxs)("section", { ref: sectionRef, "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) => {
49
171
  const isActive = activeCategory.toLowerCase() === cat.toLowerCase();
50
172
  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
51
173
  ? 'bg-slate-900 text-white dark:bg-white dark:text-slate-950 shadow-md scale-102 font-extrabold'
52
174
  : '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));
53
- }) }))] }), filteredProducts.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { ...(listPath ? { 'data-preview-list-path': listPath } : {}), className: `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`, children: filteredProducts.map((product, idx) => {
175
+ }) }))] }), showSearch && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-8 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative flex-1 max-w-md", children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400", 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: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }) }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: searchQuery, onChange: (e) => {
176
+ setSearchQuery(e.target.value);
177
+ onSearchChange?.(e.target.value);
178
+ }, 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: () => {
179
+ setSearchQuery('');
180
+ onSearchChange?.('');
181
+ }, 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.jsx)("div", { ...(listPath ? { 'data-preview-list-path': listPath } : {}), className: displayedProducts.length > 0
182
+ ? `deneb-product-grid grid ${gridColClasses} gap-6 sm:gap-8`
183
+ : 'deneb-product-grid-empty w-full', children: displayedProducts.length > 0 ? (displayedProducts.map((product, idx) => {
184
+ // Determine true index in baseProducts for exact Fivora visual editing focus targeting
185
+ const baseIndex = baseProducts.findIndex((bp) => (bp.id && product.id ? String(bp.id) === String(product.id) : false) ||
186
+ (bp.name && product.name ? bp.name === product.name : false) ||
187
+ (bp.title && product.title ? bp.title === product.title : false));
188
+ const effectiveIndex = baseIndex !== -1 ? baseIndex : (activePage - 1) * activePageSize + idx;
54
189
  const itemPath = listPath
55
- ? `${listPath}[${idx}]`
56
- : `${sectionPath}.${cardPrefix}${idx + 1}`;
190
+ ? `${listPath}[${effectiveIndex}]`
191
+ : `${sectionPath}.${cardPrefix}${effectiveIndex + 1}`;
57
192
  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, 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));
58
- }) })) : (
59
- /* Empty State */
60
- (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" })] }))] }));
193
+ })) : (
194
+ /* Empty State — strictly inside the listPath container */
195
+ (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
196
+ ? `No products matched "${searchQuery}"${activeCategory !== 'All' ? ` in category "${activeCategory}"` : ''}.`
197
+ : `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' ? (
198
+ /* Progressive "Load More" Variant */
199
+ (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)"] }))] })) : (
200
+ /* Numbered & Simple Pagination Navigation */
201
+ (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) => {
202
+ const newSize = Number(e.target.value);
203
+ setActivePageSize(newSize);
204
+ setInternalPage(1);
205
+ onPageChange?.(1);
206
+ }, 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
207
+ ? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
208
+ : '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) => {
209
+ if (pageNum === 'ellipsis') {
210
+ 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}`));
211
+ }
212
+ const isCurrent = pageNum === activePage;
213
+ 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
214
+ ? 'bg-slate-900 text-white dark:bg-lime-400 dark:text-slate-950 font-black shadow-md scale-105'
215
+ : '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));
216
+ }) })) : (
217
+ /* Simple Indicator */
218
+ (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
219
+ ? 'opacity-40 cursor-not-allowed border-slate-200 dark:border-slate-800 text-slate-400'
220
+ : '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" }) })] })] })] })) }))] }));
61
221
  }
62
222
  // Canonical alias
63
223
  exports.ProductGrid = EditableProductGrid;
@@ -81,7 +81,7 @@ function TestimonialCard({ item, index, maxStars, cardClassName, }) {
81
81
  const authorAvatar = String(currentItem.avatar ||
82
82
  'https://images.unsplash.com/photo-1583394838336-acd977736f90?q=80&w=300&auto=format&fit=crop');
83
83
  const tagText = String(currentItem.tag || '');
84
- return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `testimonials.testimonials[${index}]`, className: `relative p-5 sm:p-7 lg:p-8 rounded-2xl sm:rounded-3xl bg-white border border-slate-200 hover:border-slate-300 shadow-sm hover:shadow-md dark:bg-gradient-to-b dark:from-slate-900/70 dark:to-slate-950/90 dark:border-slate-800/80 dark:hover:border-slate-700 dark:shadow-xl transition-all duration-300 flex flex-col justify-between space-y-5 min-w-0 ${cardClassName || ''}`.trim(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4 min-w-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [Boolean(currentItem.tag) && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `testimonials.testimonials[${index}].tag`, className: "px-2.5 py-1 rounded-full bg-slate-100 text-slate-700 border border-slate-200 dark:bg-slate-800/80 dark:text-lime-400 dark:border-lime-400/20 text-[10px] sm:text-[11px] uppercase tracking-wider font-extrabold shrink-0", children: tagText })), (0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-1.5 p-1 -mr-1 rounded-lg cursor-pointer transition-all hover:bg-amber-500/10 group ml-auto shrink-0", onClick: handleFocusRating, title: `Rating: ${currentRating} of ${maxStars} (Click to edit)`, children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-[2px]", "data-fivora-stars-row": "true", children: Array.from({ length: maxStars }).map((_, sIdx) => ((0, jsx_runtime_1.jsx)(StarSvg, { filled: sIdx < currentRating, size: 14 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { ref: ratingRef, "data-preview-field-path": `testimonials.testimonials[${index}].rating`, "data-fivora-rating-text": "true", className: "text-xs font-black text-amber-600 bg-amber-500/10 px-1.5 py-0.5 rounded-md border border-amber-500/20 dark:text-amber-400 group-hover:bg-amber-500/20 transition-colors tabular-nums", title: "Star count (1-5)", children: currentRating })] })] }), (0, jsx_runtime_1.jsxs)("blockquote", { "data-preview-field-path": `testimonials.testimonials[${index}].quote`, className: "font-serif-italic text-sm sm:text-base text-slate-700 dark:text-slate-300 leading-relaxed break-words", children: ["\u201C", quoteText, "\u201D"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pt-4 border-t border-slate-100 dark:border-slate-800 flex items-center gap-3 min-w-0", children: [(0, jsx_runtime_1.jsx)("img", { src: authorAvatar, alt: authorName, "data-preview-field-path": `testimonials.testimonials[${index}].avatar`, className: "w-10 h-10 sm:w-12 sm:h-12 rounded-full object-cover border border-slate-200 dark:border-slate-700 shadow-sm shrink-0" }), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("h4", { "data-preview-field-path": `testimonials.testimonials[${index}].author`, className: "font-heading font-black text-sm sm:text-base text-slate-900 dark:text-white truncate", children: authorName }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `testimonials.testimonials[${index}].role`, className: "text-xs text-slate-500 dark:text-slate-400 font-medium leading-snug line-clamp-2", children: authorRole })] })] })] }));
84
+ return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `testimonials.testimonials[${index}]`, className: `relative p-5 sm:p-7 lg:p-8 rounded-2xl sm:rounded-3xl bg-white border border-slate-200 hover:border-slate-300 shadow-sm hover:shadow-md dark:bg-gradient-to-b dark:from-slate-900/70 dark:to-slate-950/90 dark:border-slate-800/80 dark:hover:border-slate-700 dark:shadow-xl transition-[transform,box-shadow] duration-200 flex flex-col justify-between space-y-5 min-w-0 ${cardClassName || ''}`.trim(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4 min-w-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [Boolean(currentItem.tag) && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `testimonials.testimonials[${index}].tag`, className: "px-2.5 py-1 rounded-full bg-slate-100 text-slate-700 border border-slate-200 dark:bg-slate-800/80 dark:text-lime-400 dark:border-lime-400/20 text-[10px] sm:text-[11px] uppercase tracking-wider font-extrabold shrink-0", children: tagText })), (0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-1.5 p-1 -mr-1 rounded-lg cursor-pointer transition-all hover:bg-amber-500/10 group ml-auto shrink-0", onClick: handleFocusRating, title: `Rating: ${currentRating} of ${maxStars} (Click to edit)`, children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-[2px]", "data-fivora-stars-row": "true", children: Array.from({ length: maxStars }).map((_, sIdx) => ((0, jsx_runtime_1.jsx)(StarSvg, { filled: sIdx < currentRating, size: 14 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { ref: ratingRef, "data-preview-field-path": `testimonials.testimonials[${index}].rating`, "data-fivora-rating-text": "true", className: "text-xs font-black text-amber-600 bg-amber-500/10 px-1.5 py-0.5 rounded-md border border-amber-500/20 dark:text-amber-400 group-hover:bg-amber-500/20 transition-colors tabular-nums", title: "Star count (1-5)", children: currentRating })] })] }), (0, jsx_runtime_1.jsxs)("blockquote", { "data-preview-field-path": `testimonials.testimonials[${index}].quote`, className: "font-serif-italic text-sm sm:text-base text-slate-700 dark:text-slate-300 leading-relaxed break-words", children: ["\u201C", quoteText, "\u201D"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pt-4 border-t border-slate-100 dark:border-slate-800 flex items-center gap-3 min-w-0", children: [(0, jsx_runtime_1.jsx)("img", { src: authorAvatar, alt: authorName, "data-preview-field-path": `testimonials.testimonials[${index}].avatar`, className: "w-10 h-10 sm:w-12 sm:h-12 rounded-full object-cover border border-slate-200 dark:border-slate-700 shadow-sm shrink-0" }), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("h4", { "data-preview-field-path": `testimonials.testimonials[${index}].author`, className: "font-heading font-black text-sm sm:text-base text-slate-900 dark:text-white truncate", children: authorName }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `testimonials.testimonials[${index}].role`, className: "text-xs text-slate-500 dark:text-slate-400 font-medium leading-snug line-clamp-2", children: authorRole })] })] })] }));
85
85
  }
86
86
  function EditableTestimonialSection({ basePath = 'testimonials', badge = 'WORDS OF ACCLAIM', heading = 'WHAT THE CRITICS ARE SAYING', subheading = 'Reflections from international cuppers, culinary masters, and dedicated regulars.', testimonials = DEFAULT_TESTIMONIALS, maxStars = 5, className = '', cardClassName = '', style, ...props }) {
87
87
  const items = (0, react_1.useMemo)(() => {
@@ -102,5 +102,7 @@ function EditableTestimonialSection({ basePath = 'testimonials', badge = 'WORDS
102
102
  };
103
103
  });
104
104
  }, [testimonials]);
105
- return ((0, jsx_runtime_1.jsx)("section", { "data-preview-page-key": basePath, "data-design-section": basePath, className: `editable-testimonial-section w-full py-12 sm:py-20 px-3 sm:px-6 lg:px-8 ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "w-full mx-auto", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center max-w-3xl mx-auto space-y-3 mb-10 sm:mb-14 px-2", children: [badge && ((0, jsx_runtime_1.jsx)("div", { className: "inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-slate-100 text-slate-800 border border-slate-200 dark:bg-slate-800/80 dark:text-lime-400 dark:border-lime-400/20 text-xs uppercase tracking-widest font-black shadow-sm", children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `testimonials.badge`, children: badge }) })), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `testimonials.heading`, className: "font-heading text-2xl xs:text-3xl sm:text-4xl lg:text-5xl font-black text-slate-900 dark:text-white tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `testimonials.subheading`, className: "text-xs sm:text-base text-slate-600 dark:text-slate-400 leading-relaxed max-w-2xl mx-auto", children: subheading })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `testimonials.testimonials`, className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5 sm:gap-7 [grid-template-columns:repeat(auto-fit,minmax(min(100%,290px),1fr))] w-full", children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(TestimonialCard, { item: item, index: index, maxStars: maxStars, cardClassName: cardClassName }, item.id || String(index)))) })] }) }));
105
+ const hasCustomPy = /(^|\s)(p|py|pt)-/.test(className);
106
+ const defaultPadding = hasCustomPy ? '' : 'py-6 sm:py-8';
107
+ return ((0, jsx_runtime_1.jsx)("section", { "data-preview-page-key": basePath, "data-design-section": basePath, className: `editable-testimonial-section w-full ${defaultPadding} px-3 sm:px-6 lg:px-8 ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "w-full mx-auto", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center max-w-3xl mx-auto space-y-3 mb-6 sm:mb-8 px-2", children: [badge && ((0, jsx_runtime_1.jsx)("div", { className: "inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-slate-100 text-slate-800 border border-slate-200 dark:bg-slate-800/80 dark:text-lime-400 dark:border-lime-400/20 text-xs uppercase tracking-widest font-black shadow-sm", children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `testimonials.badge`, children: badge }) })), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `testimonials.heading`, className: "font-heading text-2xl xs:text-3xl sm:text-4xl lg:text-5xl font-black text-slate-900 dark:text-white tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `testimonials.subheading`, className: "text-xs sm:text-base text-slate-600 dark:text-slate-400 leading-relaxed max-w-2xl mx-auto", children: subheading })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `testimonials.testimonials`, className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5 sm:gap-7 [grid-template-columns:repeat(auto-fit,minmax(min(100%,290px),1fr))] w-full", children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(TestimonialCard, { item: item, index: index, maxStars: maxStars, cardClassName: cardClassName }, item.id || String(index)))) })] }) }));
106
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.66",
3
+ "version": "2.0.68",
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.66"
53
+ "@deneb-ui/core": "^2.0.68"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.0.0 || ^19.0.0",