@deneb-ui/ui 2.0.69 → 2.0.71

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.
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ export interface EditableBeforeAfterSliderProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ beforeImageId?: string;
4
+ afterImageId?: string;
5
+ defaultBeforeImage?: string;
6
+ defaultAfterImage?: string;
7
+ beforeLabelId?: string;
8
+ defaultBeforeLabel?: string;
9
+ afterLabelId?: string;
10
+ defaultAfterLabel?: string;
11
+ initialPosition?: number;
12
+ aspectRatio?: string;
13
+ handleColor?: string;
14
+ }
15
+ export declare function EditableBeforeAfterSlider({ beforeImageId, afterImageId, defaultBeforeImage, defaultAfterImage, beforeLabelId, defaultBeforeLabel, afterLabelId, defaultAfterLabel, initialPosition, aspectRatio, handleColor, className, style, ...props }: EditableBeforeAfterSliderProps): React.JSX.Element;
16
+ export declare const BeforeAfterSlider: typeof EditableBeforeAfterSlider;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.BeforeAfterSlider = void 0;
5
+ exports.EditableBeforeAfterSlider = EditableBeforeAfterSlider;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const EditableImage_1 = require("./EditableImage");
9
+ const EditableText_1 = require("./EditableText");
10
+ function EditableBeforeAfterSlider({ beforeImageId = 'home.showcase.beforeImage', afterImageId = 'home.showcase.afterImage', defaultBeforeImage = 'https://images.unsplash.com/photo-1560066984-138dadb4c035?auto=format&fit=crop&w=1200&q=80', defaultAfterImage = 'https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?auto=format&fit=crop&w=1200&q=80', beforeLabelId = 'home.showcase.beforeLabel', defaultBeforeLabel = 'Before', afterLabelId = 'home.showcase.afterLabel', defaultAfterLabel = 'After', initialPosition = 50, aspectRatio = '16 / 9', handleColor = '#ffffff', className = '', style, ...props }) {
11
+ const [sliderPos, setSliderPos] = (0, react_1.useState)(initialPosition);
12
+ const [isDragging, setIsDragging] = (0, react_1.useState)(false);
13
+ const containerRef = (0, react_1.useRef)(null);
14
+ const updatePosition = (0, react_1.useCallback)((clientX) => {
15
+ if (!containerRef.current)
16
+ return;
17
+ const rect = containerRef.current.getBoundingClientRect();
18
+ const x = clientX - rect.left;
19
+ const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100));
20
+ setSliderPos(percentage);
21
+ }, []);
22
+ const handleTouchMove = (0, react_1.useCallback)((e) => {
23
+ if (!isDragging || !e.touches[0])
24
+ return;
25
+ updatePosition(e.touches[0].clientX);
26
+ }, [isDragging, updatePosition]);
27
+ const handleMouseMove = (0, react_1.useCallback)((e) => {
28
+ if (!isDragging)
29
+ return;
30
+ updatePosition(e.clientX);
31
+ }, [isDragging, updatePosition]);
32
+ const handleMouseUp = (0, react_1.useCallback)(() => {
33
+ setIsDragging(false);
34
+ }, []);
35
+ (0, react_1.useEffect)(() => {
36
+ if (isDragging) {
37
+ window.addEventListener('mousemove', handleMouseMove);
38
+ window.addEventListener('mouseup', handleMouseUp);
39
+ window.addEventListener('touchmove', handleTouchMove);
40
+ window.addEventListener('touchend', handleMouseUp);
41
+ }
42
+ return () => {
43
+ window.removeEventListener('mousemove', handleMouseMove);
44
+ window.removeEventListener('mouseup', handleMouseUp);
45
+ window.removeEventListener('touchmove', handleTouchMove);
46
+ window.removeEventListener('touchend', handleMouseUp);
47
+ };
48
+ }, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove]);
49
+ const handleKeyDown = (e) => {
50
+ if (e.key === 'ArrowLeft') {
51
+ setSliderPos((prev) => Math.max(0, prev - 5));
52
+ }
53
+ else if (e.key === 'ArrowRight') {
54
+ setSliderPos((prev) => Math.min(100, prev + 5));
55
+ }
56
+ };
57
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, tabIndex: 0, onKeyDown: handleKeyDown, className: `editable-before-after-slider relative overflow-hidden select-none cursor-ew-resize rounded-2xl shadow-xl ${className}`.trim(), style: {
58
+ position: 'relative',
59
+ width: '100%',
60
+ aspectRatio,
61
+ userSelect: 'none',
62
+ overflow: 'hidden',
63
+ outline: 'none',
64
+ ...style,
65
+ }, onMouseDown: (e) => {
66
+ setIsDragging(true);
67
+ updatePosition(e.clientX);
68
+ }, onTouchStart: (e) => {
69
+ if (e.touches[0]) {
70
+ setIsDragging(true);
71
+ updatePosition(e.touches[0].clientX);
72
+ }
73
+ }, ...props, children: [(0, jsx_runtime_1.jsxs)("div", { style: { position: 'absolute', inset: 0, width: '100%', height: '100%' }, children: [(0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: afterImageId, src: defaultAfterImage, alt: "After transformation", style: { width: '100%', height: '100%', objectFit: 'cover' } }), (0, jsx_runtime_1.jsx)("div", { style: {
74
+ position: 'absolute',
75
+ bottom: '1rem',
76
+ right: '1rem',
77
+ backgroundColor: 'rgba(0, 0, 0, 0.65)',
78
+ backdropFilter: 'blur(8px)',
79
+ color: '#ffffff',
80
+ padding: '0.25rem 0.75rem',
81
+ borderRadius: '9999px',
82
+ fontSize: '0.75rem',
83
+ fontWeight: 600,
84
+ textTransform: 'uppercase',
85
+ letterSpacing: '0.05em',
86
+ pointerEvents: 'auto',
87
+ }, children: (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: afterLabelId, defaultValue: defaultAfterLabel, as: "span" }) })] }), (0, jsx_runtime_1.jsxs)("div", { style: {
88
+ position: 'absolute',
89
+ top: 0,
90
+ left: 0,
91
+ bottom: 0,
92
+ width: `${sliderPos}%`,
93
+ overflow: 'hidden',
94
+ borderRight: `2px solid ${handleColor}`,
95
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
96
+ position: 'absolute',
97
+ top: 0,
98
+ left: 0,
99
+ width: containerRef.current ? `${containerRef.current.clientWidth}px` : '100%',
100
+ height: '100%',
101
+ }, children: (0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: beforeImageId, src: defaultBeforeImage, alt: "Before transformation", style: { width: '100%', height: '100%', objectFit: 'cover' } }) }), (0, jsx_runtime_1.jsx)("div", { style: {
102
+ position: 'absolute',
103
+ bottom: '1rem',
104
+ left: '1rem',
105
+ backgroundColor: 'rgba(0, 0, 0, 0.65)',
106
+ backdropFilter: 'blur(8px)',
107
+ color: '#ffffff',
108
+ padding: '0.25rem 0.75rem',
109
+ borderRadius: '9999px',
110
+ fontSize: '0.75rem',
111
+ fontWeight: 600,
112
+ textTransform: 'uppercase',
113
+ letterSpacing: '0.05em',
114
+ pointerEvents: 'auto',
115
+ }, children: (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: beforeLabelId, defaultValue: defaultBeforeLabel, as: "span" }) })] }), (0, jsx_runtime_1.jsx)("div", { style: {
116
+ position: 'absolute',
117
+ top: 0,
118
+ bottom: 0,
119
+ left: `${sliderPos}%`,
120
+ transform: 'translateX(-50%)',
121
+ display: 'flex',
122
+ alignItems: 'center',
123
+ justifyContent: 'center',
124
+ pointerEvents: 'none',
125
+ }, children: (0, jsx_runtime_1.jsx)("div", { style: {
126
+ width: '40px',
127
+ height: '40px',
128
+ backgroundColor: handleColor,
129
+ borderRadius: '9999px',
130
+ boxShadow: '0 4px 14px 0 rgba(0, 0, 0, 0.35)',
131
+ display: 'flex',
132
+ alignItems: 'center',
133
+ justifyContent: 'center',
134
+ cursor: 'ew-resize',
135
+ }, children: (0, jsx_runtime_1.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "#1e293b", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "data-preview-static": "slider-arrows", children: [(0, jsx_runtime_1.jsx)("polyline", { points: "15 18 9 12 15 6" }), (0, jsx_runtime_1.jsx)("polyline", { points: "9 18 3 12 9 6" })] }) }) })] }));
136
+ }
137
+ exports.BeforeAfterSlider = EditableBeforeAfterSlider;
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ export interface ServiceOption {
3
+ id: string | number;
4
+ name: string;
5
+ duration?: string;
6
+ price?: string;
7
+ }
8
+ export interface EditableBookingModalProps {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ titleId?: string;
12
+ defaultTitle?: string;
13
+ subtitleId?: string;
14
+ defaultSubtitle?: string;
15
+ buttonLabelId?: string;
16
+ defaultButtonLabel?: string;
17
+ services?: ServiceOption[];
18
+ whatsappNumber?: string;
19
+ onSubmitBooking?: (details: {
20
+ service: string;
21
+ date: string;
22
+ time: string;
23
+ name: string;
24
+ phone: string;
25
+ notes?: string;
26
+ }) => void;
27
+ }
28
+ export declare function EditableBookingModal({ isOpen, onClose, titleId, defaultTitle, subtitleId, defaultSubtitle, buttonLabelId, defaultButtonLabel, services, whatsappNumber, onSubmitBooking, }: EditableBookingModalProps): React.JSX.Element | null;
29
+ export declare const BookingModal: typeof EditableBookingModal;
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.BookingModal = void 0;
5
+ exports.EditableBookingModal = EditableBookingModal;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const EditableText_1 = require("./EditableText");
9
+ const SiteDataProvider_1 = require("./SiteDataProvider");
10
+ function EditableBookingModal({ isOpen, onClose, titleId = 'common.booking.title', defaultTitle = 'Book Your Appointment', subtitleId = 'common.booking.subtitle', defaultSubtitle = 'Select your desired service, date, and time. We will confirm instantly via WhatsApp.', buttonLabelId = 'common.booking.buttonLabel', defaultButtonLabel = 'Confirm & Book via WhatsApp', services = [
11
+ { id: '1', name: 'Signature Haircut & Style', duration: '45 mins', price: '$65' },
12
+ { id: '2', name: 'Bespoke Balayage & Gloss', duration: '120 mins', price: '$180' },
13
+ { id: '3', name: 'Scalp Detox & Deep Conditioning', duration: '40 mins', price: '$55' },
14
+ { id: '4', name: 'Executive Beard Sculpt & Hot Towel', duration: '30 mins', price: '$40' },
15
+ ], whatsappNumber, onSubmitBooking, }) {
16
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
17
+ const rawBusiness = siteData?.content?.common?.business;
18
+ const targetWhatsapp = whatsappNumber || rawBusiness?.whatsapp || rawBusiness?.phone || '1234567890';
19
+ const [selectedService, setSelectedService] = (0, react_1.useState)(services[0]?.name || '');
20
+ const [selectedDate, setSelectedDate] = (0, react_1.useState)('');
21
+ const [selectedTime, setSelectedTime] = (0, react_1.useState)('10:00 AM');
22
+ const [customerName, setCustomerName] = (0, react_1.useState)('');
23
+ const [customerPhone, setCustomerPhone] = (0, react_1.useState)('');
24
+ const [customerNotes, setCustomerNotes] = (0, react_1.useState)('');
25
+ const timeSlots = [
26
+ '09:00 AM',
27
+ '10:00 AM',
28
+ '11:30 AM',
29
+ '01:00 PM',
30
+ '02:30 PM',
31
+ '04:00 PM',
32
+ '05:30 PM',
33
+ ];
34
+ const handleKeyDown = (0, react_1.useCallback)((e) => {
35
+ if (e.key === 'Escape' && isOpen) {
36
+ onClose();
37
+ }
38
+ }, [isOpen, onClose]);
39
+ (0, react_1.useEffect)(() => {
40
+ if (isOpen) {
41
+ document.body.style.overflow = 'hidden';
42
+ window.addEventListener('keydown', handleKeyDown);
43
+ }
44
+ else {
45
+ document.body.style.overflow = '';
46
+ }
47
+ return () => {
48
+ document.body.style.overflow = '';
49
+ window.removeEventListener('keydown', handleKeyDown);
50
+ };
51
+ }, [isOpen, handleKeyDown]);
52
+ if (!isOpen)
53
+ return null;
54
+ const handleSubmit = (e) => {
55
+ e.preventDefault();
56
+ if (onSubmitBooking) {
57
+ onSubmitBooking({
58
+ service: selectedService,
59
+ date: selectedDate,
60
+ time: selectedTime,
61
+ name: customerName,
62
+ phone: customerPhone,
63
+ notes: customerNotes,
64
+ });
65
+ }
66
+ const cleanNumber = String(targetWhatsapp).replace(/[^0-9]/g, '');
67
+ const message = encodeURIComponent(`Hello! I would like to book an appointment:\n\n` +
68
+ `• Service: ${selectedService}\n` +
69
+ `• Date: ${selectedDate || 'Upcoming Available Date'}\n` +
70
+ `• Time: ${selectedTime}\n` +
71
+ `• Name: ${customerName}\n` +
72
+ `• Phone: ${customerPhone}\n` +
73
+ (customerNotes ? `• Notes: ${customerNotes}\n` : ''));
74
+ window.open(`https://wa.me/${cleanNumber}?text=${message}`, '_blank', 'noopener,noreferrer');
75
+ onClose();
76
+ };
77
+ return ((0, jsx_runtime_1.jsx)("div", { role: "dialog", "aria-modal": "true", "aria-labelledby": "booking-modal-title", style: {
78
+ position: 'fixed',
79
+ inset: 0,
80
+ zIndex: 9999,
81
+ display: 'flex',
82
+ alignItems: 'center',
83
+ justifyContent: 'center',
84
+ padding: '1rem',
85
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
86
+ backdropFilter: 'blur(6px)',
87
+ }, onClick: (e) => {
88
+ if (e.target === e.currentTarget)
89
+ onClose();
90
+ }, children: (0, jsx_runtime_1.jsxs)("div", { style: {
91
+ backgroundColor: 'var(--color-surface, #ffffff)',
92
+ color: 'var(--color-text, #1e293b)',
93
+ borderRadius: '24px',
94
+ width: '100%',
95
+ maxWidth: '560px',
96
+ maxHeight: '90vh',
97
+ overflowY: 'auto',
98
+ boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
99
+ padding: '2rem',
100
+ position: 'relative',
101
+ border: '1px solid var(--color-border, #e2e8f0)',
102
+ }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, "aria-label": "Close booking modal", "data-preview-static": "modal-close-button", style: {
103
+ position: 'absolute',
104
+ top: '1.25rem',
105
+ right: '1.25rem',
106
+ background: 'none',
107
+ border: 'none',
108
+ cursor: 'pointer',
109
+ padding: '0.5rem',
110
+ color: '#64748b',
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ borderRadius: '9999px',
115
+ }, children: (0, jsx_runtime_1.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [(0, jsx_runtime_1.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), (0, jsx_runtime_1.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }), (0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '1.5rem', paddingRight: '2rem' }, children: [(0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: titleId, defaultValue: defaultTitle, as: "h2", style: {
116
+ fontSize: '1.5rem',
117
+ fontWeight: 700,
118
+ letterSpacing: '-0.025em',
119
+ marginBottom: '0.5rem',
120
+ color: 'var(--color-text, #0f172a)',
121
+ } }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: subtitleId, defaultValue: defaultSubtitle, as: "p", style: {
122
+ fontSize: '0.875rem',
123
+ color: '#64748b',
124
+ lineHeight: 1.5,
125
+ } })] }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, style: { display: 'flex', flexDirection: 'column', gap: '1.25rem' }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { "data-preview-static": "service-label", style: { display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }, children: "Select Service" }), (0, jsx_runtime_1.jsx)("select", { value: selectedService, onChange: (e) => setSelectedService(e.target.value), style: {
126
+ width: '100%',
127
+ padding: '0.75rem 1rem',
128
+ borderRadius: '12px',
129
+ border: '1px solid #cbd5e1',
130
+ fontSize: '0.95rem',
131
+ backgroundColor: '#f8fafc',
132
+ color: '#0f172a',
133
+ outline: 'none',
134
+ }, children: services.map((svc) => ((0, jsx_runtime_1.jsxs)("option", { value: svc.name, children: [svc.name, " ", svc.duration ? `(${svc.duration})` : '', " ", svc.price ? `— ${svc.price}` : ''] }, svc.id))) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { "data-preview-static": "date-label", style: { display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }, children: "Date" }), (0, jsx_runtime_1.jsx)("input", { type: "date", required: true, value: selectedDate, onChange: (e) => setSelectedDate(e.target.value), style: {
135
+ width: '100%',
136
+ padding: '0.75rem 1rem',
137
+ borderRadius: '12px',
138
+ border: '1px solid #cbd5e1',
139
+ fontSize: '0.95rem',
140
+ backgroundColor: '#f8fafc',
141
+ color: '#0f172a',
142
+ outline: 'none',
143
+ } })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { "data-preview-static": "time-label", style: { display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }, children: "Preferred Time" }), (0, jsx_runtime_1.jsx)("select", { value: selectedTime, onChange: (e) => setSelectedTime(e.target.value), style: {
144
+ width: '100%',
145
+ padding: '0.75rem 1rem',
146
+ borderRadius: '12px',
147
+ border: '1px solid #cbd5e1',
148
+ fontSize: '0.95rem',
149
+ backgroundColor: '#f8fafc',
150
+ color: '#0f172a',
151
+ outline: 'none',
152
+ }, children: timeSlots.map((time) => ((0, jsx_runtime_1.jsx)("option", { value: time, children: time }, time))) })] })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { "data-preview-static": "name-label", style: { display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }, children: "Full Name" }), (0, jsx_runtime_1.jsx)("input", { type: "text", required: true, placeholder: "Jane Doe", value: customerName, onChange: (e) => setCustomerName(e.target.value), style: {
153
+ width: '100%',
154
+ padding: '0.75rem 1rem',
155
+ borderRadius: '12px',
156
+ border: '1px solid #cbd5e1',
157
+ fontSize: '0.95rem',
158
+ backgroundColor: '#f8fafc',
159
+ color: '#0f172a',
160
+ outline: 'none',
161
+ } })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { "data-preview-static": "phone-label", style: { display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }, children: "Phone / WhatsApp" }), (0, jsx_runtime_1.jsx)("input", { type: "tel", required: true, placeholder: "+1 (555) 000-0000", value: customerPhone, onChange: (e) => setCustomerPhone(e.target.value), style: {
162
+ width: '100%',
163
+ padding: '0.75rem 1rem',
164
+ borderRadius: '12px',
165
+ border: '1px solid #cbd5e1',
166
+ fontSize: '0.95rem',
167
+ backgroundColor: '#f8fafc',
168
+ color: '#0f172a',
169
+ outline: 'none',
170
+ } })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { "data-preview-static": "notes-label", style: { display: 'block', fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }, children: "Special Notes / Requests (Optional)" }), (0, jsx_runtime_1.jsx)("textarea", { rows: 2, placeholder: "Any hair preferences, color history, or styling goals...", value: customerNotes, onChange: (e) => setCustomerNotes(e.target.value), style: {
171
+ width: '100%',
172
+ padding: '0.75rem 1rem',
173
+ borderRadius: '12px',
174
+ border: '1px solid #cbd5e1',
175
+ fontSize: '0.95rem',
176
+ backgroundColor: '#f8fafc',
177
+ color: '#0f172a',
178
+ outline: 'none',
179
+ resize: 'vertical',
180
+ } })] }), (0, jsx_runtime_1.jsxs)("button", { type: "submit", style: {
181
+ marginTop: '0.5rem',
182
+ padding: '0.95rem 1.5rem',
183
+ borderRadius: '14px',
184
+ backgroundColor: '#16a34a',
185
+ color: '#ffffff',
186
+ fontWeight: 600,
187
+ fontSize: '1rem',
188
+ border: 'none',
189
+ cursor: 'pointer',
190
+ display: 'flex',
191
+ alignItems: 'center',
192
+ justifyContent: 'center',
193
+ gap: '0.625rem',
194
+ transition: 'background-color 0.2s ease, transform 0.1s ease',
195
+ boxShadow: '0 10px 15px -3px rgba(22, 163, 74, 0.3)',
196
+ }, children: [(0, jsx_runtime_1.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", "data-preview-static": "whatsapp-icon", children: (0, jsx_runtime_1.jsx)("path", { d: "M12.031 6.172c-3.181 0-5.767 2.586-5.768 5.766-.001 1.298.38 2.27 1.019 3.287l-.582 2.128 2.182-.573c.978.58 1.911.928 3.145.929 3.178 0 5.767-2.587 5.768-5.766.001-3.187-2.575-5.771-5.764-5.771zm3.392 8.244c-.144.405-.837.774-1.17.824-.299.045-.677.063-1.092-.069-.252-.08-.575-.187-.988-.365-1.739-.751-2.874-2.502-2.961-2.617-.087-.116-.708-.94-.708-1.793s.448-1.273.607-1.446c.159-.173.346-.217.462-.217l.332.006c.106.005.249-.04.39.298.144.347.491 1.2.534 1.287.043.087.072.188.014.304-.058.116-.087.188-.173.289l-.26.304c-.087.086-.177.18-.076.354.101.174.449.741.964 1.201.662.591 1.221.774 1.394.86.173.086.275.071.376-.043.101-.116.433-.506.549-.68.116-.173.231-.145.39-.086s1.011.477 1.184.564.289.13.332.202c.045.072.045.419-.099.824z" }) }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: buttonLabelId, defaultValue: defaultButtonLabel, as: "span" })] })] })] }) }));
197
+ }
198
+ exports.BookingModal = EditableBookingModal;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ export interface CarouselTestimonialItem {
3
+ id?: string | number;
4
+ quote: string;
5
+ author: string;
6
+ role?: string;
7
+ company?: string;
8
+ avatar?: string;
9
+ rating?: number;
10
+ }
11
+ export interface EditableTestimonialCarouselProps extends React.HTMLAttributes<HTMLDivElement> {
12
+ listPath?: string;
13
+ items?: CarouselTestimonialItem[];
14
+ autoplay?: boolean;
15
+ autoplayInterval?: number;
16
+ titleId?: string;
17
+ defaultTitle?: string;
18
+ subtitleId?: string;
19
+ defaultSubtitle?: string;
20
+ }
21
+ export declare function EditableTestimonialCarousel({ listPath, items, autoplay, autoplayInterval, titleId, defaultTitle, subtitleId, defaultSubtitle, className, style, ...props }: EditableTestimonialCarouselProps): React.JSX.Element;
22
+ export declare const TestimonialCarousel: typeof EditableTestimonialCarousel;
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TestimonialCarousel = void 0;
5
+ exports.EditableTestimonialCarousel = EditableTestimonialCarousel;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const EditableText_1 = require("./EditableText");
9
+ const EditableImage_1 = require("./EditableImage");
10
+ const SiteDataProvider_1 = require("./SiteDataProvider");
11
+ function EditableTestimonialCarousel({ listPath = 'home.testimonials', items, autoplay = true, autoplayInterval = 5000, titleId = 'home.testimonialsSection.title', defaultTitle = 'Loved by Over 5,000+ Happy Clients', subtitleId = 'home.testimonialsSection.subtitle', defaultSubtitle = 'Real experiences, authentic transformations, and 5-star artistry.', className = '', style, ...props }) {
12
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
13
+ const defaultItems = [
14
+ {
15
+ id: '1',
16
+ quote: 'The balayage and haircut completely transformed my look. The stylists here take genuine time to understand your face shape and hair texture. Absolutely premier experience!',
17
+ author: 'Sophia Kensington',
18
+ role: 'Fashion Director & Verified Client',
19
+ avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=400&q=80',
20
+ rating: 5,
21
+ },
22
+ {
23
+ id: '2',
24
+ quote: 'Flawless precision fade and hot towel treatment. This is not just a haircut, it is true modern atelier craftsmanship. I have been coming here every two weeks for two years.',
25
+ author: 'Marcus Vance',
26
+ role: 'Creative Producer',
27
+ avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80',
28
+ rating: 5,
29
+ },
30
+ {
31
+ id: '3',
32
+ quote: 'Brought my daughter here for her first styling experience. The patience, warmth, and luxury care they showed made it unforgettable. Cannot recommend enough!',
33
+ author: 'Elena Rostova',
34
+ role: 'Architect & Mother',
35
+ avatar: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=400&q=80',
36
+ rating: 5,
37
+ },
38
+ ];
39
+ // Resolve collection from siteData content if present
40
+ let resolvedList = defaultItems;
41
+ if (items && items.length > 0) {
42
+ resolvedList = items;
43
+ }
44
+ else if (siteData?.content) {
45
+ const parts = listPath.split('.');
46
+ let curr = siteData.content;
47
+ for (const part of parts) {
48
+ if (curr && typeof curr === 'object') {
49
+ curr = curr[part];
50
+ }
51
+ else {
52
+ curr = null;
53
+ break;
54
+ }
55
+ }
56
+ if (Array.isArray(curr) && curr.length > 0) {
57
+ resolvedList = curr;
58
+ }
59
+ }
60
+ const [activeIndex, setActiveIndex] = (0, react_1.useState)(0);
61
+ const [isPaused, setIsPaused] = (0, react_1.useState)(false);
62
+ const touchStartX = (0, react_1.useRef)(0);
63
+ const touchEndX = (0, react_1.useRef)(0);
64
+ const nextSlide = (0, react_1.useCallback)(() => {
65
+ setActiveIndex((prev) => (prev + 1) % resolvedList.length);
66
+ }, [resolvedList.length]);
67
+ const prevSlide = (0, react_1.useCallback)(() => {
68
+ setActiveIndex((prev) => (prev - 1 + resolvedList.length) % resolvedList.length);
69
+ }, [resolvedList.length]);
70
+ (0, react_1.useEffect)(() => {
71
+ if (!autoplay || isPaused || resolvedList.length <= 1)
72
+ return;
73
+ const interval = setInterval(nextSlide, autoplayInterval);
74
+ return () => clearInterval(interval);
75
+ }, [autoplay, isPaused, autoplayInterval, nextSlide, resolvedList.length]);
76
+ const handleTouchStart = (e) => {
77
+ touchStartX.current = e.targetTouches[0].clientX;
78
+ };
79
+ const handleTouchMove = (e) => {
80
+ touchEndX.current = e.targetTouches[0].clientX;
81
+ };
82
+ const handleTouchEnd = () => {
83
+ if (touchStartX.current - touchEndX.current > 60) {
84
+ nextSlide();
85
+ }
86
+ if (touchStartX.current - touchEndX.current < -60) {
87
+ prevSlide();
88
+ }
89
+ };
90
+ const currentItem = resolvedList[activeIndex] || resolvedList[0];
91
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `editable-testimonial-carousel relative py-12 px-4 max-w-5xl mx-auto ${className}`.trim(), onMouseEnter: () => setIsPaused(true), onMouseLeave: () => setIsPaused(false), onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, style: {
92
+ position: 'relative',
93
+ width: '100%',
94
+ maxWidth: '56rem',
95
+ margin: '0 auto',
96
+ padding: '3rem 1rem',
97
+ ...style,
98
+ }, ...props, children: [(0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', marginBottom: '2.5rem' }, children: [(0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: titleId, defaultValue: defaultTitle, as: "h2", style: {
99
+ fontSize: '2rem',
100
+ fontWeight: 700,
101
+ letterSpacing: '-0.03em',
102
+ marginBottom: '0.75rem',
103
+ color: 'var(--color-text, #0f172a)',
104
+ } }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: subtitleId, defaultValue: defaultSubtitle, as: "p", style: {
105
+ fontSize: '1rem',
106
+ color: '#64748b',
107
+ maxWidth: '36rem',
108
+ margin: '0 auto',
109
+ lineHeight: 1.6,
110
+ } })] }), (0, jsx_runtime_1.jsxs)("div", { "data-preview-list-path": listPath, style: {
111
+ position: 'relative',
112
+ backgroundColor: 'var(--color-surface, #ffffff)',
113
+ borderRadius: '24px',
114
+ padding: '3rem 2.5rem',
115
+ boxShadow: '0 20px 40px -15px rgba(0, 0, 0, 0.07)',
116
+ border: '1px solid var(--color-border, #f1f5f9)',
117
+ minHeight: '280px',
118
+ display: 'flex',
119
+ flexDirection: 'column',
120
+ justifyContent: 'space-between',
121
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { "data-preview-item-path": `${listPath}[${activeIndex}]`, children: [(0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', gap: '4px', marginBottom: '1.5rem' }, children: [...Array(5)].map((_, i) => ((0, jsx_runtime_1.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: i < (currentItem?.rating ?? 5) ? '#f59e0b' : '#e2e8f0', "data-preview-static": "carousel-star", children: (0, jsx_runtime_1.jsx)("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }, i))) }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: `${listPath}[${activeIndex}].quote`, defaultValue: currentItem?.quote, as: "p", style: {
122
+ fontSize: '1.25rem',
123
+ lineHeight: 1.7,
124
+ fontWeight: 400,
125
+ fontStyle: 'italic',
126
+ color: 'var(--color-text, #1e293b)',
127
+ marginBottom: '2rem',
128
+ } }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', gap: '1rem' }, children: [(0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${listPath}[${activeIndex}].avatar`, src: currentItem?.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=400&q=80', alt: currentItem?.author || 'Client', style: {
129
+ width: '52px',
130
+ height: '52px',
131
+ borderRadius: '9999px',
132
+ objectFit: 'cover',
133
+ border: '2px solid #e2e8f0',
134
+ } }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: `${listPath}[${activeIndex}].author`, defaultValue: currentItem?.author, as: "h4", style: {
135
+ fontSize: '1rem',
136
+ fontWeight: 700,
137
+ color: 'var(--color-text, #0f172a)',
138
+ margin: 0,
139
+ } }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { id: `${listPath}[${activeIndex}].role`, defaultValue: currentItem?.role || currentItem?.company || 'Verified Client', as: "p", style: {
140
+ fontSize: '0.85rem',
141
+ color: '#64748b',
142
+ margin: '0.2rem 0 0 0',
143
+ } })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { "data-preview-static": "carousel-controls", style: {
144
+ display: 'flex',
145
+ alignItems: 'center',
146
+ justifyContent: 'space-between',
147
+ marginTop: '2rem',
148
+ paddingTop: '1.5rem',
149
+ borderTop: '1px solid #f1f5f9',
150
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', gap: '8px' }, children: resolvedList.map((item, idx) => ((0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": `Go to slide ${idx + 1}`, onClick: () => setActiveIndex(idx), style: {
151
+ width: idx === activeIndex ? '28px' : '8px',
152
+ height: '8px',
153
+ borderRadius: '9999px',
154
+ backgroundColor: idx === activeIndex ? 'var(--color-primary, #0f172a)' : '#cbd5e1',
155
+ border: 'none',
156
+ cursor: 'pointer',
157
+ transition: 'all 0.3s ease',
158
+ padding: 0,
159
+ } }, item.id || item.author || idx))) }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '8px' }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: prevSlide, "aria-label": "Previous testimonial", style: {
160
+ width: '40px',
161
+ height: '40px',
162
+ borderRadius: '9999px',
163
+ border: '1px solid #e2e8f0',
164
+ backgroundColor: '#ffffff',
165
+ display: 'flex',
166
+ alignItems: 'center',
167
+ justifyContent: 'center',
168
+ cursor: 'pointer',
169
+ color: '#334155',
170
+ transition: 'background-color 0.2s ease',
171
+ }, children: (0, jsx_runtime_1.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: (0, jsx_runtime_1.jsx)("polyline", { points: "15 18 9 12 15 6" }) }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: nextSlide, "aria-label": "Next testimonial", style: {
172
+ width: '40px',
173
+ height: '40px',
174
+ borderRadius: '9999px',
175
+ border: '1px solid #e2e8f0',
176
+ backgroundColor: '#ffffff',
177
+ display: 'flex',
178
+ alignItems: 'center',
179
+ justifyContent: 'center',
180
+ cursor: 'pointer',
181
+ color: '#334155',
182
+ transition: 'background-color 0.2s ease',
183
+ }, children: (0, jsx_runtime_1.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: (0, jsx_runtime_1.jsx)("polyline", { points: "9 18 15 12 9 6" }) }) })] })] })] })] }));
184
+ }
185
+ exports.TestimonialCarousel = EditableTestimonialCarousel;
@@ -57,7 +57,8 @@ export type SiteData = {
57
57
  styles?: GenericRecord | null;
58
58
  [key: string]: unknown;
59
59
  };
60
- export declare const SiteDataContext: React.Context<SiteData>;
60
+ declare const UNINITIALIZED_SITE_DATA: unique symbol;
61
+ export declare const SiteDataContext: React.Context<SiteData | typeof UNINITIALIZED_SITE_DATA>;
61
62
  export declare function isRecord(value: unknown): value is GenericRecord;
62
63
  export declare function isDarkColor(color?: unknown): boolean;
63
64
  export declare function syncThemeToDocument(theme: unknown): void;
@@ -99,7 +100,7 @@ export declare function useFieldStyle(path?: string): GenericRecord | null;
99
100
  */
100
101
  export declare const DenebDataProvider: typeof SiteDataProvider;
101
102
  export declare const useDenebData: typeof useSiteData;
102
- export declare const DenebDataContext: React.Context<SiteData>;
103
+ export declare const DenebDataContext: React.Context<SiteData | typeof UNINITIALIZED_SITE_DATA>;
103
104
  export type DenebData = SiteData;
104
105
  /**
105
106
  * Hook to retrieve products cleanly from SiteData, supporting both
@@ -28,6 +28,7 @@ const core_1 = require("@deneb-ui/core");
28
28
  const DenebComponentStyles_1 = require("./DenebComponentStyles");
29
29
  const FontLoader_1 = require("./fonts/FontLoader");
30
30
  const ResponsiveBaseStyles_1 = require("./ResponsiveBaseStyles");
31
+ const ThemeStyles_1 = require("./ThemeStyles");
31
32
  exports.DENEB_PREVIEW_DATA_MESSAGE = 'DENEB_PREVIEW_SITE_DATA';
32
33
  exports.PREVIEW_DATA_MESSAGE = 'FIVORA_PREVIEW_SITE_DATA';
33
34
  const PREVIOUS_PREVIEW_PREFIX = `${['MARKET', 'PLACE'].join('')}_PREVIEW_`;
@@ -50,7 +51,8 @@ const SITE_DATA_CACHE_KEY = '__FIVORA_PREVIEW_SITE_DATA_CACHE__';
50
51
  const LEGACY_SITE_DATA_CACHE_KEY = previousPreviewStorageKey('SITE_DATA_CACHE');
51
52
  const SITE_DATA_GLOBAL_KEY = '__FIVORA_PREVIEW_SITE_DATA__';
52
53
  const LEGACY_SITE_DATA_GLOBAL_KEY = previousPreviewStorageKey('SITE_DATA');
53
- exports.SiteDataContext = (0, react_1.createContext)({});
54
+ const UNINITIALIZED_SITE_DATA = Symbol('DENEB_UNINITIALIZED_SITE_DATA');
55
+ exports.SiteDataContext = (0, react_1.createContext)(UNINITIALIZED_SITE_DATA);
54
56
  function isRecord(value) {
55
57
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
56
58
  }
@@ -422,6 +424,14 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
422
424
  return;
423
425
  if (isData && isRecord(event.data.siteData)) {
424
426
  applyIncomingSiteData(event.data.siteData);
427
+ try {
428
+ const target = parentOrigin && parentOrigin !== 'null' ? parentOrigin : '*';
429
+ const appliedFull = event.data.full !== false;
430
+ window.parent.postMessage({ type: 'FIVORA_PREVIEW_SITE_DATA_APPLIED', full: appliedFull }, target);
431
+ }
432
+ catch {
433
+ // ignore
434
+ }
425
435
  return;
426
436
  }
427
437
  if (isStylePatch) {
@@ -456,10 +466,19 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
456
466
  return () => window.removeEventListener('message', onMessage);
457
467
  }, []);
458
468
  const value = (0, react_1.useMemo)(() => siteData, [siteData]);
459
- return ((0, jsx_runtime_1.jsxs)(exports.SiteDataContext.Provider, { value: value, children: [(0, jsx_runtime_1.jsx)(FontLoader_1.FontLoader, {}), (0, jsx_runtime_1.jsx)(ResponsiveBaseStyles_1.ResponsiveBaseStyles, {}), (0, jsx_runtime_1.jsx)(DenebComponentStyles_1.DenebComponentStyles, {}), children] }));
469
+ return ((0, jsx_runtime_1.jsxs)(exports.SiteDataContext.Provider, { value: value, children: [(0, jsx_runtime_1.jsx)(ThemeStyles_1.ThemeStyles, { theme: value?.theme }), (0, jsx_runtime_1.jsx)(FontLoader_1.FontLoader, {}), (0, jsx_runtime_1.jsx)(ResponsiveBaseStyles_1.ResponsiveBaseStyles, {}), (0, jsx_runtime_1.jsx)(DenebComponentStyles_1.DenebComponentStyles, {}), children] }));
460
470
  }
461
471
  function useSiteData() {
462
- return (0, react_1.useContext)(exports.SiteDataContext);
472
+ const ctx = (0, react_1.useContext)(exports.SiteDataContext);
473
+ if (ctx === UNINITIALIZED_SITE_DATA) {
474
+ if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'production') {
475
+ console.warn('[Deneb UI] useSiteData() was called outside of <SiteDataProvider>. ' +
476
+ 'Ensure your root layout.tsx or _app.tsx wraps the tree with: ' +
477
+ '<SiteDataProvider initialSiteData={initialSiteData}>. Falling back to empty data.');
478
+ }
479
+ return {};
480
+ }
481
+ return ctx;
463
482
  }
464
483
  function contentObject(value) {
465
484
  return isRecord(value) ? value : {};
@@ -17,6 +17,8 @@ export interface TemplateTheme {
17
17
  align?: 'left' | 'center' | 'right';
18
18
  buttonBackgroundColor?: string;
19
19
  buttonTextColor?: string;
20
+ dark?: Partial<TemplateTheme>;
21
+ light?: Partial<TemplateTheme>;
20
22
  [key: string]: unknown;
21
23
  }
22
24
  /**
@@ -41,8 +43,15 @@ export interface ThemeStylesProps {
41
43
  defaultAccent?: string;
42
44
  defaultBg?: string;
43
45
  defaultText?: string;
46
+ /**
47
+ * Automatically generate opposite mode selectors (.dark / .light or [data-theme="..."])
48
+ * so templates with theme switchers transition without writing manual CSS.
49
+ * Default: true.
50
+ */
51
+ enableDualMode?: boolean;
44
52
  }
45
53
  /**
46
54
  * Automatically injects standard and custom fivora theme variables into the document.
55
+ * Supports light-only, dark-only, and dual-mode (light & dark toggle) templates.
47
56
  */
48
- export declare function ThemeStyles({ theme, defaultPrimary, defaultSecondary, defaultAccent, defaultBg, defaultText, }: ThemeStylesProps): React.JSX.Element;
57
+ export declare function ThemeStyles({ theme, defaultPrimary, defaultSecondary, defaultAccent, defaultBg, defaultText, enableDualMode, }: ThemeStylesProps): React.JSX.Element;
@@ -139,14 +139,14 @@ function getThemeCssProperties(theme) {
139
139
  const customVars = {};
140
140
  if (theme) {
141
141
  for (const [key, val] of Object.entries(theme)) {
142
- if (typeof val === 'string' || typeof val === 'number') {
142
+ if (key !== 'dark' && key !== 'light' && (typeof val === 'string' || typeof val === 'number')) {
143
143
  const cssVarName = `--${key.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()}`;
144
144
  customVars[cssVarName] = String(val);
145
145
  }
146
146
  }
147
147
  }
148
148
  const isDark = (0, core_1.isDarkColor)(theme?.backgroundColor);
149
- const bgColor = theme?.backgroundColor || (isDark ? '#0f172a' : '#ffffff');
149
+ const bgColor = theme?.backgroundColor || (isDark ? '#090d1a' : '#ffffff');
150
150
  const textColor = theme?.textColor || (isDark ? '#f8fafc' : '#0f172a');
151
151
  const mutedColor = theme?.mutedTextColor || (isDark ? 'rgba(248, 250, 252, 0.7)' : '#64748b');
152
152
  const borderColor = isDark ? 'rgba(255, 255, 255, 0.1)' : '#e2e8f0';
@@ -211,17 +211,61 @@ function getThemeCssProperties(theme) {
211
211
  }
212
212
  /**
213
213
  * Automatically injects standard and custom fivora theme variables into the document.
214
+ * Supports light-only, dark-only, and dual-mode (light & dark toggle) templates.
214
215
  */
215
- function ThemeStyles({ theme, defaultPrimary = '#2563eb', defaultSecondary = '#0f172a', defaultAccent = '#14b8a6', defaultBg = '#ffffff', defaultText = '#0f172a', }) {
216
+ function ThemeStyles({ theme, defaultPrimary = '#2563eb', defaultSecondary = '#0f172a', defaultAccent = '#14b8a6', defaultBg = '#ffffff', defaultText = '#0f172a', enableDualMode = true, }) {
216
217
  const styleProps = getThemeCssProperties(theme);
217
- const cssLines = Object.entries(styleProps)
218
+ const baseLines = Object.entries(styleProps)
218
219
  .filter(([key]) => key.startsWith('--'))
219
220
  .map(([key, value]) => ` ${key}: ${value};`)
220
221
  .join('\n');
221
- const css = `
222
+ let css = `
222
223
  :root {
223
- ${cssLines}
224
+ ${baseLines}
224
225
  }
225
226
  `;
227
+ if (enableDualMode) {
228
+ const isDarkBase = (0, core_1.isDarkColor)(theme?.backgroundColor);
229
+ if (isDarkBase) {
230
+ // Base theme is dark. Generate light mode rules for .light or [data-theme="light"]
231
+ const lightTheme = {
232
+ ...theme,
233
+ backgroundColor: '#ffffff',
234
+ textColor: '#0f172a',
235
+ mutedTextColor: '#64748b',
236
+ ...(theme?.light || {}),
237
+ };
238
+ const lightProps = getThemeCssProperties(lightTheme);
239
+ const lightLines = Object.entries(lightProps)
240
+ .filter(([key]) => key.startsWith('--'))
241
+ .map(([key, value]) => ` ${key}: ${value};`)
242
+ .join('\n');
243
+ css += `
244
+ .light, [data-theme="light"] {
245
+ ${lightLines}
246
+ }
247
+ `;
248
+ }
249
+ else {
250
+ // Base theme is light. Generate dark mode rules for .dark or [data-theme="dark"]
251
+ const darkTheme = {
252
+ ...theme,
253
+ backgroundColor: '#090d1a',
254
+ textColor: '#f8fafc',
255
+ mutedTextColor: 'rgba(248, 250, 252, 0.7)',
256
+ ...(theme?.dark || {}),
257
+ };
258
+ const darkProps = getThemeCssProperties(darkTheme);
259
+ const darkLines = Object.entries(darkProps)
260
+ .filter(([key]) => key.startsWith('--'))
261
+ .map(([key, value]) => ` ${key}: ${value};`)
262
+ .join('\n');
263
+ css += `
264
+ .dark, [data-theme="dark"] {
265
+ ${darkLines}
266
+ }
267
+ `;
268
+ }
269
+ }
226
270
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ResponsiveBaseStyles_1.ResponsiveBaseStyles, {}), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: css } })] }));
227
271
  }
package/dist/index.d.ts CHANGED
@@ -22,6 +22,9 @@ export * from './EditableCard';
22
22
  export * from './EditablePricingCard';
23
23
  export * from './EditableTestimonialCard';
24
24
  export * from './EditableTestimonialSection';
25
+ export * from './EditableTestimonialCarousel';
26
+ export * from './EditableBeforeAfterSlider';
27
+ export * from './EditableBookingModal';
25
28
  export * from './EditableFAQAccordion';
26
29
  export * from './EditableContactForm';
27
30
  export * from './EditableNavbar';
@@ -86,6 +89,9 @@ export { EditableAnnouncementBar as AnnouncementBar } from './EditableAnnounceme
86
89
  export { EditableCategoryPills as CategoryPills } from './EditableCategoryPills';
87
90
  export { EditableCartDrawer as CartDrawer } from './EditableCartDrawer';
88
91
  export { EditableFilterSidebar as FilterSidebar } from './EditableFilterSidebar';
92
+ export { EditableBeforeAfterSlider as BeforeAfterSlider } from './EditableBeforeAfterSlider';
93
+ export { EditableBookingModal as BookingModal } from './EditableBookingModal';
94
+ export { EditableTestimonialCarousel as TestimonialCarousel } from './EditableTestimonialCarousel';
89
95
  export { PlatformAdditionalPages, AdditionalPagesNav } from './PlatformAdditionalPages';
90
96
  /**
91
97
  * DENEB UI Framework Metadata
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.FAQ = exports.Accordion = exports.Testimonials = exports.TestimonialSection = exports.TestimonialCard = exports.PricingCard = exports.ServiceCard = exports.GoogleFeedback = exports.CustomerReviews = exports.ProductDetail = exports.ProductShowcase = exports.ProductGrid = exports.ProductCard = exports.List = exports.Box = exports.Section = exports.Grid = exports.Map = exports.Image = exports.Quote = exports.Badge = exports.Paragraph = exports.Heading = exports.Text = exports.Dialog = exports.Card = exports.Button = exports.collectStyleTargetsFromHtml = exports.validateStyleTree = exports.styleToCssVariables = exports.patchStyleByPath = exports.patchElementStyle = exports.STYLE_TYPE_ATTRIBUTE = exports.STYLE_TARGET_ATTRIBUTE = exports.DENEB_STYLE_PATCH_MESSAGE = exports.STYLE_PATCH_MESSAGE = exports.formatResponsiveFontSize = exports.resolveInstallableFont = exports.normalizeFontId = exports.lookupFontDefinition = exports.listFontsByCategory = exports.collectFontIdsFromSiteData = exports.buildGoogleFontsStylesheetUrl = exports.DENEB_GOOGLE_FONT_COUNT = exports.DENEB_FONT_REGISTRY = exports.useDenebFonts = exports.DENEB_FONTS_LINK_ID = exports.FontLoader = exports.DenebComponentStyles = exports.ResponsiveBaseStyles = void 0;
22
- exports.generateThemeVariables = exports.mergeVisualCustomizationIntoTheme = exports.customizationToTheme = exports.parseVisualCustomization = exports.normalizeHexColor = exports.toHexColor = exports.getAutoContrastTextColor = exports.isDarkColor = exports.FONT_PAIRINGS = exports.GLOBAL_FONT_OPTIONS = exports.THEME_PALETTES = exports.DENEB_AUTHOR = exports.DENEB_FRAMEWORK_VERSION = exports.DENEB_FRAMEWORK_NAME = exports.AdditionalPagesNav = exports.PlatformAdditionalPages = exports.FilterSidebar = exports.CartDrawer = exports.CategoryPills = exports.AnnouncementBar = exports.HeroSplit = exports.Hero = exports.Footer = exports.Header = exports.Navbar = exports.ContactForm = void 0;
22
+ exports.generateThemeVariables = exports.mergeVisualCustomizationIntoTheme = exports.customizationToTheme = exports.parseVisualCustomization = exports.normalizeHexColor = exports.toHexColor = exports.getAutoContrastTextColor = exports.isDarkColor = exports.FONT_PAIRINGS = exports.GLOBAL_FONT_OPTIONS = exports.THEME_PALETTES = exports.DENEB_AUTHOR = exports.DENEB_FRAMEWORK_VERSION = exports.DENEB_FRAMEWORK_NAME = exports.AdditionalPagesNav = exports.PlatformAdditionalPages = exports.TestimonialCarousel = exports.BookingModal = exports.BeforeAfterSlider = exports.FilterSidebar = exports.CartDrawer = exports.CategoryPills = exports.AnnouncementBar = exports.HeroSplit = exports.Hero = exports.Footer = exports.Header = exports.Navbar = exports.ContactForm = void 0;
23
23
  __exportStar(require("./PreviewField"), exports);
24
24
  __exportStar(require("./EditableText"), exports);
25
25
  __exportStar(require("./EditableImage"), exports);
@@ -40,6 +40,9 @@ __exportStar(require("./EditableCard"), exports);
40
40
  __exportStar(require("./EditablePricingCard"), exports);
41
41
  __exportStar(require("./EditableTestimonialCard"), exports);
42
42
  __exportStar(require("./EditableTestimonialSection"), exports);
43
+ __exportStar(require("./EditableTestimonialCarousel"), exports);
44
+ __exportStar(require("./EditableBeforeAfterSlider"), exports);
45
+ __exportStar(require("./EditableBookingModal"), exports);
43
46
  __exportStar(require("./EditableFAQAccordion"), exports);
44
47
  __exportStar(require("./EditableContactForm"), exports);
45
48
  __exportStar(require("./EditableNavbar"), exports);
@@ -166,6 +169,12 @@ var EditableCartDrawer_1 = require("./EditableCartDrawer");
166
169
  Object.defineProperty(exports, "CartDrawer", { enumerable: true, get: function () { return EditableCartDrawer_1.EditableCartDrawer; } });
167
170
  var EditableFilterSidebar_1 = require("./EditableFilterSidebar");
168
171
  Object.defineProperty(exports, "FilterSidebar", { enumerable: true, get: function () { return EditableFilterSidebar_1.EditableFilterSidebar; } });
172
+ var EditableBeforeAfterSlider_1 = require("./EditableBeforeAfterSlider");
173
+ Object.defineProperty(exports, "BeforeAfterSlider", { enumerable: true, get: function () { return EditableBeforeAfterSlider_1.EditableBeforeAfterSlider; } });
174
+ var EditableBookingModal_1 = require("./EditableBookingModal");
175
+ Object.defineProperty(exports, "BookingModal", { enumerable: true, get: function () { return EditableBookingModal_1.EditableBookingModal; } });
176
+ var EditableTestimonialCarousel_1 = require("./EditableTestimonialCarousel");
177
+ Object.defineProperty(exports, "TestimonialCarousel", { enumerable: true, get: function () { return EditableTestimonialCarousel_1.EditableTestimonialCarousel; } });
169
178
  var PlatformAdditionalPages_1 = require("./PlatformAdditionalPages");
170
179
  Object.defineProperty(exports, "PlatformAdditionalPages", { enumerable: true, get: function () { return PlatformAdditionalPages_1.PlatformAdditionalPages; } });
171
180
  Object.defineProperty(exports, "AdditionalPagesNav", { enumerable: true, get: function () { return PlatformAdditionalPages_1.AdditionalPagesNav; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.69",
3
+ "version": "2.0.71",
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.69"
53
+ "@deneb-ui/core": "^2.0.71"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.0.0 || ^19.0.0",