@qwickapps/react-framework 1.5.12 → 1.5.13
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/index.esm.js +1192 -265
- package/dist/index.js +1194 -263
- package/dist/palettes/manifest.json +19 -19
- 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 +2 -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/schemas/ImageGallerySchema.ts +148 -0
- package/src/schemas/OptionSelectorSchema.ts +216 -0
- package/src/schemas/index.ts +2 -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.5.13.css} +0 -0
- /package/dist/palettes/{palette-autumn.1.5.12.min.css → palette-autumn.1.5.13.min.css} +0 -0
- /package/dist/palettes/{palette-cosmic.1.5.12.css → palette-cosmic.1.5.13.css} +0 -0
- /package/dist/palettes/{palette-cosmic.1.5.12.min.css → palette-cosmic.1.5.13.min.css} +0 -0
- /package/dist/palettes/{palette-default.1.5.12.css → palette-default.1.5.13.css} +0 -0
- /package/dist/palettes/{palette-default.1.5.12.min.css → palette-default.1.5.13.min.css} +0 -0
- /package/dist/palettes/{palette-ocean.1.5.12.css → palette-ocean.1.5.13.css} +0 -0
- /package/dist/palettes/{palette-ocean.1.5.12.min.css → palette-ocean.1.5.13.min.css} +0 -0
- /package/dist/palettes/{palette-spring.1.5.12.css → palette-spring.1.5.13.css} +0 -0
- /package/dist/palettes/{palette-spring.1.5.12.min.css → palette-spring.1.5.13.min.css} +0 -0
- /package/dist/palettes/{palette-winter.1.5.12.css → palette-winter.1.5.13.css} +0 -0
- /package/dist/palettes/{palette-winter.1.5.12.min.css → palette-winter.1.5.13.min.css} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./manifest.schema.json",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.13",
|
|
4
4
|
"palettes": [
|
|
5
5
|
{
|
|
6
6
|
"id": "default",
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"description": "Classic blue and neutral color scheme - the original QwickApps palette",
|
|
9
9
|
"author": "QwickApps",
|
|
10
10
|
"license": "PolyForm-Shield-1.0.0",
|
|
11
|
-
"version": "1.5.
|
|
12
|
-
"file": "palette-default.1.5.
|
|
11
|
+
"version": "1.5.13",
|
|
12
|
+
"file": "palette-default.1.5.13.css",
|
|
13
13
|
"primaryColor": "#007bff",
|
|
14
14
|
"inlined": true,
|
|
15
|
-
"fileMinified": "palette-default.1.5.
|
|
15
|
+
"fileMinified": "palette-default.1.5.13.min.css",
|
|
16
16
|
"fileLatest": "palette-default.latest.css",
|
|
17
17
|
"fileLatestMinified": "palette-default.latest.min.css"
|
|
18
18
|
},
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"description": "Warm oranges, golden yellows, and earthy browns - inspired by fall foliage",
|
|
23
23
|
"author": "QwickApps",
|
|
24
24
|
"license": "PolyForm-Shield-1.0.0",
|
|
25
|
-
"version": "1.5.
|
|
26
|
-
"file": "palette-autumn.1.5.
|
|
25
|
+
"version": "1.5.13",
|
|
26
|
+
"file": "palette-autumn.1.5.13.css",
|
|
27
27
|
"primaryColor": "#ea580c",
|
|
28
28
|
"inlined": false,
|
|
29
|
-
"fileMinified": "palette-autumn.1.5.
|
|
29
|
+
"fileMinified": "palette-autumn.1.5.13.min.css",
|
|
30
30
|
"fileLatest": "palette-autumn.latest.css",
|
|
31
31
|
"fileLatestMinified": "palette-autumn.latest.min.css"
|
|
32
32
|
},
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"description": "Modern purple gradient for creative and tech brands - inspired by cosmic nebulae",
|
|
37
37
|
"author": "QwickApps",
|
|
38
38
|
"license": "PolyForm-Shield-1.0.0",
|
|
39
|
-
"version": "1.5.
|
|
40
|
-
"file": "palette-cosmic.1.5.
|
|
39
|
+
"version": "1.5.13",
|
|
40
|
+
"file": "palette-cosmic.1.5.13.css",
|
|
41
41
|
"primaryColor": "#8b5cf6",
|
|
42
42
|
"inlined": false,
|
|
43
|
-
"fileMinified": "palette-cosmic.1.5.
|
|
43
|
+
"fileMinified": "palette-cosmic.1.5.13.min.css",
|
|
44
44
|
"fileLatest": "palette-cosmic.latest.css",
|
|
45
45
|
"fileLatestMinified": "palette-cosmic.latest.min.css"
|
|
46
46
|
},
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"description": "Deep blues, aqua teals, and seafoam greens - inspired by ocean depths",
|
|
51
51
|
"author": "QwickApps",
|
|
52
52
|
"license": "PolyForm-Shield-1.0.0",
|
|
53
|
-
"version": "1.5.
|
|
54
|
-
"file": "palette-ocean.1.5.
|
|
53
|
+
"version": "1.5.13",
|
|
54
|
+
"file": "palette-ocean.1.5.13.css",
|
|
55
55
|
"primaryColor": "#0891b2",
|
|
56
56
|
"inlined": false,
|
|
57
|
-
"fileMinified": "palette-ocean.1.5.
|
|
57
|
+
"fileMinified": "palette-ocean.1.5.13.min.css",
|
|
58
58
|
"fileLatest": "palette-ocean.latest.css",
|
|
59
59
|
"fileLatestMinified": "palette-ocean.latest.min.css"
|
|
60
60
|
},
|
|
@@ -64,11 +64,11 @@
|
|
|
64
64
|
"description": "Fresh greens, soft pinks, and bright yellows - inspired by spring blooms",
|
|
65
65
|
"author": "QwickApps",
|
|
66
66
|
"license": "PolyForm-Shield-1.0.0",
|
|
67
|
-
"version": "1.5.
|
|
68
|
-
"file": "palette-spring.1.5.
|
|
67
|
+
"version": "1.5.13",
|
|
68
|
+
"file": "palette-spring.1.5.13.css",
|
|
69
69
|
"primaryColor": "#16a34a",
|
|
70
70
|
"inlined": false,
|
|
71
|
-
"fileMinified": "palette-spring.1.5.
|
|
71
|
+
"fileMinified": "palette-spring.1.5.13.min.css",
|
|
72
72
|
"fileLatest": "palette-spring.latest.css",
|
|
73
73
|
"fileLatestMinified": "palette-spring.latest.min.css"
|
|
74
74
|
},
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"description": "Cool blues, icy whites, and frosty grays - inspired by winter landscapes",
|
|
79
79
|
"author": "QwickApps",
|
|
80
80
|
"license": "PolyForm-Shield-1.0.0",
|
|
81
|
-
"version": "1.5.
|
|
82
|
-
"file": "palette-winter.1.5.
|
|
81
|
+
"version": "1.5.13",
|
|
82
|
+
"file": "palette-winter.1.5.13.css",
|
|
83
83
|
"primaryColor": "#0077be",
|
|
84
84
|
"inlined": false,
|
|
85
|
-
"fileMinified": "palette-winter.1.5.
|
|
85
|
+
"fileMinified": "palette-winter.1.5.13.min.css",
|
|
86
86
|
"fileLatest": "palette-winter.latest.css",
|
|
87
87
|
"fileLatestMinified": "palette-winter.latest.min.css"
|
|
88
88
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for ImageGallery component - Image gallery with multiple view variants
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
import 'reflect-metadata';
|
|
7
|
+
import { ViewSchema } from './ViewSchema';
|
|
8
|
+
export declare class GalleryImageModel {
|
|
9
|
+
url: string;
|
|
10
|
+
alt: string;
|
|
11
|
+
thumbnail?: string;
|
|
12
|
+
}
|
|
13
|
+
export type GalleryVariant = 'thumbnails' | 'carousel' | 'grid';
|
|
14
|
+
export type ThumbnailPosition = 'left' | 'bottom' | 'right';
|
|
15
|
+
export declare class ImageGalleryModel extends ViewSchema {
|
|
16
|
+
images: GalleryImageModel[];
|
|
17
|
+
productName: string;
|
|
18
|
+
variant?: GalleryVariant;
|
|
19
|
+
thumbnailPosition?: ThumbnailPosition;
|
|
20
|
+
aspectRatio?: string;
|
|
21
|
+
showZoom?: boolean;
|
|
22
|
+
maxImages?: number;
|
|
23
|
+
dataSource?: string;
|
|
24
|
+
bindingOptions?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
export default ImageGalleryModel;
|
|
27
|
+
//# sourceMappingURL=ImageGallerySchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageGallerySchema.d.ts","sourceRoot":"","sources":["../../src/schemas/ImageGallerySchema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,qBAAa,iBAAiB;IAG5B,GAAG,EAAG,MAAM,CAAC;IAIb,GAAG,EAAG,MAAM,CAAC;IAKb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAGhE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE5D,qBACa,iBAAkB,SAAQ,UAAU;IAU/C,MAAM,EAAG,iBAAiB,EAAE,CAAC;IAU7B,WAAW,EAAG,MAAM,CAAC;IAkBrB,OAAO,CAAC,EAAE,cAAc,CAAC;IAkBzB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAWtC,WAAW,CAAC,EAAE,MAAM,CAAC;IAUrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAWnB,SAAS,CAAC,EAAE,MAAM,CAAC;IAWnB,UAAU,CAAC,EAAE,MAAM,CAAC;IAUpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for OptionSelector component - Universal option selection with visual modes
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
import 'reflect-metadata';
|
|
7
|
+
import { ViewSchema } from './ViewSchema';
|
|
8
|
+
export declare class SelectOptionModel {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
available: boolean;
|
|
12
|
+
price?: number;
|
|
13
|
+
hexValue?: string;
|
|
14
|
+
imageUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export type DisplayMode = 'text' | 'color' | 'image';
|
|
17
|
+
export type OptionVariant = 'buttons' | 'dropdown' | 'grid';
|
|
18
|
+
export type OptionLayout = 'horizontal' | 'vertical' | 'wrap';
|
|
19
|
+
export type VisualSize = 'small' | 'medium' | 'large';
|
|
20
|
+
export declare class OptionSelectorModel extends ViewSchema {
|
|
21
|
+
options: SelectOptionModel[];
|
|
22
|
+
selectedOption?: string;
|
|
23
|
+
displayMode?: DisplayMode;
|
|
24
|
+
variant?: OptionVariant;
|
|
25
|
+
layout?: OptionLayout;
|
|
26
|
+
visualSize?: VisualSize;
|
|
27
|
+
showLabel?: boolean;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
label?: string;
|
|
30
|
+
dataSource?: string;
|
|
31
|
+
bindingOptions?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export default OptionSelectorModel;
|
|
34
|
+
//# sourceMappingURL=OptionSelectorSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OptionSelectorSchema.d.ts","sourceRoot":"","sources":["../../src/schemas/OptionSelectorSchema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,qBAAa,iBAAiB;IAG5B,EAAE,EAAG,MAAM,CAAC;IAIZ,KAAK,EAAG,MAAM,CAAC;IAIf,SAAS,EAAG,OAAO,CAAC;IAKpB,KAAK,CAAC,EAAE,MAAM,CAAC;IAWf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAWlB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAGrD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAG5D,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAG9D,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,qBACa,mBAAoB,SAAQ,UAAU;IAUjD,OAAO,EAAG,iBAAiB,EAAE,CAAC;IAW9B,cAAc,CAAC,EAAE,MAAM,CAAC;IAkBxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAkB1B,OAAO,CAAC,EAAE,aAAa,CAAC;IAkBxB,MAAM,CAAC,EAAE,YAAY,CAAC;IAkBtB,UAAU,CAAC,EAAE,UAAU,CAAC;IAUxB,SAAS,CAAC,EAAE,OAAO,CAAC;IAUpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAWnB,KAAK,CAAC,EAAE,MAAM,CAAC;IAWf,UAAU,CAAC,EAAE,MAAM,CAAC;IAUpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,eAAe,mBAAmB,CAAC"}
|
package/dist/schemas/index.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export * from './MetadataItemSchema';
|
|
|
36
36
|
export * from './PageBannerHeaderSchema';
|
|
37
37
|
export * from './PaletteSwitcherSchema';
|
|
38
38
|
export * from './ProductCardSchema';
|
|
39
|
+
export * from './ImageGallerySchema';
|
|
40
|
+
export * from './OptionSelectorSchema';
|
|
39
41
|
export * from './SafeSpanSchema';
|
|
40
42
|
export * from './SectionSchema';
|
|
41
43
|
export * from './TextInputFieldSchema';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAE3E,OAAO,yBAAyB,CAAC;AAGjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AAGtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAE3E,OAAO,yBAAyB,CAAC;AAGjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AAGtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qwickapps/react-framework",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.13",
|
|
4
4
|
"description": "Complete React framework with responsive navigation, flexible layouts, theming system, and reusable components for building modern applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -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;
|