@qwickapps/react-framework 1.5.12 → 1.6.0
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/README.md +23 -0
- package/dist/components/blocks/ImageGallery.d.ts +30 -0
- package/dist/components/blocks/ImageGallery.d.ts.map +1 -0
- package/dist/components/blocks/OptionSelector.d.ts +45 -0
- package/dist/components/blocks/OptionSelector.d.ts.map +1 -0
- package/dist/components/blocks/index.d.ts +4 -0
- package/dist/components/blocks/index.d.ts.map +1 -1
- package/dist/components/forms/Captcha.d.ts +33 -28
- package/dist/components/forms/Captcha.d.ts.map +1 -1
- package/dist/components/forms/FormCheckbox.d.ts +15 -12
- package/dist/components/forms/FormCheckbox.d.ts.map +1 -1
- package/dist/components/forms/FormField.d.ts +20 -23
- package/dist/components/forms/FormField.d.ts.map +1 -1
- package/dist/components/forms/FormSelect.d.ts +16 -15
- package/dist/components/forms/FormSelect.d.ts.map +1 -1
- package/dist/hooks/useBaseProps.d.ts +27 -1172
- package/dist/hooks/useBaseProps.d.ts.map +1 -1
- package/dist/index.esm.js +1674 -554
- package/dist/index.js +1676 -552
- package/dist/palettes/manifest.json +19 -19
- package/dist/schemas/CaptchaSchema.d.ts +16 -0
- package/dist/schemas/CaptchaSchema.d.ts.map +1 -0
- package/dist/schemas/FormCheckboxSchema.d.ts +16 -0
- package/dist/schemas/FormCheckboxSchema.d.ts.map +1 -0
- package/dist/schemas/FormFieldSchema.d.ts +23 -0
- package/dist/schemas/FormFieldSchema.d.ts.map +1 -0
- package/dist/schemas/FormSelectSchema.d.ts +20 -0
- package/dist/schemas/FormSelectSchema.d.ts.map +1 -0
- package/dist/schemas/ImageGallerySchema.d.ts +27 -0
- package/dist/schemas/ImageGallerySchema.d.ts.map +1 -0
- package/dist/schemas/OptionSelectorSchema.d.ts +34 -0
- package/dist/schemas/OptionSelectorSchema.d.ts.map +1 -0
- package/dist/schemas/index.d.ts +6 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/blocks/Article.tsx +1 -1
- package/src/components/blocks/ImageGallery.tsx +464 -0
- package/src/components/blocks/OptionSelector.tsx +459 -0
- package/src/components/blocks/index.ts +4 -0
- package/src/components/forms/Captcha.tsx +57 -63
- package/src/components/forms/FormCheckbox.tsx +35 -43
- package/src/components/forms/FormField.tsx +50 -66
- package/src/components/forms/FormSelect.tsx +41 -49
- package/src/hooks/useBaseProps.ts +34 -1
- package/src/schemas/CaptchaSchema.ts +65 -0
- package/src/schemas/FormCheckboxSchema.ts +65 -0
- package/src/schemas/FormFieldSchema.ts +140 -0
- package/src/schemas/FormSelectSchema.ts +108 -0
- package/src/schemas/ImageGallerySchema.ts +148 -0
- package/src/schemas/OptionSelectorSchema.ts +216 -0
- package/src/schemas/index.ts +6 -0
- package/src/stories/ImageGallery.stories.tsx +497 -0
- package/src/stories/OptionSelector.stories.tsx +506 -0
- /package/dist/palettes/{palette-autumn.1.5.12.css → palette-autumn.1.6.0.css} +0 -0
- /package/dist/palettes/{palette-autumn.1.5.12.min.css → palette-autumn.1.6.0.min.css} +0 -0
- /package/dist/palettes/{palette-cosmic.1.5.12.css → palette-cosmic.1.6.0.css} +0 -0
- /package/dist/palettes/{palette-cosmic.1.5.12.min.css → palette-cosmic.1.6.0.min.css} +0 -0
- /package/dist/palettes/{palette-default.1.5.12.css → palette-default.1.6.0.css} +0 -0
- /package/dist/palettes/{palette-default.1.5.12.min.css → palette-default.1.6.0.min.css} +0 -0
- /package/dist/palettes/{palette-ocean.1.5.12.css → palette-ocean.1.6.0.css} +0 -0
- /package/dist/palettes/{palette-ocean.1.5.12.min.css → palette-ocean.1.6.0.min.css} +0 -0
- /package/dist/palettes/{palette-spring.1.5.12.css → palette-spring.1.6.0.css} +0 -0
- /package/dist/palettes/{palette-spring.1.5.12.min.css → palette-spring.1.6.0.min.css} +0 -0
- /package/dist/palettes/{palette-winter.1.5.12.css → palette-winter.1.6.0.css} +0 -0
- /package/dist/palettes/{palette-winter.1.5.12.min.css → palette-winter.1.6.0.min.css} +0 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ImageGallery - Comprehensive product image gallery with multiple view variants
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Multiple view variants (thumbnails, carousel, grid)
|
|
8
|
+
* - Configurable thumbnail position (left, bottom, right)
|
|
9
|
+
* - Image zoom modal
|
|
10
|
+
* - Video support
|
|
11
|
+
* - Responsive design
|
|
12
|
+
* - Accessibility support
|
|
13
|
+
* - Full serialization support via factory pattern
|
|
14
|
+
* - Theme-compliant styling with CSS custom properties
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <ImageGallery
|
|
18
|
+
* images={[
|
|
19
|
+
* { url: '/image1.jpg', alt: 'Product view 1' },
|
|
20
|
+
* { url: '/image2.jpg', alt: 'Product view 2' }
|
|
21
|
+
* ]}
|
|
22
|
+
* productName="Premium Cotton T-Shirt"
|
|
23
|
+
* variant="thumbnails"
|
|
24
|
+
* thumbnailPosition="left"
|
|
25
|
+
* showZoom={true}
|
|
26
|
+
* />
|
|
27
|
+
*
|
|
28
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import React, { useState, useCallback } from 'react';
|
|
32
|
+
import { Box, IconButton, Modal, Grid, Skeleton } from '@mui/material';
|
|
33
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
34
|
+
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
|
35
|
+
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
36
|
+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
37
|
+
import { createSerializableView, SerializableComponent } from '../shared/createSerializableView';
|
|
38
|
+
import { ViewProps } from '../shared/viewProps';
|
|
39
|
+
|
|
40
|
+
export interface GalleryImage {
|
|
41
|
+
url: string;
|
|
42
|
+
alt: string;
|
|
43
|
+
thumbnail?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ImageGalleryProps extends ViewProps {
|
|
47
|
+
/** Array of product images */
|
|
48
|
+
images: GalleryImage[];
|
|
49
|
+
|
|
50
|
+
/** Product name for accessibility */
|
|
51
|
+
productName: string;
|
|
52
|
+
|
|
53
|
+
/** Gallery display variant */
|
|
54
|
+
variant?: 'thumbnails' | 'carousel' | 'grid';
|
|
55
|
+
|
|
56
|
+
/** Position of thumbnails (only for thumbnails variant) */
|
|
57
|
+
thumbnailPosition?: 'left' | 'bottom' | 'right';
|
|
58
|
+
|
|
59
|
+
/** Aspect ratio for main image */
|
|
60
|
+
aspectRatio?: string;
|
|
61
|
+
|
|
62
|
+
/** Enable zoom functionality */
|
|
63
|
+
showZoom?: boolean;
|
|
64
|
+
|
|
65
|
+
/** Maximum number of images to display */
|
|
66
|
+
maxImages?: number;
|
|
67
|
+
|
|
68
|
+
/** Data source for dynamic loading */
|
|
69
|
+
dataSource?: string;
|
|
70
|
+
|
|
71
|
+
/** Data binding configuration */
|
|
72
|
+
bindingOptions?: Record<string, unknown>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// View component - handles the actual rendering
|
|
76
|
+
function ImageGalleryView({
|
|
77
|
+
images = [],
|
|
78
|
+
productName,
|
|
79
|
+
variant = 'thumbnails',
|
|
80
|
+
thumbnailPosition = 'left',
|
|
81
|
+
aspectRatio = '1',
|
|
82
|
+
showZoom = true,
|
|
83
|
+
maxImages,
|
|
84
|
+
dataSource,
|
|
85
|
+
bindingOptions,
|
|
86
|
+
...restProps
|
|
87
|
+
}: ImageGalleryProps) {
|
|
88
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
89
|
+
const [zoomOpen, setZoomOpen] = useState(false);
|
|
90
|
+
|
|
91
|
+
// Limit images if maxImages is specified
|
|
92
|
+
const displayImages = maxImages ? images.slice(0, maxImages) : images;
|
|
93
|
+
|
|
94
|
+
const handlePrevious = useCallback(() => {
|
|
95
|
+
setSelectedIndex((prev) => (prev === 0 ? displayImages.length - 1 : prev - 1));
|
|
96
|
+
}, [displayImages.length]);
|
|
97
|
+
|
|
98
|
+
const handleNext = useCallback(() => {
|
|
99
|
+
setSelectedIndex((prev) => (prev === displayImages.length - 1 ? 0 : prev + 1));
|
|
100
|
+
}, [displayImages.length]);
|
|
101
|
+
|
|
102
|
+
const handleThumbnailClick = useCallback((index: number) => {
|
|
103
|
+
setSelectedIndex(index);
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
const handleZoomOpen = useCallback(() => {
|
|
107
|
+
if (showZoom) {
|
|
108
|
+
setZoomOpen(true);
|
|
109
|
+
}
|
|
110
|
+
}, [showZoom]);
|
|
111
|
+
|
|
112
|
+
const handleZoomClose = useCallback(() => {
|
|
113
|
+
setZoomOpen(false);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
// Handle empty images
|
|
117
|
+
if (!displayImages || displayImages.length === 0) {
|
|
118
|
+
return (
|
|
119
|
+
<Box
|
|
120
|
+
{...restProps}
|
|
121
|
+
sx={{
|
|
122
|
+
backgroundColor: 'var(--theme-surface-variant)',
|
|
123
|
+
borderRadius: 'var(--theme-border-radius)',
|
|
124
|
+
display: 'flex',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
justifyContent: 'center',
|
|
127
|
+
aspectRatio,
|
|
128
|
+
minHeight: 400,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<Skeleton
|
|
132
|
+
variant="rectangular"
|
|
133
|
+
width="100%"
|
|
134
|
+
height="100%"
|
|
135
|
+
sx={{
|
|
136
|
+
borderRadius: 'var(--theme-border-radius)',
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
</Box>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const currentImage = displayImages[selectedIndex];
|
|
144
|
+
|
|
145
|
+
// Render thumbnails
|
|
146
|
+
const renderThumbnails = () => (
|
|
147
|
+
<Box
|
|
148
|
+
sx={{
|
|
149
|
+
display: 'flex',
|
|
150
|
+
flexDirection: thumbnailPosition === 'left' || thumbnailPosition === 'right' ? 'column' : 'row',
|
|
151
|
+
gap: 1,
|
|
152
|
+
overflow: 'auto',
|
|
153
|
+
maxHeight: thumbnailPosition === 'left' || thumbnailPosition === 'right' ? 500 : 'auto',
|
|
154
|
+
maxWidth: thumbnailPosition === 'bottom' ? '100%' : 'auto',
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{displayImages.map((image, index) => (
|
|
158
|
+
<Box
|
|
159
|
+
key={index}
|
|
160
|
+
onClick={() => handleThumbnailClick(index)}
|
|
161
|
+
sx={{
|
|
162
|
+
width: 80,
|
|
163
|
+
height: 80,
|
|
164
|
+
flexShrink: 0,
|
|
165
|
+
cursor: 'pointer',
|
|
166
|
+
border: '2px solid',
|
|
167
|
+
borderColor: index === selectedIndex ? 'var(--theme-primary)' : 'var(--theme-border-main)',
|
|
168
|
+
borderRadius: 'var(--theme-border-radius-small)',
|
|
169
|
+
overflow: 'hidden',
|
|
170
|
+
transition: 'all 0.2s ease-in-out',
|
|
171
|
+
'&:hover': {
|
|
172
|
+
borderColor: 'var(--theme-border-emphasis)',
|
|
173
|
+
},
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
<img
|
|
177
|
+
src={image.thumbnail || image.url}
|
|
178
|
+
alt={`${productName} thumbnail ${index + 1}`}
|
|
179
|
+
style={{
|
|
180
|
+
width: '100%',
|
|
181
|
+
height: '100%',
|
|
182
|
+
objectFit: 'cover',
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
</Box>
|
|
186
|
+
))}
|
|
187
|
+
</Box>
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Render main image container
|
|
191
|
+
const renderMainImage = () => (
|
|
192
|
+
<Box
|
|
193
|
+
sx={{
|
|
194
|
+
position: 'relative',
|
|
195
|
+
width: '100%',
|
|
196
|
+
backgroundColor: 'var(--theme-surface)',
|
|
197
|
+
borderRadius: 'var(--theme-border-radius)',
|
|
198
|
+
border: '1px solid var(--theme-border-main)',
|
|
199
|
+
overflow: 'hidden',
|
|
200
|
+
aspectRatio,
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
<img
|
|
204
|
+
src={currentImage.url}
|
|
205
|
+
alt={currentImage.alt || `${productName} - Image ${selectedIndex + 1}`}
|
|
206
|
+
style={{
|
|
207
|
+
width: '100%',
|
|
208
|
+
height: '100%',
|
|
209
|
+
objectFit: 'contain',
|
|
210
|
+
display: 'block',
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
{/* Zoom button */}
|
|
215
|
+
{showZoom && (
|
|
216
|
+
<IconButton
|
|
217
|
+
onClick={handleZoomOpen}
|
|
218
|
+
sx={{
|
|
219
|
+
position: 'absolute',
|
|
220
|
+
top: 8,
|
|
221
|
+
right: 8,
|
|
222
|
+
backgroundColor: 'var(--theme-surface)',
|
|
223
|
+
color: 'var(--theme-text-primary)',
|
|
224
|
+
'&:hover': {
|
|
225
|
+
backgroundColor: 'var(--theme-surface-variant)',
|
|
226
|
+
},
|
|
227
|
+
}}
|
|
228
|
+
aria-label="Zoom image"
|
|
229
|
+
>
|
|
230
|
+
<ZoomInIcon />
|
|
231
|
+
</IconButton>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{/* Navigation arrows for carousel */}
|
|
235
|
+
{variant === 'carousel' && displayImages.length > 1 && (
|
|
236
|
+
<>
|
|
237
|
+
<IconButton
|
|
238
|
+
onClick={handlePrevious}
|
|
239
|
+
sx={{
|
|
240
|
+
position: 'absolute',
|
|
241
|
+
left: 8,
|
|
242
|
+
top: '50%',
|
|
243
|
+
transform: 'translateY(-50%)',
|
|
244
|
+
backgroundColor: 'var(--theme-surface)',
|
|
245
|
+
color: 'var(--theme-text-primary)',
|
|
246
|
+
'&:hover': {
|
|
247
|
+
backgroundColor: 'var(--theme-surface-variant)',
|
|
248
|
+
},
|
|
249
|
+
}}
|
|
250
|
+
aria-label="Previous image"
|
|
251
|
+
>
|
|
252
|
+
<ChevronLeftIcon />
|
|
253
|
+
</IconButton>
|
|
254
|
+
<IconButton
|
|
255
|
+
onClick={handleNext}
|
|
256
|
+
sx={{
|
|
257
|
+
position: 'absolute',
|
|
258
|
+
right: 8,
|
|
259
|
+
top: '50%',
|
|
260
|
+
transform: 'translateY(-50%)',
|
|
261
|
+
backgroundColor: 'var(--theme-surface)',
|
|
262
|
+
color: 'var(--theme-text-primary)',
|
|
263
|
+
'&:hover': {
|
|
264
|
+
backgroundColor: 'var(--theme-surface-variant)',
|
|
265
|
+
},
|
|
266
|
+
}}
|
|
267
|
+
aria-label="Next image"
|
|
268
|
+
>
|
|
269
|
+
<ChevronRightIcon />
|
|
270
|
+
</IconButton>
|
|
271
|
+
</>
|
|
272
|
+
)}
|
|
273
|
+
</Box>
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Render zoom modal
|
|
277
|
+
const renderZoomModal = () => (
|
|
278
|
+
<Modal
|
|
279
|
+
open={zoomOpen}
|
|
280
|
+
onClose={handleZoomClose}
|
|
281
|
+
sx={{
|
|
282
|
+
display: 'flex',
|
|
283
|
+
alignItems: 'center',
|
|
284
|
+
justifyContent: 'center',
|
|
285
|
+
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
<Box
|
|
289
|
+
sx={{
|
|
290
|
+
position: 'relative',
|
|
291
|
+
maxWidth: '90vw',
|
|
292
|
+
maxHeight: '90vh',
|
|
293
|
+
outline: 'none',
|
|
294
|
+
}}
|
|
295
|
+
>
|
|
296
|
+
<IconButton
|
|
297
|
+
onClick={handleZoomClose}
|
|
298
|
+
sx={{
|
|
299
|
+
position: 'absolute',
|
|
300
|
+
top: -40,
|
|
301
|
+
right: 0,
|
|
302
|
+
color: 'white',
|
|
303
|
+
'&:hover': {
|
|
304
|
+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
305
|
+
},
|
|
306
|
+
}}
|
|
307
|
+
aria-label="Close zoom"
|
|
308
|
+
>
|
|
309
|
+
<CloseIcon />
|
|
310
|
+
</IconButton>
|
|
311
|
+
<img
|
|
312
|
+
src={currentImage.url}
|
|
313
|
+
alt={currentImage.alt || `${productName} - Zoomed view`}
|
|
314
|
+
style={{
|
|
315
|
+
maxWidth: '100%',
|
|
316
|
+
maxHeight: '90vh',
|
|
317
|
+
objectFit: 'contain',
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
</Box>
|
|
321
|
+
</Modal>
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
// Render thumbnails variant
|
|
325
|
+
if (variant === 'thumbnails') {
|
|
326
|
+
return (
|
|
327
|
+
<Box {...restProps}>
|
|
328
|
+
<Box
|
|
329
|
+
sx={{
|
|
330
|
+
display: 'flex',
|
|
331
|
+
flexDirection: {
|
|
332
|
+
xs: 'column',
|
|
333
|
+
sm: thumbnailPosition === 'bottom' ? 'column' : 'row',
|
|
334
|
+
},
|
|
335
|
+
gap: 2,
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
{/* Thumbnails on left */}
|
|
339
|
+
{thumbnailPosition === 'left' && (
|
|
340
|
+
<Box sx={{ order: { xs: 2, sm: 1 } }}>
|
|
341
|
+
{renderThumbnails()}
|
|
342
|
+
</Box>
|
|
343
|
+
)}
|
|
344
|
+
|
|
345
|
+
{/* Main image */}
|
|
346
|
+
<Box sx={{ flex: 1, order: { xs: 1, sm: thumbnailPosition === 'left' ? 2 : 1 } }}>
|
|
347
|
+
{renderMainImage()}
|
|
348
|
+
</Box>
|
|
349
|
+
|
|
350
|
+
{/* Thumbnails on right */}
|
|
351
|
+
{thumbnailPosition === 'right' && (
|
|
352
|
+
<Box sx={{ order: { xs: 2, sm: 2 } }}>
|
|
353
|
+
{renderThumbnails()}
|
|
354
|
+
</Box>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{/* Thumbnails on bottom */}
|
|
358
|
+
{thumbnailPosition === 'bottom' && (
|
|
359
|
+
<Box sx={{ order: 2 }}>
|
|
360
|
+
{renderThumbnails()}
|
|
361
|
+
</Box>
|
|
362
|
+
)}
|
|
363
|
+
</Box>
|
|
364
|
+
{renderZoomModal()}
|
|
365
|
+
</Box>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Render carousel variant
|
|
370
|
+
if (variant === 'carousel') {
|
|
371
|
+
return (
|
|
372
|
+
<Box {...restProps}>
|
|
373
|
+
{renderMainImage()}
|
|
374
|
+
{/* Dot indicators */}
|
|
375
|
+
{displayImages.length > 1 && (
|
|
376
|
+
<Box
|
|
377
|
+
sx={{
|
|
378
|
+
display: 'flex',
|
|
379
|
+
justifyContent: 'center',
|
|
380
|
+
gap: 1,
|
|
381
|
+
mt: 2,
|
|
382
|
+
}}
|
|
383
|
+
>
|
|
384
|
+
{displayImages.map((_, index) => (
|
|
385
|
+
<Box
|
|
386
|
+
key={index}
|
|
387
|
+
onClick={() => handleThumbnailClick(index)}
|
|
388
|
+
sx={{
|
|
389
|
+
width: 10,
|
|
390
|
+
height: 10,
|
|
391
|
+
borderRadius: '50%',
|
|
392
|
+
backgroundColor: index === selectedIndex ? 'var(--theme-primary)' : 'var(--theme-border-main)',
|
|
393
|
+
cursor: 'pointer',
|
|
394
|
+
transition: 'all 0.2s ease-in-out',
|
|
395
|
+
'&:hover': {
|
|
396
|
+
backgroundColor: index === selectedIndex ? 'var(--theme-primary)' : 'var(--theme-border-emphasis)',
|
|
397
|
+
},
|
|
398
|
+
}}
|
|
399
|
+
/>
|
|
400
|
+
))}
|
|
401
|
+
</Box>
|
|
402
|
+
)}
|
|
403
|
+
{renderZoomModal()}
|
|
404
|
+
</Box>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Render grid variant
|
|
409
|
+
if (variant === 'grid') {
|
|
410
|
+
return (
|
|
411
|
+
<Box {...restProps}>
|
|
412
|
+
<Grid container spacing={2}>
|
|
413
|
+
{displayImages.map((image, index) => (
|
|
414
|
+
<Grid item xs={6} sm={4} md={3} key={index}>
|
|
415
|
+
<Box
|
|
416
|
+
onClick={() => {
|
|
417
|
+
setSelectedIndex(index);
|
|
418
|
+
handleZoomOpen();
|
|
419
|
+
}}
|
|
420
|
+
sx={{
|
|
421
|
+
width: '100%',
|
|
422
|
+
aspectRatio: '1',
|
|
423
|
+
borderRadius: 'var(--theme-border-radius)',
|
|
424
|
+
border: '1px solid var(--theme-border-main)',
|
|
425
|
+
overflow: 'hidden',
|
|
426
|
+
cursor: showZoom ? 'pointer' : 'default',
|
|
427
|
+
transition: 'all 0.2s ease-in-out',
|
|
428
|
+
'&:hover': {
|
|
429
|
+
borderColor: 'var(--theme-border-emphasis)',
|
|
430
|
+
boxShadow: 'var(--theme-elevation-1)',
|
|
431
|
+
},
|
|
432
|
+
}}
|
|
433
|
+
>
|
|
434
|
+
<img
|
|
435
|
+
src={image.url}
|
|
436
|
+
alt={image.alt || `${productName} - Image ${index + 1}`}
|
|
437
|
+
style={{
|
|
438
|
+
width: '100%',
|
|
439
|
+
height: '100%',
|
|
440
|
+
objectFit: 'cover',
|
|
441
|
+
}}
|
|
442
|
+
/>
|
|
443
|
+
</Box>
|
|
444
|
+
</Grid>
|
|
445
|
+
))}
|
|
446
|
+
</Grid>
|
|
447
|
+
{renderZoomModal()}
|
|
448
|
+
</Box>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Create the serializable ImageGallery component using the factory
|
|
456
|
+
export const ImageGallery: SerializableComponent<ImageGalleryProps> =
|
|
457
|
+
createSerializableView<ImageGalleryProps>({
|
|
458
|
+
tagName: 'ImageGallery',
|
|
459
|
+
version: '1.0.0',
|
|
460
|
+
role: 'view',
|
|
461
|
+
View: ImageGalleryView,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
export default ImageGallery;
|