@deneb-ui/ui 2.0.37 → 2.0.39

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.
@@ -60,7 +60,7 @@ function GoogleStarSvg({ filled = true, size = 18 }) {
60
60
  flexShrink: 0,
61
61
  }, "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" }) }));
62
62
  }
63
- function FeedbackCard({ item, idx, basePath, maxStars, badgeIcon, cardClassName, }) {
63
+ function FeedbackCard({ item, index, basePath, maxStars, badgeIcon, cardClassName, }) {
64
64
  const currentItem = item || {};
65
65
  const rawRating = currentItem.rating != null && currentItem.rating !== '' ? currentItem.rating : 5;
66
66
  const initialRating = Math.min(Math.max(Math.round(Number(rawRating) || 5), 1), maxStars);
@@ -100,7 +100,7 @@ function FeedbackCard({ item, idx, basePath, maxStars, badgeIcon, cardClassName,
100
100
  'https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=300&auto=format&fit=crop');
101
101
  const reviewerComment = String(currentItem.comment || '');
102
102
  const reviewerDate = String(currentItem.date || 'Recent review');
103
- return ((0, jsx_runtime_1.jsx)("div", { "data-preview-item-path": `${basePath}.feedbacks[${idx}]`, className: `p-6 sm:p-7 rounded-3xl bg-[#ffffff] border border-[#3d2114]/12 shadow-sm hover:shadow-md 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": `${basePath}.feedbacks[${idx}].avatar`, className: "w-11 h-11 rounded-full object-cover border border-[#3d2114]/15 shadow-sm" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.feedbacks[${idx}].name`, className: "block text-sm font-black text-[#2b170e]", children: reviewerName }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.feedbacks[${idx}].date`, className: "block text-xs text-[#8c7a6e]", children: reviewerDate })] })] }), (0, jsx_runtime_1.jsx)("img", { src: badgeIcon || DEFAULT_GOOGLE_ICON, alt: "Google", className: "w-5 h-5 object-contain opacity-90" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-2 pt-1", children: [(0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-2 p-1 -ml-1 rounded-lg cursor-pointer transition-all hover:bg-[#fa7014]/10 group", 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)(GoogleStarSvg, { filled: sIdx < currentRating, size: 18 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { ref: ratingRef, "data-preview-field-path": `${basePath}.feedbacks[${idx}].rating`, "data-fivora-rating-text": "true", className: "text-xs font-black text-[#fa7014] bg-[#fa7014]/10 px-1.5 py-0.5 rounded-md border border-[#fa7014]/20 group-hover:bg-[#fa7014]/20 transition-colors tabular-nums", title: "Star count (1-5)", children: currentRating })] }), (0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-1 text-[11px] font-bold text-[#1a73e8] bg-[#e8f0fe] px-2.5 py-1 rounded-full border border-[#1a73e8]/20", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3 h-3 fill-current", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }), (0, jsx_runtime_1.jsx)("span", { children: "Verified" })] })] }), (0, jsx_runtime_1.jsxs)("p", { "data-preview-field-path": `${basePath}.feedbacks[${idx}].comment`, className: "text-sm sm:text-base text-[#3d2114] leading-relaxed italic font-normal pt-1", children: ["\u201C", reviewerComment, "\u201D"] })] }) }));
103
+ return ((0, jsx_runtime_1.jsx)("div", { "data-preview-item-path": `feedback.feedbacks[${index}]`, className: `p-6 sm:p-7 rounded-3xl bg-[#ffffff] border border-[#3d2114]/12 shadow-sm hover:shadow-md 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-[#3d2114]/15 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-[#2b170e]", children: reviewerName }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `feedback.feedbacks[${index}].date`, className: "block text-xs text-[#8c7a6e]", children: reviewerDate })] })] }), (0, jsx_runtime_1.jsx)("img", { src: badgeIcon || DEFAULT_GOOGLE_ICON, alt: "Google", className: "w-5 h-5 object-contain opacity-90" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-2 pt-1", children: [(0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-2 p-1 -ml-1 rounded-lg cursor-pointer transition-all hover:bg-[#fa7014]/10 group", 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)(GoogleStarSvg, { filled: sIdx < currentRating, size: 18 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { ref: ratingRef, "data-preview-field-path": `feedback.feedbacks[${index}].rating`, "data-fivora-rating-text": "true", className: "text-xs font-black text-[#fa7014] bg-[#fa7014]/10 px-1.5 py-0.5 rounded-md border border-[#fa7014]/20 group-hover:bg-[#fa7014]/20 transition-colors tabular-nums", title: "Star count (1-5)", children: currentRating })] }), (0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-1 text-[11px] font-bold text-[#1a73e8] bg-[#e8f0fe] px-2.5 py-1 rounded-full border border-[#1a73e8]/20", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-3 h-3 fill-current", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }), (0, jsx_runtime_1.jsx)("span", { children: "Verified" })] })] }), (0, jsx_runtime_1.jsxs)("p", { "data-preview-field-path": `feedback.feedbacks[${index}].comment`, className: "text-sm sm:text-base text-[#3d2114] leading-relaxed italic font-normal pt-1", children: ["\u201C", reviewerComment, "\u201D"] })] }) }));
104
104
  }
105
105
  function EditableGoogleFeedback({ basePath = 'feedback', badgeIcon = DEFAULT_GOOGLE_ICON, badgeTitle = 'Google Reviews', badgeRating = '4.9', badgeReviewsCount = '340+ Verified Reviews', heading = 'PRAISED BY COFFEE LOVERS', subheading = 'Real experiences from patrons who visit our roastery parlor and brew our micro-lots.', feedbacks = DEFAULT_FEEDBACKS, maxStars = 5, className = '', cardClassName = '', style, ...props }) {
106
106
  const items = (0, react_1.useMemo)(() => {
@@ -108,18 +108,18 @@ function EditableGoogleFeedback({ basePath = 'feedback', badgeIcon = DEFAULT_GOO
108
108
  if (list.length === 0) {
109
109
  return DEFAULT_FEEDBACKS;
110
110
  }
111
- return list.map((item, idx) => {
112
- const fallback = DEFAULT_FEEDBACKS[idx % DEFAULT_FEEDBACKS.length] || DEFAULT_FEEDBACKS[0];
111
+ return list.map((item, index) => {
112
+ const fallback = DEFAULT_FEEDBACKS[index % DEFAULT_FEEDBACKS.length] || DEFAULT_FEEDBACKS[0];
113
113
  if (!item || typeof item !== 'object') {
114
- return { ...fallback, id: `fallback-${idx}` };
114
+ return { ...fallback, id: `fallback-${index}` };
115
115
  }
116
116
  return {
117
117
  ...fallback,
118
118
  ...item,
119
- id: item.id || `feedback-${idx}`,
119
+ id: item.id || `feedback-${index}`,
120
120
  rating: item.rating !== undefined && item.rating !== null ? item.rating : 5,
121
121
  };
122
122
  });
123
123
  }, [feedbacks]);
124
- return ((0, jsx_runtime_1.jsx)("section", { "data-design-section": "google-feedback", className: `editable-google-feedback w-full ${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-[#ede0d4]/80 border border-[#3d2114]/15 shadow-sm", children: [(0, jsx_runtime_1.jsx)("img", { src: badgeIcon || DEFAULT_GOOGLE_ICON, alt: "Google", "data-preview-field-path": `${basePath}.badgeIcon`, className: "w-4 h-4 object-contain" }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.badgeTitle`, className: "text-xs uppercase tracking-widest font-black text-[#3d2114]", children: badgeTitle }), (0, jsx_runtime_1.jsx)("span", { className: "w-1 h-1 rounded-full bg-[#84431b]" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.badgeRating`, className: "text-xs font-black text-[#84431b]", 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-[#786154] font-medium hidden sm:inline", children: ["(", (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.badgeReviewsCount`, children: badgeReviewsCount }), ")"] })] }), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `${basePath}.heading`, className: "font-heading text-2xl xs:text-3xl sm:text-4xl lg:text-5xl font-black text-[#2b170e] tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `${basePath}.subheading`, className: "text-xs sm:text-base text-[#5c493f] 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": `${basePath}.badgeRating`, className: "font-heading text-4xl font-black text-[#84431b]", 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": `${basePath}.badgeReviewsCount`, className: "text-xs text-[#786154] font-bold", children: badgeReviewsCount })] })] }) })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `${basePath}.feedbacks`, className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: items.map((item, idx) => ((0, jsx_runtime_1.jsx)(FeedbackCard, { item: item, idx: idx, basePath: basePath, maxStars: maxStars, badgeIcon: badgeIcon, cardClassName: cardClassName }, item.id || `feedback-${idx}`))) })] }) }));
124
+ return ((0, jsx_runtime_1.jsx)("section", { "data-design-section": "google-feedback", className: `editable-google-feedback w-full ${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-[#ede0d4]/80 border border-[#3d2114]/15 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-[#3d2114]", children: badgeTitle }), (0, jsx_runtime_1.jsx)("span", { className: "w-1 h-1 rounded-full bg-[#84431b]" }), (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-[#84431b]", 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-[#786154] 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-[#2b170e] tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `feedback.subheading`, className: "text-xs sm:text-base text-[#5c493f] 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-[#84431b]", 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-[#786154] 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, basePath: basePath, maxStars: maxStars, badgeIcon: badgeIcon, cardClassName: cardClassName }, item.id || `feedback-${index}`))) })] }) }));
125
125
  }
@@ -41,7 +41,7 @@ function StarSvg({ filled = true, size = 16 }) {
41
41
  flexShrink: 0,
42
42
  }, "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" }) }));
43
43
  }
44
- function TestimonialCard({ item, idx, basePath, maxStars, cardClassName, }) {
44
+ function TestimonialCard({ item, index, basePath, maxStars, cardClassName, }) {
45
45
  const currentItem = item || {};
46
46
  const rawRating = currentItem.rating != null && currentItem.rating !== '' ? currentItem.rating : 5;
47
47
  const initialRating = Math.min(Math.max(Math.round(Number(rawRating) || 5), 1), maxStars);
@@ -80,7 +80,7 @@ function TestimonialCard({ item, idx, basePath, maxStars, cardClassName, }) {
80
80
  const authorAvatar = String(currentItem.avatar ||
81
81
  'https://images.unsplash.com/photo-1583394838336-acd977736f90?q=80&w=300&auto=format&fit=crop');
82
82
  const tagText = String(currentItem.tag || '');
83
- return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `${basePath}.testimonials[${idx}]`, className: `relative p-7 sm:p-8 rounded-3xl bg-[#ffffff] border border-[#3d2114]/12 shadow-md hover:shadow-xl transition-all duration-300 flex flex-col justify-between space-y-6 ${cardClassName || ''}`.trim(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [Boolean(currentItem.tag) && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.testimonials[${idx}].tag`, className: "px-3 py-1 rounded-full bg-[#f5ebe1] text-[#84431b] text-[11px] uppercase tracking-wider font-extrabold border border-[#3d2114]/10", children: tagText })), (0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-2 p-1 -mr-1 rounded-lg cursor-pointer transition-all hover:bg-[#fa7014]/10 group ml-auto", 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: 16 }, sIdx))) }), (0, jsx_runtime_1.jsx)("span", { ref: ratingRef, "data-preview-field-path": `${basePath}.testimonials[${idx}].rating`, "data-fivora-rating-text": "true", className: "text-xs font-black text-[#fa7014] bg-[#fa7014]/10 px-1.5 py-0.5 rounded-md border border-[#fa7014]/20 group-hover:bg-[#fa7014]/20 transition-colors tabular-nums", title: "Star count (1-5)", children: currentRating })] })] }), (0, jsx_runtime_1.jsxs)("blockquote", { "data-preview-field-path": `${basePath}.testimonials[${idx}].quote`, className: "font-serif-italic text-base sm:text-lg text-[#2b170e] leading-relaxed", children: ["\u201C", quoteText, "\u201D"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pt-4 border-t border-[#3d2114]/10 flex items-center gap-3.5", children: [(0, jsx_runtime_1.jsx)("img", { src: authorAvatar, alt: authorName, "data-preview-field-path": `${basePath}.testimonials[${idx}].avatar`, className: "w-12 h-12 rounded-full object-cover border border-[#3d2114]/15 shadow-sm" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { "data-preview-field-path": `${basePath}.testimonials[${idx}].author`, className: "font-heading font-black text-sm sm:text-base text-[#2b170e]", children: authorName }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `${basePath}.testimonials[${idx}].role`, className: "text-xs text-[#786154] font-medium leading-snug", children: authorRole })] })] })] }));
83
+ return ((0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `testimonials.testimonials[${index}]`, className: `relative p-7 sm:p-8 rounded-3xl bg-[#ffffff] border border-[#3d2114]/12 shadow-md hover:shadow-xl transition-all duration-300 flex flex-col justify-between space-y-6 ${cardClassName || ''}`.trim(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex 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-3 py-1 rounded-full bg-[#f5ebe1] text-[#84431b] text-[11px] uppercase tracking-wider font-extrabold border border-[#3d2114]/10", children: tagText })), (0, jsx_runtime_1.jsxs)("div", { className: "inline-flex items-center gap-2 p-1 -mr-1 rounded-lg cursor-pointer transition-all hover:bg-[#fa7014]/10 group ml-auto", 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: 16 }, 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-[#fa7014] bg-[#fa7014]/10 px-1.5 py-0.5 rounded-md border border-[#fa7014]/20 group-hover:bg-[#fa7014]/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-base sm:text-lg text-[#2b170e] leading-relaxed", children: ["\u201C", quoteText, "\u201D"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pt-4 border-t border-[#3d2114]/10 flex items-center gap-3.5", children: [(0, jsx_runtime_1.jsx)("img", { src: authorAvatar, alt: authorName, "data-preview-field-path": `testimonials.testimonials[${index}].avatar`, className: "w-12 h-12 rounded-full object-cover border border-[#3d2114]/15 shadow-sm" }), (0, jsx_runtime_1.jsxs)("div", { 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-[#2b170e]", children: authorName }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `testimonials.testimonials[${index}].role`, className: "text-xs text-[#786154] font-medium leading-snug", children: authorRole })] })] })] }));
84
84
  }
85
85
  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 }) {
86
86
  const items = (0, react_1.useMemo)(() => {
@@ -88,18 +88,18 @@ function EditableTestimonialSection({ basePath = 'testimonials', badge = 'WORDS
88
88
  if (list.length === 0) {
89
89
  return DEFAULT_TESTIMONIALS;
90
90
  }
91
- return list.map((item, idx) => {
92
- const fallback = DEFAULT_TESTIMONIALS[idx % DEFAULT_TESTIMONIALS.length] || DEFAULT_TESTIMONIALS[0];
91
+ return list.map((item, index) => {
92
+ const fallback = DEFAULT_TESTIMONIALS[index % DEFAULT_TESTIMONIALS.length] || DEFAULT_TESTIMONIALS[0];
93
93
  if (!item || typeof item !== 'object') {
94
- return { ...fallback, id: `fallback-${idx}` };
94
+ return { ...fallback, id: `fallback-${index}` };
95
95
  }
96
96
  return {
97
97
  ...fallback,
98
98
  ...item,
99
- id: item.id || `testimonial-${idx}`,
99
+ id: item.id || `testimonial-${index}`,
100
100
  rating: item.rating !== undefined && item.rating !== null ? item.rating : 5,
101
101
  };
102
102
  });
103
103
  }, [testimonials]);
104
- return ((0, jsx_runtime_1.jsx)("section", { "data-design-section": "testimonials", className: `editable-testimonial-section w-full py-16 sm:py-24 px-4 sm:px-6 lg:px-10 ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-[1600px] mx-auto", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center max-w-3xl mx-auto space-y-3 mb-12 sm:mb-16", children: [badge && ((0, jsx_runtime_1.jsx)("div", { className: "inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[#ede0d4] text-[#3d2114] text-xs uppercase tracking-widest font-black border border-[#3d2114]/15", children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": `${basePath}.badge`, children: badge }) })), (0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": `${basePath}.heading`, className: "font-heading text-2xl xs:text-3xl sm:text-4xl lg:text-5xl font-black text-[#2b170e] tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `${basePath}.subheading`, className: "text-xs sm:text-base text-[#5c493f] leading-relaxed", children: subheading })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `${basePath}.testimonials`, className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8", children: items.map((item, idx) => ((0, jsx_runtime_1.jsx)(TestimonialCard, { item: item, idx: idx, basePath: basePath, maxStars: maxStars, cardClassName: cardClassName }, item.id || `testimonial-${idx}`))) })] }) }));
104
+ return ((0, jsx_runtime_1.jsx)("section", { "data-design-section": "testimonials", className: `editable-testimonial-section w-full py-16 sm:py-24 px-4 sm:px-6 lg:px-10 ${className}`.trim(), style: style, ...props, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-[1600px] mx-auto", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center max-w-3xl mx-auto space-y-3 mb-12 sm:mb-16", children: [badge && ((0, jsx_runtime_1.jsx)("div", { className: "inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[#ede0d4] text-[#3d2114] text-xs uppercase tracking-widest font-black border border-[#3d2114]/15", 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-[#2b170e] tracking-tight", children: heading }), (0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": `testimonials.subheading`, className: "text-xs sm:text-base text-[#5c493f] leading-relaxed", children: subheading })] }), (0, jsx_runtime_1.jsx)("div", { "data-preview-list-path": `testimonials.testimonials`, className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8", children: items.map((item, index) => ((0, jsx_runtime_1.jsx)(TestimonialCard, { item: item, index: index, basePath: basePath, maxStars: maxStars, cardClassName: cardClassName }, item.id || `testimonial-${index}`))) })] }) }));
105
105
  }
@@ -1,4 +1,6 @@
1
1
  import React, { type ReactNode } from 'react';
2
+ import type { ProductItem } from './EditableProductCard';
3
+ import type { ServiceItem } from './EditableServiceCard';
2
4
  export declare const DENEB_PREVIEW_DATA_MESSAGE = "DENEB_PREVIEW_SITE_DATA";
3
5
  export declare const PREVIEW_DATA_MESSAGE = "FIVORA_PREVIEW_SITE_DATA";
4
6
  export declare const LEGACY_PREVIEW_DATA_MESSAGE: string;
@@ -11,10 +13,34 @@ export declare const LEGACY_PREVIEW_FOCUS_MESSAGE: string;
11
13
  export declare const PREVIEW_FIELD_ATTRIBUTE = "data-preview-field-path";
12
14
  export { STYLE_PATCH_MESSAGE, DENEB_STYLE_PATCH_MESSAGE } from '@deneb-ui/core';
13
15
  export type GenericRecord = Record<string, any>;
16
+ export interface SiteDataApiConfig {
17
+ baseUrl?: string | null;
18
+ catalogUrl?: string | null;
19
+ contactUrl?: string | null;
20
+ analyticsUrl?: string | null;
21
+ [key: string]: unknown;
22
+ }
23
+ export interface SiteDataProject {
24
+ id?: string | null;
25
+ slug?: string | null;
26
+ title?: string | null;
27
+ status?: string | null;
28
+ [key: string]: unknown;
29
+ }
30
+ export interface SiteInstanceData {
31
+ id?: string | null;
32
+ slug?: string | null;
33
+ domain?: string | null;
34
+ subdomain?: string | null;
35
+ customDomain?: string | null;
36
+ liveUrl?: string | null;
37
+ [key: string]: unknown;
38
+ }
14
39
  export type SiteData = {
15
- project?: {
16
- id?: string | null;
17
- } | null;
40
+ project?: SiteDataProject | null;
41
+ siteInstance?: SiteInstanceData | null;
42
+ api?: SiteDataApiConfig | null;
43
+ shop?: GenericRecord | null;
18
44
  merchant?: GenericRecord | null;
19
45
  template?: {
20
46
  structure?: {
@@ -26,6 +52,8 @@ export type SiteData = {
26
52
  requiredPages?: string[] | null;
27
53
  } | null;
28
54
  content?: GenericRecord | null;
55
+ media?: Record<string, string[]> | null;
56
+ seo?: GenericRecord | null;
29
57
  styles?: GenericRecord | null;
30
58
  [key: string]: unknown;
31
59
  };
@@ -44,8 +72,17 @@ export interface SiteDataProviderProps<T = SiteData> {
44
72
  children: ReactNode;
45
73
  initialSiteData?: T;
46
74
  fallbackSiteData?: T;
75
+ /**
76
+ * Optional custom live catalog endpoint.
77
+ * If omitted, defaults to `/api/site-catalog/${slug}/live-data`.
78
+ */
79
+ liveCatalogEndpoint?: string;
80
+ /**
81
+ * Optional site slug for live rehydration. If omitted, resolved from initialSiteData.
82
+ */
83
+ siteSlug?: string;
47
84
  }
48
- export declare function SiteDataProvider<T extends SiteData = SiteData>({ children, initialSiteData, fallbackSiteData, }: SiteDataProviderProps<T>): React.JSX.Element;
85
+ export declare function SiteDataProvider<T extends SiteData = SiteData>({ children, initialSiteData, fallbackSiteData, liveCatalogEndpoint, siteSlug, }: SiteDataProviderProps<T>): React.JSX.Element;
49
86
  export declare function useSiteData<T = SiteData>(): T;
50
87
  export declare function contentObject(value: unknown): GenericRecord;
51
88
  export declare function contentText(value: unknown): string;
@@ -62,3 +99,27 @@ export declare const DenebDataProvider: typeof SiteDataProvider;
62
99
  export declare const useDenebData: typeof useSiteData;
63
100
  export declare const DenebDataContext: React.Context<SiteData>;
64
101
  export type DenebData = SiteData;
102
+ /**
103
+ * Hook to retrieve products cleanly from SiteData, supporting both
104
+ * top-level content.products and nested content.home.products.
105
+ */
106
+ export declare function useProducts(fallback?: ProductItem[]): ProductItem[];
107
+ /**
108
+ * Hook to retrieve services cleanly from SiteData, supporting both
109
+ * top-level content.services and nested content.home.services.
110
+ */
111
+ export declare function useServices(fallback?: ServiceItem[]): ServiceItem[];
112
+ /**
113
+ * Hook to access official Fivora backend API endpoints (catalogUrl, contactUrl, analyticsUrl).
114
+ */
115
+ export declare function useSiteApi(): SiteDataApiConfig | null;
116
+ /**
117
+ * Hook to access full catalog metadata and live status.
118
+ */
119
+ export declare function useSiteCatalog(): {
120
+ products: ProductItem[];
121
+ services: ServiceItem[];
122
+ project: SiteDataProject | null;
123
+ siteInstance: SiteInstanceData | null;
124
+ api: SiteDataApiConfig | null;
125
+ };
@@ -16,6 +16,10 @@ exports.contentNumber = contentNumber;
16
16
  exports.parseFieldPath = parseFieldPath;
17
17
  exports.getFieldStyle = getFieldStyle;
18
18
  exports.useFieldStyle = useFieldStyle;
19
+ exports.useProducts = useProducts;
20
+ exports.useServices = useServices;
21
+ exports.useSiteApi = useSiteApi;
22
+ exports.useSiteCatalog = useSiteCatalog;
19
23
  const jsx_runtime_1 = require("react/jsx-runtime");
20
24
  const react_1 = require("react");
21
25
  const core_1 = require("@deneb-ui/core");
@@ -167,13 +171,92 @@ function readCachedSiteData() {
167
171
  return null;
168
172
  }
169
173
  }
170
- function SiteDataProvider({ children, initialSiteData, fallbackSiteData, }) {
174
+ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCatalogEndpoint, siteSlug, }) {
171
175
  // SSR HTML and the first client paint must match. Reading sessionStorage here
172
176
  // causes React hydration error #418 when a previous preview left merchant
173
177
  // content in the cache. Cache is applied after mount in useEffect instead.
174
178
  const [siteData, setSiteData] = (0, react_1.useState)(() => {
175
179
  return (initialSiteData ?? fallbackSiteData ?? {});
176
180
  });
181
+ // Real-time catalog & theme hydration for standalone live sites
182
+ (0, react_1.useEffect)(() => {
183
+ if (typeof window === "undefined" || window.parent !== window)
184
+ return;
185
+ const candidateSlug = siteSlug ||
186
+ initialSiteData?.siteInstance?.slug ||
187
+ initialSiteData?.project?.slug ||
188
+ initialSiteData?.project?.id;
189
+ const endpoint = liveCatalogEndpoint ||
190
+ initialSiteData?.api?.catalogUrl ||
191
+ (candidateSlug && initialSiteData?.api?.baseUrl
192
+ ? `${initialSiteData.api.baseUrl.replace(/\/+$/, '')}/site-catalog/${candidateSlug}/live-data`
193
+ : candidateSlug
194
+ ? `/site-catalog/${candidateSlug}/live-data`
195
+ : null);
196
+ if (!endpoint)
197
+ return;
198
+ const controller = new AbortController();
199
+ fetch(endpoint, {
200
+ signal: controller.signal,
201
+ headers: { Accept: "application/json" },
202
+ })
203
+ .then((res) => (res.ok ? res.json() : null))
204
+ .then((live) => {
205
+ if (!live || !isRecord(live))
206
+ return;
207
+ setSiteData((current) => {
208
+ const currentContent = isRecord(current.content) ? current.content : {};
209
+ const currentHome = isRecord(currentContent.home) ? currentContent.home : null;
210
+ const nextProducts = Array.isArray(live.products)
211
+ ? live.products
212
+ : currentContent.products;
213
+ const nextServices = Array.isArray(live.services)
214
+ ? live.services
215
+ : currentContent.services;
216
+ return {
217
+ ...current,
218
+ content: {
219
+ ...currentContent,
220
+ ...(nextProducts !== undefined ? { products: nextProducts } : {}),
221
+ ...(nextServices !== undefined ? { services: nextServices } : {}),
222
+ ...(currentHome
223
+ ? {
224
+ home: {
225
+ ...currentHome,
226
+ ...(nextProducts !== undefined && 'products' in currentHome
227
+ ? { products: nextProducts }
228
+ : {}),
229
+ ...(nextServices !== undefined && 'services' in currentHome
230
+ ? { services: nextServices }
231
+ : {}),
232
+ ...(nextProducts !== undefined && 'featuredProducts' in currentHome
233
+ ? { featuredProducts: nextProducts }
234
+ : {}),
235
+ },
236
+ }
237
+ : {}),
238
+ },
239
+ };
240
+ });
241
+ // Dynamic theme variable injection for instant color updates
242
+ if (isRecord(live.theme)) {
243
+ const rootStyle = document.documentElement.style;
244
+ if (typeof live.theme.primaryColor === "string") {
245
+ rootStyle.setProperty("--brand-primary", live.theme.primaryColor);
246
+ }
247
+ if (typeof live.theme.secondaryColor === "string") {
248
+ rootStyle.setProperty("--brand-secondary", live.theme.secondaryColor);
249
+ }
250
+ if (typeof live.theme.accentColor === "string") {
251
+ rootStyle.setProperty("--brand-accent", live.theme.accentColor);
252
+ }
253
+ }
254
+ })
255
+ .catch(() => {
256
+ // Gracefully keep pre-rendered static fallback if live API is unreachable
257
+ });
258
+ return () => controller.abort();
259
+ }, [liveCatalogEndpoint, siteSlug, initialSiteData]);
177
260
  (0, react_1.useEffect)(() => {
178
261
  let parentOrigin = resolveParentOrigin();
179
262
  const applyIncomingSiteData = (incoming) => {
@@ -342,3 +425,71 @@ function useFieldStyle(path) {
342
425
  exports.DenebDataProvider = SiteDataProvider;
343
426
  exports.useDenebData = useSiteData;
344
427
  exports.DenebDataContext = exports.SiteDataContext;
428
+ /**
429
+ * Hook to retrieve products cleanly from SiteData, supporting both
430
+ * top-level content.products and nested content.home.products.
431
+ */
432
+ function useProducts(fallback = []) {
433
+ const siteData = useSiteData();
434
+ const content = isRecord(siteData?.content) ? siteData.content : null;
435
+ if (!content)
436
+ return fallback;
437
+ if (Array.isArray(content.products) && content.products.length > 0) {
438
+ return content.products;
439
+ }
440
+ const home = isRecord(content.home) ? content.home : null;
441
+ if (home) {
442
+ if (Array.isArray(home.products) && home.products.length > 0) {
443
+ return home.products;
444
+ }
445
+ if (Array.isArray(home.featuredProducts) && home.featuredProducts.length > 0) {
446
+ return home.featuredProducts;
447
+ }
448
+ }
449
+ return fallback;
450
+ }
451
+ /**
452
+ * Hook to retrieve services cleanly from SiteData, supporting both
453
+ * top-level content.services and nested content.home.services.
454
+ */
455
+ function useServices(fallback = []) {
456
+ const siteData = useSiteData();
457
+ const content = isRecord(siteData?.content) ? siteData.content : null;
458
+ if (!content)
459
+ return fallback;
460
+ if (Array.isArray(content.services) && content.services.length > 0) {
461
+ return content.services;
462
+ }
463
+ const home = isRecord(content.home) ? content.home : null;
464
+ if (home) {
465
+ if (Array.isArray(home.services) && home.services.length > 0) {
466
+ return home.services;
467
+ }
468
+ if (Array.isArray(home.featuredServices) && home.featuredServices.length > 0) {
469
+ return home.featuredServices;
470
+ }
471
+ }
472
+ return fallback;
473
+ }
474
+ /**
475
+ * Hook to access official Fivora backend API endpoints (catalogUrl, contactUrl, analyticsUrl).
476
+ */
477
+ function useSiteApi() {
478
+ const siteData = useSiteData();
479
+ return siteData?.api ?? null;
480
+ }
481
+ /**
482
+ * Hook to access full catalog metadata and live status.
483
+ */
484
+ function useSiteCatalog() {
485
+ const products = useProducts();
486
+ const services = useServices();
487
+ const siteData = useSiteData();
488
+ return {
489
+ products,
490
+ services,
491
+ project: siteData?.project ?? null,
492
+ siteInstance: siteData?.siteInstance ?? null,
493
+ api: siteData?.api ?? null,
494
+ };
495
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.37",
3
+ "version": "2.0.39",
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",
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "license": "MIT",
51
51
  "dependencies": {
52
- "@deneb-ui/core": "^2.0.37"
52
+ "@deneb-ui/core": "^2.0.39"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "react": "^18.0.0 || ^19.0.0",