@seekora-ai/ui-sdk-react 0.2.10 → 0.2.12
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.
- package/dist/components/primitives/ActionButtons.d.ts +27 -0
- package/dist/components/primitives/ActionButtons.d.ts.map +1 -0
- package/dist/components/primitives/ActionButtons.js +78 -0
- package/dist/components/primitives/AnalyticsProvider.d.ts +22 -0
- package/dist/components/primitives/AnalyticsProvider.d.ts.map +1 -0
- package/dist/components/primitives/AnalyticsProvider.js +87 -0
- package/dist/components/primitives/BadgeList.d.ts +14 -0
- package/dist/components/primitives/BadgeList.d.ts.map +1 -0
- package/dist/components/primitives/BadgeList.js +45 -0
- package/dist/components/primitives/ImageDisplay.d.ts +10 -1
- package/dist/components/primitives/ImageDisplay.d.ts.map +1 -1
- package/dist/components/primitives/ImageDisplay.js +49 -9
- package/dist/components/primitives/ImageZoom.d.ts +33 -0
- package/dist/components/primitives/ImageZoom.d.ts.map +1 -0
- package/dist/components/primitives/ImageZoom.js +357 -0
- package/dist/components/primitives/PriceDisplay.d.ts +21 -0
- package/dist/components/primitives/PriceDisplay.d.ts.map +1 -0
- package/dist/components/primitives/PriceDisplay.js +44 -0
- package/dist/components/primitives/RatingDisplay.d.ts +43 -0
- package/dist/components/primitives/RatingDisplay.d.ts.map +1 -0
- package/dist/components/primitives/RatingDisplay.js +114 -0
- package/dist/components/primitives/VariantSelector.d.ts +30 -0
- package/dist/components/primitives/VariantSelector.d.ts.map +1 -0
- package/dist/components/primitives/VariantSelector.js +162 -0
- package/dist/components/primitives/VariantSwatches.d.ts +28 -0
- package/dist/components/primitives/VariantSwatches.d.ts.map +1 -0
- package/dist/components/primitives/VariantSwatches.js +173 -0
- package/dist/components/primitives/index.d.ts +9 -0
- package/dist/components/primitives/index.d.ts.map +1 -1
- package/dist/components/primitives/index.js +9 -0
- package/dist/components/primitives/withAnalytics.d.ts +24 -0
- package/dist/components/primitives/withAnalytics.d.ts.map +1 -0
- package/dist/components/primitives/withAnalytics.js +73 -0
- package/dist/components/product-page/ProductInfo.d.ts +25 -2
- package/dist/components/product-page/ProductInfo.d.ts.map +1 -1
- package/dist/components/product-page/ProductInfo.js +20 -5
- package/dist/components/suggestions/types.d.ts +24 -0
- package/dist/components/suggestions/types.d.ts.map +1 -1
- package/dist/components/suggestions/utils.d.ts +37 -0
- package/dist/components/suggestions/utils.d.ts.map +1 -1
- package/dist/components/suggestions/utils.js +118 -0
- package/dist/components/suggestions-primitives/ItemCard.d.ts +10 -1
- package/dist/components/suggestions-primitives/ItemCard.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ItemCard.js +20 -6
- package/dist/components/suggestions-primitives/ProductCard.d.ts +27 -3
- package/dist/components/suggestions-primitives/ProductCard.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCard.js +124 -17
- package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts +44 -0
- package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts.map +1 -0
- package/dist/components/suggestions-primitives/ProductCardLayouts.js +105 -0
- package/dist/components/suggestions-primitives/ProductGrid.d.ts +6 -1
- package/dist/components/suggestions-primitives/ProductGrid.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductGrid.js +2 -2
- package/dist/hooks/useProductAnalytics.d.ts +49 -0
- package/dist/hooks/useProductAnalytics.d.ts.map +1 -0
- package/dist/hooks/useProductAnalytics.js +116 -0
- package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -1
- package/dist/hooks/useSuggestionsAnalytics.js +6 -0
- package/dist/hooks/useVariantSelection.d.ts +28 -0
- package/dist/hooks/useVariantSelection.d.ts.map +1 -0
- package/dist/hooks/useVariantSelection.js +44 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +1107 -679
- package/dist/src/index.esm.js +2267 -600
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/index.js +2283 -599
- package/dist/src/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImageZoom – zoom on hover and click (Amazon-style magnifier + lightbox)
|
|
3
|
+
*
|
|
4
|
+
* Supports three zoom modes:
|
|
5
|
+
* - hover: Magnified view in a separate panel on the right (Amazon style)
|
|
6
|
+
* - lens: Magnifying glass that follows cursor
|
|
7
|
+
* - click: Full-screen lightbox modal
|
|
8
|
+
*/
|
|
9
|
+
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
10
|
+
import { clsx } from 'clsx';
|
|
11
|
+
export function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, style, showZoomIndicator = true, lensSize = 150, zoomPanelSize = { width: 400, height: 400 }, images, currentIndex = 0, }) {
|
|
12
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
13
|
+
const [isLightboxOpen, setIsLightboxOpen] = useState(false);
|
|
14
|
+
const [lightboxIndex, setLightboxIndex] = useState(currentIndex);
|
|
15
|
+
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
|
|
16
|
+
const [imageLoaded, setImageLoaded] = useState(false);
|
|
17
|
+
const [touchStart, setTouchStart] = useState(null);
|
|
18
|
+
const [touchEnd, setTouchEnd] = useState(null);
|
|
19
|
+
const imageRef = useRef(null);
|
|
20
|
+
const containerRef = useRef(null);
|
|
21
|
+
const allImages = images && images.length > 0 ? images : [src];
|
|
22
|
+
const hasMultipleImages = allImages.length > 1;
|
|
23
|
+
// Minimum swipe distance (in px) to trigger navigation
|
|
24
|
+
const minSwipeDistance = 50;
|
|
25
|
+
const supportsHover = mode === 'hover' || mode === 'both';
|
|
26
|
+
const supportsClick = mode === 'click' || mode === 'both';
|
|
27
|
+
const supportsLens = mode === 'lens';
|
|
28
|
+
const handleMouseMove = useCallback((e) => {
|
|
29
|
+
if (!containerRef.current)
|
|
30
|
+
return;
|
|
31
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
32
|
+
const x = e.clientX - rect.left;
|
|
33
|
+
const y = e.clientY - rect.top;
|
|
34
|
+
setCursorPos({ x, y });
|
|
35
|
+
}, []);
|
|
36
|
+
const handleMouseEnter = useCallback(() => {
|
|
37
|
+
setIsHovering(true);
|
|
38
|
+
}, []);
|
|
39
|
+
const handleMouseLeave = useCallback(() => {
|
|
40
|
+
setIsHovering(false);
|
|
41
|
+
}, []);
|
|
42
|
+
const handleClick = useCallback((e) => {
|
|
43
|
+
if (supportsClick) {
|
|
44
|
+
e?.stopPropagation();
|
|
45
|
+
setLightboxIndex(currentIndex);
|
|
46
|
+
setIsLightboxOpen(true);
|
|
47
|
+
}
|
|
48
|
+
}, [supportsClick, currentIndex]);
|
|
49
|
+
const closeLightbox = useCallback((e) => {
|
|
50
|
+
e?.stopPropagation();
|
|
51
|
+
setIsLightboxOpen(false);
|
|
52
|
+
}, []);
|
|
53
|
+
const goToNext = useCallback(() => {
|
|
54
|
+
setLightboxIndex((i) => (i + 1) % allImages.length);
|
|
55
|
+
}, [allImages.length]);
|
|
56
|
+
const goToPrev = useCallback(() => {
|
|
57
|
+
setLightboxIndex((i) => (i - 1 + allImages.length) % allImages.length);
|
|
58
|
+
}, [allImages.length]);
|
|
59
|
+
// Touch event handlers for swipe
|
|
60
|
+
const handleTouchStart = useCallback((e) => {
|
|
61
|
+
setTouchEnd(null);
|
|
62
|
+
setTouchStart(e.targetTouches[0].clientX);
|
|
63
|
+
}, []);
|
|
64
|
+
const handleTouchMove = useCallback((e) => {
|
|
65
|
+
setTouchEnd(e.targetTouches[0].clientX);
|
|
66
|
+
}, []);
|
|
67
|
+
const handleTouchEnd = useCallback(() => {
|
|
68
|
+
if (!touchStart || !touchEnd)
|
|
69
|
+
return;
|
|
70
|
+
const distance = touchStart - touchEnd;
|
|
71
|
+
const isLeftSwipe = distance > minSwipeDistance;
|
|
72
|
+
const isRightSwipe = distance < -minSwipeDistance;
|
|
73
|
+
if (isLeftSwipe) {
|
|
74
|
+
goToNext();
|
|
75
|
+
}
|
|
76
|
+
else if (isRightSwipe) {
|
|
77
|
+
goToPrev();
|
|
78
|
+
}
|
|
79
|
+
}, [touchStart, touchEnd, minSwipeDistance, goToNext, goToPrev]);
|
|
80
|
+
// Close lightbox on Escape key, navigate with arrow keys
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!isLightboxOpen)
|
|
83
|
+
return;
|
|
84
|
+
const handleKeyboard = (e) => {
|
|
85
|
+
if (e.key === 'Escape')
|
|
86
|
+
closeLightbox();
|
|
87
|
+
if (hasMultipleImages) {
|
|
88
|
+
if (e.key === 'ArrowRight')
|
|
89
|
+
goToNext();
|
|
90
|
+
if (e.key === 'ArrowLeft')
|
|
91
|
+
goToPrev();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
window.addEventListener('keydown', handleKeyboard);
|
|
95
|
+
return () => window.removeEventListener('keydown', handleKeyboard);
|
|
96
|
+
}, [isLightboxOpen, closeLightbox, hasMultipleImages, goToNext, goToPrev]);
|
|
97
|
+
const containerStyle = {
|
|
98
|
+
position: 'relative',
|
|
99
|
+
display: 'inline-block',
|
|
100
|
+
cursor: supportsClick ? 'zoom-in' : supportsHover || supportsLens ? 'crosshair' : 'default',
|
|
101
|
+
overflow: 'hidden',
|
|
102
|
+
...style,
|
|
103
|
+
};
|
|
104
|
+
const imageStyle = {
|
|
105
|
+
width: '100%',
|
|
106
|
+
height: '100%',
|
|
107
|
+
objectFit: 'cover',
|
|
108
|
+
display: 'block',
|
|
109
|
+
};
|
|
110
|
+
// Calculate zoom panel background position (Amazon-style)
|
|
111
|
+
const getZoomPanelStyle = () => {
|
|
112
|
+
if (!containerRef.current || !imageLoaded)
|
|
113
|
+
return { display: 'none' };
|
|
114
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
115
|
+
const bgPosX = (cursorPos.x / rect.width) * 100;
|
|
116
|
+
const bgPosY = (cursorPos.y / rect.height) * 100;
|
|
117
|
+
return {
|
|
118
|
+
position: 'fixed', // Changed from absolute to fixed for better positioning
|
|
119
|
+
top: rect.top,
|
|
120
|
+
left: rect.right + 16, // 16px gap from the image
|
|
121
|
+
width: zoomPanelSize.width,
|
|
122
|
+
height: zoomPanelSize.height,
|
|
123
|
+
backgroundImage: `url(${src})`,
|
|
124
|
+
backgroundSize: `${zoomLevel * 100}%`,
|
|
125
|
+
backgroundPosition: `${bgPosX}% ${bgPosY}%`,
|
|
126
|
+
backgroundRepeat: 'no-repeat',
|
|
127
|
+
border: '2px solid var(--seekora-border-color, #e5e7eb)',
|
|
128
|
+
borderRadius: 8,
|
|
129
|
+
boxShadow: '0 8px 24px rgba(0,0,0,0.2)',
|
|
130
|
+
backgroundColor: '#fff',
|
|
131
|
+
pointerEvents: 'none',
|
|
132
|
+
zIndex: 9998,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
// Calculate lens position and zoom
|
|
136
|
+
const getLensStyle = () => {
|
|
137
|
+
if (!containerRef.current)
|
|
138
|
+
return { display: 'none' };
|
|
139
|
+
return {
|
|
140
|
+
position: 'absolute',
|
|
141
|
+
width: lensSize,
|
|
142
|
+
height: lensSize,
|
|
143
|
+
left: cursorPos.x - lensSize / 2,
|
|
144
|
+
top: cursorPos.y - lensSize / 2,
|
|
145
|
+
border: '2px solid rgba(255,255,255,0.8)',
|
|
146
|
+
borderRadius: '50%',
|
|
147
|
+
boxShadow: '0 0 0 1px rgba(0,0,0,0.3), inset 0 0 0 1px rgba(0,0,0,0.3)',
|
|
148
|
+
pointerEvents: 'none',
|
|
149
|
+
overflow: 'hidden',
|
|
150
|
+
zIndex: 100,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
const getLensImageStyle = () => {
|
|
154
|
+
if (!containerRef.current || !imageRef.current)
|
|
155
|
+
return {};
|
|
156
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
157
|
+
const imgRect = imageRef.current.getBoundingClientRect();
|
|
158
|
+
return {
|
|
159
|
+
position: 'absolute',
|
|
160
|
+
width: rect.width * zoomLevel,
|
|
161
|
+
height: rect.height * zoomLevel,
|
|
162
|
+
left: -(cursorPos.x * zoomLevel - lensSize / 2),
|
|
163
|
+
top: -(cursorPos.y * zoomLevel - lensSize / 2),
|
|
164
|
+
objectFit: 'cover',
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
return (React.createElement(React.Fragment, null,
|
|
168
|
+
React.createElement("div", { ref: containerRef, className: clsx('seekora-image-zoom', className), style: containerStyle, onMouseMove: handleMouseMove, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onClick: handleClick },
|
|
169
|
+
React.createElement("img", { ref: imageRef, src: src, alt: alt, style: imageStyle, onLoad: () => setImageLoaded(true) }),
|
|
170
|
+
showZoomIndicator && supportsClick && (React.createElement("div", { style: {
|
|
171
|
+
position: 'absolute',
|
|
172
|
+
top: 8,
|
|
173
|
+
right: 8,
|
|
174
|
+
width: 32,
|
|
175
|
+
height: 32,
|
|
176
|
+
borderRadius: '50%',
|
|
177
|
+
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
178
|
+
color: '#fff',
|
|
179
|
+
display: 'flex',
|
|
180
|
+
alignItems: 'center',
|
|
181
|
+
justifyContent: 'center',
|
|
182
|
+
fontSize: '1.25rem',
|
|
183
|
+
pointerEvents: 'none',
|
|
184
|
+
opacity: isHovering ? 1 : 0.7,
|
|
185
|
+
transition: 'opacity 150ms',
|
|
186
|
+
} }, "\uD83D\uDD0D")),
|
|
187
|
+
supportsLens && isHovering && imageLoaded && (React.createElement("div", { style: getLensStyle() },
|
|
188
|
+
React.createElement("img", { src: src, alt: "", style: getLensImageStyle() }))),
|
|
189
|
+
supportsHover && isHovering && imageLoaded && containerRef.current && (React.createElement("div", { style: {
|
|
190
|
+
position: 'absolute',
|
|
191
|
+
left: cursorPos.x - 75,
|
|
192
|
+
top: cursorPos.y - 75,
|
|
193
|
+
width: 150,
|
|
194
|
+
height: 150,
|
|
195
|
+
border: '2px solid rgba(0,0,0,0.3)',
|
|
196
|
+
backgroundColor: 'rgba(255,255,255,0.1)',
|
|
197
|
+
pointerEvents: 'none',
|
|
198
|
+
zIndex: 50,
|
|
199
|
+
} })),
|
|
200
|
+
supportsHover && isHovering && imageLoaded && (React.createElement("div", { style: getZoomPanelStyle() }))),
|
|
201
|
+
isLightboxOpen && (React.createElement("div", { className: "seekora-image-zoom-lightbox", style: {
|
|
202
|
+
position: 'fixed',
|
|
203
|
+
top: 0,
|
|
204
|
+
left: 0,
|
|
205
|
+
right: 0,
|
|
206
|
+
bottom: 0,
|
|
207
|
+
backgroundColor: 'rgba(0,0,0,0.95)',
|
|
208
|
+
zIndex: 9999,
|
|
209
|
+
display: 'flex',
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
justifyContent: 'center',
|
|
212
|
+
cursor: 'zoom-out',
|
|
213
|
+
padding: 20,
|
|
214
|
+
}, onClick: closeLightbox, onTouchStart: hasMultipleImages ? handleTouchStart : undefined, onTouchMove: hasMultipleImages ? handleTouchMove : undefined, onTouchEnd: hasMultipleImages ? handleTouchEnd : undefined },
|
|
215
|
+
React.createElement("button", { type: "button", "aria-label": "Close zoom", style: {
|
|
216
|
+
position: 'absolute',
|
|
217
|
+
top: 20,
|
|
218
|
+
right: 20,
|
|
219
|
+
width: 44,
|
|
220
|
+
height: 44,
|
|
221
|
+
borderRadius: '50%',
|
|
222
|
+
border: 'none',
|
|
223
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
224
|
+
color: '#fff',
|
|
225
|
+
fontSize: '1.5rem',
|
|
226
|
+
cursor: 'pointer',
|
|
227
|
+
display: 'flex',
|
|
228
|
+
alignItems: 'center',
|
|
229
|
+
justifyContent: 'center',
|
|
230
|
+
transition: 'background-color 150ms',
|
|
231
|
+
zIndex: 10001,
|
|
232
|
+
}, onClick: (e) => {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
closeLightbox();
|
|
235
|
+
}, onMouseEnter: (e) => {
|
|
236
|
+
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.3)';
|
|
237
|
+
}, onMouseLeave: (e) => {
|
|
238
|
+
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.2)';
|
|
239
|
+
} }, "\u2715"),
|
|
240
|
+
hasMultipleImages && (React.createElement(React.Fragment, null,
|
|
241
|
+
React.createElement("button", { type: "button", "aria-label": "Previous image", style: {
|
|
242
|
+
position: 'absolute',
|
|
243
|
+
left: 20,
|
|
244
|
+
top: '50%',
|
|
245
|
+
transform: 'translateY(-50%)',
|
|
246
|
+
width: 56,
|
|
247
|
+
height: 56,
|
|
248
|
+
borderRadius: '50%',
|
|
249
|
+
border: 'none',
|
|
250
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
251
|
+
color: '#fff',
|
|
252
|
+
fontSize: '2rem',
|
|
253
|
+
fontWeight: 'bold',
|
|
254
|
+
cursor: 'pointer',
|
|
255
|
+
display: 'flex',
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
justifyContent: 'center',
|
|
258
|
+
transition: 'background-color 150ms',
|
|
259
|
+
zIndex: 10001,
|
|
260
|
+
}, onClick: (e) => {
|
|
261
|
+
e.stopPropagation();
|
|
262
|
+
goToPrev();
|
|
263
|
+
}, onMouseEnter: (e) => {
|
|
264
|
+
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.3)';
|
|
265
|
+
}, onMouseLeave: (e) => {
|
|
266
|
+
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.2)';
|
|
267
|
+
} }, "\u2039"),
|
|
268
|
+
React.createElement("button", { type: "button", "aria-label": "Next image", style: {
|
|
269
|
+
position: 'absolute',
|
|
270
|
+
right: 20,
|
|
271
|
+
top: '50%',
|
|
272
|
+
transform: 'translateY(-50%)',
|
|
273
|
+
width: 56,
|
|
274
|
+
height: 56,
|
|
275
|
+
borderRadius: '50%',
|
|
276
|
+
border: 'none',
|
|
277
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
278
|
+
color: '#fff',
|
|
279
|
+
fontSize: '2rem',
|
|
280
|
+
fontWeight: 'bold',
|
|
281
|
+
cursor: 'pointer',
|
|
282
|
+
display: 'flex',
|
|
283
|
+
alignItems: 'center',
|
|
284
|
+
justifyContent: 'center',
|
|
285
|
+
transition: 'background-color 150ms',
|
|
286
|
+
zIndex: 10001,
|
|
287
|
+
}, onClick: (e) => {
|
|
288
|
+
e.stopPropagation();
|
|
289
|
+
goToNext();
|
|
290
|
+
}, onMouseEnter: (e) => {
|
|
291
|
+
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.3)';
|
|
292
|
+
}, onMouseLeave: (e) => {
|
|
293
|
+
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.2)';
|
|
294
|
+
} }, "\u203A"))),
|
|
295
|
+
React.createElement("img", { src: allImages[lightboxIndex], alt: alt, style: {
|
|
296
|
+
maxWidth: '90%',
|
|
297
|
+
maxHeight: '90%',
|
|
298
|
+
objectFit: 'contain',
|
|
299
|
+
borderRadius: 4,
|
|
300
|
+
cursor: 'default',
|
|
301
|
+
}, onClick: (e) => e.stopPropagation() }),
|
|
302
|
+
hasMultipleImages && (React.createElement("div", { style: {
|
|
303
|
+
position: 'absolute',
|
|
304
|
+
bottom: 20,
|
|
305
|
+
left: '50%',
|
|
306
|
+
transform: 'translateX(-50%)',
|
|
307
|
+
display: 'flex',
|
|
308
|
+
flexDirection: 'column',
|
|
309
|
+
alignItems: 'center',
|
|
310
|
+
gap: 12,
|
|
311
|
+
}, onClick: (e) => e.stopPropagation() },
|
|
312
|
+
React.createElement("div", { style: { display: 'flex', gap: 8, overflowX: 'auto', maxWidth: '80vw', padding: '8px 0' } }, allImages.map((img, i) => (React.createElement("button", { key: i, type: "button", onClick: (e) => {
|
|
313
|
+
e.stopPropagation();
|
|
314
|
+
setLightboxIndex(i);
|
|
315
|
+
}, style: {
|
|
316
|
+
width: 60,
|
|
317
|
+
height: 60,
|
|
318
|
+
padding: 0,
|
|
319
|
+
border: i === lightboxIndex ? '3px solid #fff' : '2px solid rgba(255,255,255,0.3)',
|
|
320
|
+
borderRadius: 4,
|
|
321
|
+
overflow: 'hidden',
|
|
322
|
+
cursor: 'pointer',
|
|
323
|
+
opacity: i === lightboxIndex ? 1 : 0.6,
|
|
324
|
+
transition: 'all 150ms ease',
|
|
325
|
+
flexShrink: 0,
|
|
326
|
+
background: 'none',
|
|
327
|
+
}, onMouseEnter: (e) => {
|
|
328
|
+
e.currentTarget.style.opacity = '1';
|
|
329
|
+
}, onMouseLeave: (e) => {
|
|
330
|
+
if (i !== lightboxIndex)
|
|
331
|
+
e.currentTarget.style.opacity = '0.6';
|
|
332
|
+
} },
|
|
333
|
+
React.createElement("img", { src: img, alt: "", style: { width: '100%', height: '100%', objectFit: 'cover' } }))))),
|
|
334
|
+
React.createElement("div", { style: {
|
|
335
|
+
color: 'rgba(255,255,255,0.9)',
|
|
336
|
+
fontSize: '0.875rem',
|
|
337
|
+
textAlign: 'center',
|
|
338
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
339
|
+
padding: '4px 12px',
|
|
340
|
+
borderRadius: 12,
|
|
341
|
+
} },
|
|
342
|
+
lightboxIndex + 1,
|
|
343
|
+
" / ",
|
|
344
|
+
allImages.length))),
|
|
345
|
+
React.createElement("div", { style: {
|
|
346
|
+
position: 'absolute',
|
|
347
|
+
top: 20,
|
|
348
|
+
left: '50%',
|
|
349
|
+
transform: 'translateX(-50%)',
|
|
350
|
+
color: 'rgba(255,255,255,0.7)',
|
|
351
|
+
fontSize: '0.875rem',
|
|
352
|
+
textAlign: 'center',
|
|
353
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
354
|
+
padding: '8px 16px',
|
|
355
|
+
borderRadius: 12,
|
|
356
|
+
} }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')))));
|
|
357
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PriceDisplay – reusable price display primitive
|
|
3
|
+
*
|
|
4
|
+
* Handles single price, compare price with strikethrough, discount percentage,
|
|
5
|
+
* and price range display.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import type { PriceRange } from '@seekora-ai/ui-sdk-types';
|
|
9
|
+
export interface PriceDisplayProps {
|
|
10
|
+
price?: number;
|
|
11
|
+
comparePrice?: number;
|
|
12
|
+
priceRange?: PriceRange | null;
|
|
13
|
+
currency?: string;
|
|
14
|
+
currencyPosition?: 'before' | 'after';
|
|
15
|
+
priceDecimals?: number;
|
|
16
|
+
showDiscount?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
style?: React.CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
export declare function PriceDisplay({ price, comparePrice, priceRange, currency, currencyPosition, priceDecimals, showDiscount, className, style, }: PriceDisplayProps): React.JSX.Element | null;
|
|
21
|
+
//# sourceMappingURL=PriceDisplay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PriceDisplay.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/PriceDisplay.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAeD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,YAAY,EACZ,UAAU,EACV,QAAc,EACd,gBAA2B,EAC3B,aAAiB,EACjB,YAAmB,EACnB,SAAS,EACT,KAAK,GACN,EAAE,iBAAiB,4BAgDnB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PriceDisplay – reusable price display primitive
|
|
3
|
+
*
|
|
4
|
+
* Handles single price, compare price with strikethrough, discount percentage,
|
|
5
|
+
* and price range display.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { clsx } from 'clsx';
|
|
9
|
+
const formatNum = (value, currency, position, decimals) => {
|
|
10
|
+
const formatted = value.toLocaleString(undefined, {
|
|
11
|
+
minimumFractionDigits: decimals,
|
|
12
|
+
maximumFractionDigits: decimals,
|
|
13
|
+
});
|
|
14
|
+
return position === 'before' ? `${currency}${formatted}` : `${formatted}${currency}`;
|
|
15
|
+
};
|
|
16
|
+
export function PriceDisplay({ price, comparePrice, priceRange, currency = '$', currencyPosition = 'before', priceDecimals = 2, showDiscount = true, className, style, }) {
|
|
17
|
+
const fmt = (v) => formatNum(v, currency, currencyPosition, priceDecimals);
|
|
18
|
+
// Price range mode
|
|
19
|
+
if (priceRange) {
|
|
20
|
+
return (React.createElement("span", { className: clsx('seekora-price-display', 'seekora-price-range', className), style: style },
|
|
21
|
+
fmt(priceRange.min),
|
|
22
|
+
" \u2013 ",
|
|
23
|
+
fmt(priceRange.max)));
|
|
24
|
+
}
|
|
25
|
+
if (price == null)
|
|
26
|
+
return null;
|
|
27
|
+
const hasCompare = comparePrice != null && comparePrice > price;
|
|
28
|
+
const discount = hasCompare ? Math.round(((comparePrice - price) / comparePrice) * 100) : 0;
|
|
29
|
+
return (React.createElement("span", { className: clsx('seekora-price-display', className), style: { display: 'inline-flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', ...style } },
|
|
30
|
+
React.createElement("span", { className: "seekora-price-current", style: { fontWeight: 600 } }, fmt(price)),
|
|
31
|
+
hasCompare && (React.createElement("span", { className: "seekora-price-compare", style: {
|
|
32
|
+
textDecoration: 'line-through',
|
|
33
|
+
color: 'var(--seekora-text-secondary, #6b7280)',
|
|
34
|
+
fontSize: '0.875em',
|
|
35
|
+
} }, fmt(comparePrice))),
|
|
36
|
+
hasCompare && showDiscount && discount > 0 && (React.createElement("span", { className: "seekora-price-discount", style: {
|
|
37
|
+
color: 'var(--seekora-error, #ef4444)',
|
|
38
|
+
fontSize: '0.8125em',
|
|
39
|
+
fontWeight: 600,
|
|
40
|
+
} },
|
|
41
|
+
"-",
|
|
42
|
+
discount,
|
|
43
|
+
"%"))));
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RatingDisplay – star rating display with review count
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple variants: stars-only, compact, full, inline.
|
|
5
|
+
* Can be read-only (display) or interactive (for reviews).
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
export type RatingVariant = 'stars-only' | 'compact' | 'full' | 'inline';
|
|
9
|
+
export type RatingSize = 'small' | 'medium' | 'large';
|
|
10
|
+
export interface RatingDisplayProps {
|
|
11
|
+
/** Rating value (0-5) */
|
|
12
|
+
rating: number;
|
|
13
|
+
/** Total number of reviews */
|
|
14
|
+
reviewCount?: number;
|
|
15
|
+
/** Display variant */
|
|
16
|
+
variant?: RatingVariant;
|
|
17
|
+
/** Size of stars */
|
|
18
|
+
size?: RatingSize;
|
|
19
|
+
/** Maximum rating (default: 5) */
|
|
20
|
+
maxRating?: number;
|
|
21
|
+
/** Show decimal rating number */
|
|
22
|
+
showNumeric?: boolean;
|
|
23
|
+
/** Show half stars */
|
|
24
|
+
showHalfStars?: boolean;
|
|
25
|
+
/** Interactive mode (for leaving reviews) */
|
|
26
|
+
interactive?: boolean;
|
|
27
|
+
/** Callback when rating is clicked (interactive mode) */
|
|
28
|
+
onRatingChange?: (rating: number) => void;
|
|
29
|
+
/** Custom star color */
|
|
30
|
+
starColor?: string;
|
|
31
|
+
/** Custom empty star color */
|
|
32
|
+
emptyStarColor?: string;
|
|
33
|
+
/** Custom text color */
|
|
34
|
+
textColor?: string;
|
|
35
|
+
/** Show review count in parentheses */
|
|
36
|
+
showReviewCount?: boolean;
|
|
37
|
+
/** Custom review count format */
|
|
38
|
+
reviewCountFormat?: (count: number) => string;
|
|
39
|
+
className?: string;
|
|
40
|
+
style?: React.CSSProperties;
|
|
41
|
+
}
|
|
42
|
+
export declare function RatingDisplay({ rating, reviewCount, variant, size, maxRating, showNumeric, showHalfStars, interactive, onRatingChange, starColor, emptyStarColor, textColor, showReviewCount, reviewCountFormat, className, style, }: RatingDisplayProps): React.JSX.Element | null;
|
|
43
|
+
//# sourceMappingURL=RatingDisplay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RatingDisplay.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/RatingDisplay.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AACzE,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,oBAAoB;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yDAAyD;IACzD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iCAAiC;IACjC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AA2GD,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,WAAW,EACX,OAAmB,EACnB,IAAe,EACf,SAAa,EACb,WAAmB,EACnB,aAAoB,EACpB,WAAmB,EACnB,cAAc,EACd,SAAqB,EACrB,cAA0B,EAC1B,SAAoD,EACpD,eAAsB,EACtB,iBAAiB,EACjB,SAAS,EACT,KAAK,GACN,EAAE,kBAAkB,4BA0IpB"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RatingDisplay – star rating display with review count
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple variants: stars-only, compact, full, inline.
|
|
5
|
+
* Can be read-only (display) or interactive (for reviews).
|
|
6
|
+
*/
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
import { clsx } from 'clsx';
|
|
9
|
+
const sizeMap = {
|
|
10
|
+
small: 14,
|
|
11
|
+
medium: 18,
|
|
12
|
+
large: 24,
|
|
13
|
+
};
|
|
14
|
+
const fontSizeMap = {
|
|
15
|
+
small: '0.75rem',
|
|
16
|
+
medium: '0.875rem',
|
|
17
|
+
large: '1rem',
|
|
18
|
+
};
|
|
19
|
+
function StarIcon({ filled, half, size, color, emptyColor, interactive, onHover, onClick, }) {
|
|
20
|
+
if (half) {
|
|
21
|
+
return (React.createElement("span", { className: clsx('seekora-rating-star', 'seekora-rating-star--half', interactive && 'seekora-rating-star--interactive'), style: {
|
|
22
|
+
position: 'relative',
|
|
23
|
+
display: 'inline-block',
|
|
24
|
+
width: size,
|
|
25
|
+
height: size,
|
|
26
|
+
cursor: interactive ? 'pointer' : 'default',
|
|
27
|
+
}, onMouseEnter: onHover, onClick: onClick },
|
|
28
|
+
React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", style: { position: 'absolute', top: 0, left: 0 } },
|
|
29
|
+
React.createElement("defs", null,
|
|
30
|
+
React.createElement("linearGradient", { id: "half-fill" },
|
|
31
|
+
React.createElement("stop", { offset: "50%", stopColor: color }),
|
|
32
|
+
React.createElement("stop", { offset: "50%", stopColor: emptyColor }))),
|
|
33
|
+
React.createElement("path", { d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z", fill: "url(#half-fill)", stroke: color, strokeWidth: "1" }))));
|
|
34
|
+
}
|
|
35
|
+
return (React.createElement("span", { className: clsx('seekora-rating-star', filled ? 'seekora-rating-star--filled' : 'seekora-rating-star--empty', interactive && 'seekora-rating-star--interactive'), style: {
|
|
36
|
+
display: 'inline-block',
|
|
37
|
+
width: size,
|
|
38
|
+
height: size,
|
|
39
|
+
cursor: interactive ? 'pointer' : 'default',
|
|
40
|
+
}, onMouseEnter: onHover, onClick: onClick },
|
|
41
|
+
React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: filled ? color : 'none', xmlns: "http://www.w3.org/2000/svg" },
|
|
42
|
+
React.createElement("path", { d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z", stroke: filled ? color : emptyColor, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }))));
|
|
43
|
+
}
|
|
44
|
+
export function RatingDisplay({ rating, reviewCount, variant = 'compact', size = 'medium', maxRating = 5, showNumeric = false, showHalfStars = true, interactive = false, onRatingChange, starColor = '#f59e0b', emptyStarColor = '#d1d5db', textColor = 'var(--seekora-text-secondary, #6b7280)', showReviewCount = true, reviewCountFormat, className, style, }) {
|
|
45
|
+
const [hoverRating, setHoverRating] = useState(null);
|
|
46
|
+
const clampedRating = Math.max(0, Math.min(maxRating, rating));
|
|
47
|
+
const displayRating = interactive && hoverRating !== null ? hoverRating : clampedRating;
|
|
48
|
+
const starSize = sizeMap[size];
|
|
49
|
+
const fontSize = fontSizeMap[size];
|
|
50
|
+
const formatReviewCount = (count) => {
|
|
51
|
+
if (reviewCountFormat)
|
|
52
|
+
return reviewCountFormat(count);
|
|
53
|
+
if (count >= 1000000)
|
|
54
|
+
return `${(count / 1000000).toFixed(1)}M`;
|
|
55
|
+
if (count >= 1000)
|
|
56
|
+
return `${(count / 1000).toFixed(1)}K`;
|
|
57
|
+
return count.toString();
|
|
58
|
+
};
|
|
59
|
+
const renderStars = () => {
|
|
60
|
+
const stars = [];
|
|
61
|
+
for (let i = 1; i <= maxRating; i++) {
|
|
62
|
+
const filled = i <= Math.floor(displayRating);
|
|
63
|
+
const half = showHalfStars && i === Math.ceil(displayRating) && displayRating % 1 >= 0.25 && displayRating % 1 < 0.75;
|
|
64
|
+
stars.push(React.createElement(StarIcon, { key: i, filled: filled, half: half, size: starSize, color: starColor, emptyColor: emptyStarColor, interactive: interactive, onHover: interactive ? () => setHoverRating(i) : undefined, onClick: interactive
|
|
65
|
+
? () => {
|
|
66
|
+
setHoverRating(null);
|
|
67
|
+
onRatingChange?.(i);
|
|
68
|
+
}
|
|
69
|
+
: undefined }));
|
|
70
|
+
}
|
|
71
|
+
return stars;
|
|
72
|
+
};
|
|
73
|
+
const handleMouseLeave = () => {
|
|
74
|
+
if (interactive) {
|
|
75
|
+
setHoverRating(null);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
if (variant === 'stars-only') {
|
|
79
|
+
return (React.createElement("div", { className: clsx('seekora-rating-display', 'seekora-rating-display--stars-only', className), style: { display: 'inline-flex', alignItems: 'center', gap: 2, ...style }, onMouseLeave: handleMouseLeave }, renderStars()));
|
|
80
|
+
}
|
|
81
|
+
if (variant === 'compact') {
|
|
82
|
+
return (React.createElement("div", { className: clsx('seekora-rating-display', 'seekora-rating-display--compact', className), style: { display: 'inline-flex', alignItems: 'center', gap: 4, fontSize, ...style }, onMouseLeave: handleMouseLeave },
|
|
83
|
+
React.createElement("div", { style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, renderStars()),
|
|
84
|
+
showNumeric && (React.createElement("span", { className: "seekora-rating-numeric", style: { fontWeight: 600, color: textColor } }, clampedRating.toFixed(1))),
|
|
85
|
+
showReviewCount && reviewCount != null && reviewCount > 0 && (React.createElement("span", { className: "seekora-rating-review-count", style: { color: textColor } },
|
|
86
|
+
"(",
|
|
87
|
+
formatReviewCount(reviewCount),
|
|
88
|
+
")"))));
|
|
89
|
+
}
|
|
90
|
+
if (variant === 'full') {
|
|
91
|
+
return (React.createElement("div", { className: clsx('seekora-rating-display', 'seekora-rating-display--full', className), style: { display: 'flex', flexDirection: 'column', gap: 4, fontSize, ...style }, onMouseLeave: handleMouseLeave },
|
|
92
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
93
|
+
React.createElement("div", { style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, renderStars()),
|
|
94
|
+
React.createElement("span", { className: "seekora-rating-numeric", style: { fontWeight: 600, color: 'var(--seekora-text-primary, #111827)' } }, clampedRating.toFixed(1)),
|
|
95
|
+
React.createElement("span", { className: "seekora-rating-max", style: { color: textColor } },
|
|
96
|
+
"/ ",
|
|
97
|
+
maxRating)),
|
|
98
|
+
showReviewCount && reviewCount != null && reviewCount > 0 && (React.createElement("span", { className: "seekora-rating-review-text", style: { fontSize: '0.875em', color: textColor } },
|
|
99
|
+
"Based on ",
|
|
100
|
+
formatReviewCount(reviewCount),
|
|
101
|
+
" ",
|
|
102
|
+
reviewCount === 1 ? 'review' : 'reviews'))));
|
|
103
|
+
}
|
|
104
|
+
if (variant === 'inline') {
|
|
105
|
+
return (React.createElement("div", { className: clsx('seekora-rating-display', 'seekora-rating-display--inline', className), style: { display: 'inline-flex', alignItems: 'center', gap: 6, fontSize, ...style }, onMouseLeave: handleMouseLeave },
|
|
106
|
+
React.createElement("span", { className: "seekora-rating-numeric", style: { fontWeight: 600, color: 'var(--seekora-text-primary, #111827)' } }, clampedRating.toFixed(1)),
|
|
107
|
+
React.createElement("div", { style: { display: 'inline-flex', alignItems: 'center', gap: 2 } }, renderStars()),
|
|
108
|
+
showReviewCount && reviewCount != null && reviewCount > 0 && (React.createElement("span", { className: "seekora-rating-review-count", style: { color: textColor } },
|
|
109
|
+
"(",
|
|
110
|
+
formatReviewCount(reviewCount),
|
|
111
|
+
")"))));
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VariantSelector – full variant selector for product detail pages
|
|
3
|
+
*
|
|
4
|
+
* Three render modes per option:
|
|
5
|
+
* - swatch: color circles (auto-detected for "Color" option or when colorMap provided)
|
|
6
|
+
* - button: rectangular buttons (default for most options)
|
|
7
|
+
* - dropdown: <select> for options with many values
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import type { ProductOption, ProductVariant } from '@seekora-ai/ui-sdk-types';
|
|
11
|
+
export interface VariantSelectorProps {
|
|
12
|
+
options: ProductOption[];
|
|
13
|
+
variants: ProductVariant[];
|
|
14
|
+
selections: Record<string, string>;
|
|
15
|
+
onSelectionChange: (optionName: string, value: string) => void;
|
|
16
|
+
/** Override render mode per option name */
|
|
17
|
+
optionRenderModes?: Record<string, 'swatch' | 'button' | 'dropdown'>;
|
|
18
|
+
/** Auto-switch to dropdown when values exceed this count (default: 8) */
|
|
19
|
+
dropdownThreshold?: number;
|
|
20
|
+
/** Map value names to CSS colors */
|
|
21
|
+
colorMap?: Record<string, string>;
|
|
22
|
+
/** Show availability cross-out for unavailable combos */
|
|
23
|
+
showAvailability?: boolean;
|
|
24
|
+
/** Currently matched variant (for display) */
|
|
25
|
+
selectedVariant?: ProductVariant | null;
|
|
26
|
+
className?: string;
|
|
27
|
+
style?: React.CSSProperties;
|
|
28
|
+
}
|
|
29
|
+
export declare function VariantSelector({ options, variants, selections, onSelectionChange, optionRenderModes, dropdownThreshold, colorMap, showAvailability, selectedVariant: _selectedVariant, className, style, }: VariantSelectorProps): React.JSX.Element | null;
|
|
30
|
+
//# sourceMappingURL=VariantSelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VariantSelector.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/VariantSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE9E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;IACrE,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,8CAA8C;IAC9C,eAAe,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAqDD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAqB,EACrB,QAAQ,EACR,gBAAuB,EACvB,eAAe,EAAE,gBAAgB,EACjC,SAAS,EACT,KAAK,GACN,EAAE,oBAAoB,4BA0KtB"}
|