datastake-daf 0.6.206 → 0.6.207
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/index.js +129 -15
- package/package.json +1 -1
- package/public/assets/images/no-image.svg +18 -0
- package/src/@daf/core/components/Dashboard/Widget/CarouselWidget/index.jsx +6 -4
- package/src/@daf/core/components/Dashboard/Widget/ImageCarousel/ImageCarousel.stories.js +83 -1
- package/src/@daf/core/components/Dashboard/Widget/ImageCarousel/components/CustomArrowButton/index.js +52 -0
- package/src/@daf/core/components/Dashboard/Widget/ImageCarousel/index.jsx +95 -23
- package/src/@daf/core/components/Dashboard/Widget/ImageCarousel/style.js +15 -0
- package/.env +0 -8
- package/.vscode/settings.json +0 -13
package/dist/components/index.js
CHANGED
|
@@ -21226,7 +21226,7 @@ const WidgetCard = _ref => {
|
|
|
21226
21226
|
};
|
|
21227
21227
|
|
|
21228
21228
|
const _excluded$e = ["title", "children"];
|
|
21229
|
-
|
|
21229
|
+
const CarouselWidget = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
21230
21230
|
let {
|
|
21231
21231
|
title,
|
|
21232
21232
|
children
|
|
@@ -21241,19 +21241,91 @@ function CarouselWidget(_ref) {
|
|
|
21241
21241
|
return /*#__PURE__*/jsxRuntime.jsx(Widget, {
|
|
21242
21242
|
title: title,
|
|
21243
21243
|
className: "with-border-header h-w-btn-header",
|
|
21244
|
-
children: /*#__PURE__*/jsxRuntime.jsx(antd.Carousel, _objectSpread2(_objectSpread2({
|
|
21244
|
+
children: /*#__PURE__*/jsxRuntime.jsx(antd.Carousel, _objectSpread2(_objectSpread2({
|
|
21245
|
+
ref: ref
|
|
21246
|
+
}, rest), {}, {
|
|
21245
21247
|
children: children
|
|
21246
21248
|
}))
|
|
21247
21249
|
});
|
|
21250
|
+
});
|
|
21251
|
+
CarouselWidget.displayName = 'CarouselWidget';
|
|
21252
|
+
|
|
21253
|
+
function CustomArrowButton({
|
|
21254
|
+
icon,
|
|
21255
|
+
onClick = () => {},
|
|
21256
|
+
isLeft = false,
|
|
21257
|
+
iconColor = "#666",
|
|
21258
|
+
hoverIconColor = "#1890ff"
|
|
21259
|
+
}) {
|
|
21260
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
21261
|
+
|
|
21262
|
+
// Clone the icon and add color styling
|
|
21263
|
+
const styledIcon = icon && typeof icon === 'object' ? {
|
|
21264
|
+
...icon,
|
|
21265
|
+
props: {
|
|
21266
|
+
...icon.props,
|
|
21267
|
+
style: {
|
|
21268
|
+
color: isHovered ? hoverIconColor : iconColor
|
|
21269
|
+
}
|
|
21270
|
+
}
|
|
21271
|
+
} : icon;
|
|
21272
|
+
return /*#__PURE__*/jsxRuntime.jsx(antd.Button, {
|
|
21273
|
+
type: "default",
|
|
21274
|
+
shape: "circle",
|
|
21275
|
+
icon: styledIcon,
|
|
21276
|
+
onClick: onClick,
|
|
21277
|
+
style: {
|
|
21278
|
+
position: "absolute",
|
|
21279
|
+
right: !isLeft ? "40px" : "auto",
|
|
21280
|
+
left: !isLeft ? "auto" : "40px",
|
|
21281
|
+
top: "58%",
|
|
21282
|
+
transform: "translateY(-50%)",
|
|
21283
|
+
zIndex: 10,
|
|
21284
|
+
backgroundColor: "rgba(255, 255, 255, 0.4)",
|
|
21285
|
+
border: "1px solid rgba(0, 0, 0, 0.1)",
|
|
21286
|
+
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
|
|
21287
|
+
width: "40px",
|
|
21288
|
+
height: "40px",
|
|
21289
|
+
display: "flex",
|
|
21290
|
+
alignItems: "center",
|
|
21291
|
+
justifyContent: "center"
|
|
21292
|
+
},
|
|
21293
|
+
onMouseEnter: e => {
|
|
21294
|
+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 1)";
|
|
21295
|
+
setIsHovered(true);
|
|
21296
|
+
},
|
|
21297
|
+
onMouseLeave: e => {
|
|
21298
|
+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.4)";
|
|
21299
|
+
setIsHovered(false);
|
|
21300
|
+
}
|
|
21301
|
+
});
|
|
21248
21302
|
}
|
|
21249
21303
|
|
|
21250
|
-
const
|
|
21304
|
+
const StyledCarouselWrapper = dt.div`
|
|
21305
|
+
position: relative;
|
|
21306
|
+
|
|
21307
|
+
.ant-carousel .slick-dots li button {
|
|
21308
|
+
background: ${props => props.inactiveDotColor} !important;
|
|
21309
|
+
opacity: 1 !important;
|
|
21310
|
+
}
|
|
21311
|
+
|
|
21312
|
+
.ant-carousel .slick-dots li.slick-active button {
|
|
21313
|
+
background: ${props => props.activeDotColor} !important;
|
|
21314
|
+
opacity: 1 !important;
|
|
21315
|
+
}
|
|
21316
|
+
`;
|
|
21317
|
+
|
|
21318
|
+
const _excluded$d = ["title", "images", "height", "fallback", "activeDotColor", "inactiveDotColor", "arrowIconColor", "arrowHoverIconColor"];
|
|
21251
21319
|
function ImageCarousel(_ref) {
|
|
21252
21320
|
let {
|
|
21253
21321
|
title,
|
|
21254
21322
|
images,
|
|
21255
21323
|
height = 400,
|
|
21256
|
-
fallback = "/assets/images/empty-box.svg"
|
|
21324
|
+
fallback = "/assets/images/empty-box.svg",
|
|
21325
|
+
activeDotColor = "#1890ff",
|
|
21326
|
+
inactiveDotColor = "rgba(255, 255, 255, 0.3)",
|
|
21327
|
+
arrowIconColor = "#666",
|
|
21328
|
+
arrowHoverIconColor = "#1890ff"
|
|
21257
21329
|
} = _ref,
|
|
21258
21330
|
rest = _objectWithoutProperties(_ref, _excluded$d);
|
|
21259
21331
|
const [previewVisible, setPreviewVisible] = React.useState(false);
|
|
@@ -21262,16 +21334,30 @@ function ImageCarousel(_ref) {
|
|
|
21262
21334
|
const handleCarouselChange = index => {
|
|
21263
21335
|
setCurrent(index);
|
|
21264
21336
|
};
|
|
21337
|
+
const goToPrevious = () => {
|
|
21338
|
+
var _carouselRef$current;
|
|
21339
|
+
(_carouselRef$current = carouselRef.current) === null || _carouselRef$current === void 0 || _carouselRef$current.prev();
|
|
21340
|
+
};
|
|
21341
|
+
const goToNext = () => {
|
|
21342
|
+
var _carouselRef$current2;
|
|
21343
|
+
(_carouselRef$current2 = carouselRef.current) === null || _carouselRef$current2 === void 0 || _carouselRef$current2.next();
|
|
21344
|
+
};
|
|
21345
|
+
|
|
21346
|
+
// Check if images array is empty or invalid
|
|
21347
|
+
const hasImages = images && images.length > 0;
|
|
21265
21348
|
return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
21266
|
-
children: [/*#__PURE__*/jsxRuntime.
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21349
|
+
children: [/*#__PURE__*/jsxRuntime.jsxs(StyledCarouselWrapper, {
|
|
21350
|
+
activeDotColor: activeDotColor,
|
|
21351
|
+
inactiveDotColor: inactiveDotColor,
|
|
21352
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(CarouselWidget, _objectSpread2(_objectSpread2({
|
|
21353
|
+
title: title
|
|
21354
|
+
}, rest), {}, {
|
|
21355
|
+
arrows: false,
|
|
21271
21356
|
ref: carouselRef,
|
|
21272
21357
|
afterChange: handleCarouselChange,
|
|
21273
|
-
infinite:
|
|
21274
|
-
|
|
21358
|
+
infinite: hasImages,
|
|
21359
|
+
dots: hasImages,
|
|
21360
|
+
children: hasImages ? images.map((image, index) => {
|
|
21275
21361
|
const imageSrc = typeof image === "string" ? image : image.src;
|
|
21276
21362
|
const imageAlt = typeof image === "string" ? "".concat(title, " - Image ").concat(index + 1) : image.alt;
|
|
21277
21363
|
return /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
@@ -21291,9 +21377,37 @@ function ImageCarousel(_ref) {
|
|
|
21291
21377
|
}
|
|
21292
21378
|
})
|
|
21293
21379
|
}, imageSrc);
|
|
21380
|
+
}) :
|
|
21381
|
+
/*#__PURE__*/
|
|
21382
|
+
// No images - show placeholder
|
|
21383
|
+
jsxRuntime.jsx("div", {
|
|
21384
|
+
children: /*#__PURE__*/jsxRuntime.jsx(antd.Image, {
|
|
21385
|
+
src: "/assets/images/no-image.svg",
|
|
21386
|
+
alt: "No images available",
|
|
21387
|
+
height: height,
|
|
21388
|
+
width: "100%",
|
|
21389
|
+
fallback: fallback,
|
|
21390
|
+
preview: false,
|
|
21391
|
+
style: {
|
|
21392
|
+
objectFit: "contain"
|
|
21393
|
+
}
|
|
21394
|
+
})
|
|
21294
21395
|
})
|
|
21295
|
-
})
|
|
21296
|
-
|
|
21396
|
+
})), hasImages && images.length > 1 && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
21397
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(CustomArrowButton, {
|
|
21398
|
+
icon: /*#__PURE__*/jsxRuntime.jsx(Icons.LeftOutlined, {}),
|
|
21399
|
+
onClick: goToPrevious,
|
|
21400
|
+
isLeft: true,
|
|
21401
|
+
iconColor: arrowIconColor,
|
|
21402
|
+
hoverIconColor: arrowHoverIconColor
|
|
21403
|
+
}), /*#__PURE__*/jsxRuntime.jsx(CustomArrowButton, {
|
|
21404
|
+
icon: /*#__PURE__*/jsxRuntime.jsx(Icons.RightOutlined, {}),
|
|
21405
|
+
onClick: goToNext,
|
|
21406
|
+
iconColor: arrowIconColor,
|
|
21407
|
+
hoverIconColor: arrowHoverIconColor
|
|
21408
|
+
})]
|
|
21409
|
+
})]
|
|
21410
|
+
}), hasImages && /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
21297
21411
|
style: {
|
|
21298
21412
|
display: "none"
|
|
21299
21413
|
},
|
|
@@ -21303,9 +21417,9 @@ function ImageCarousel(_ref) {
|
|
|
21303
21417
|
current,
|
|
21304
21418
|
onVisibleChange: vis => setPreviewVisible(vis),
|
|
21305
21419
|
onChange: idx => {
|
|
21306
|
-
var _carouselRef$
|
|
21420
|
+
var _carouselRef$current3;
|
|
21307
21421
|
setCurrent(idx);
|
|
21308
|
-
(_carouselRef$
|
|
21422
|
+
(_carouselRef$current3 = carouselRef.current) === null || _carouselRef$current3 === void 0 || _carouselRef$current3.goTo(idx);
|
|
21309
21423
|
}
|
|
21310
21424
|
},
|
|
21311
21425
|
children: images.map((image, index) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg width="321" height="190" viewBox="0 0 321 190" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_4160_16949)">
|
|
3
|
+
<g clip-path="url(#clip1_4160_16949)">
|
|
4
|
+
<path d="M94.375 99.5C78.3569 103.095 68.5 107.992 68.5 113.388C68.5 124.421 109.69 133.364 160.5 133.364C211.31 133.364 252.5 124.421 252.5 113.388C252.5 107.992 242.643 103.095 226.625 99.5V108.208C226.625 114.266 222.83 119.232 218.144 119.232H102.856C98.17 119.232 94.375 114.263 94.375 108.208V99.5Z" fill="#F3F4F6"/>
|
|
5
|
+
<path d="M188.137 64.8152C188.137 60.2351 190.995 56.454 194.54 56.4512H226.625V108.208C226.625 114.266 222.83 119.232 218.144 119.232H102.856C98.17 119.232 94.375 114.263 94.375 108.208V56.4512H126.46C130.005 56.4512 132.863 60.2266 132.863 64.8067V64.8695C132.863 69.4496 135.752 73.1479 139.294 73.1479H181.706C185.248 73.1479 188.137 69.4153 188.137 64.8352V64.8152Z" fill="#E5E7EB" stroke="#E5E7EB"/>
|
|
6
|
+
<path d="M226.625 56.7663L197.455 23.9435C196.055 21.7063 194.011 20.3536 191.858 20.3536H129.142C126.989 20.3536 124.945 21.7063 123.545 23.9407L94.375 56.7692" stroke="#E5E7EB"/>
|
|
7
|
+
</g>
|
|
8
|
+
<path d="M133.177 162.5H131.687V152.636H133.074L138.188 159.909H138.242V152.636H139.732V162.5H138.352L133.238 155.233H133.177V162.5ZM144.586 162.589C142.439 162.589 141.072 161.126 141.072 158.788C141.072 156.457 142.446 154.98 144.586 154.98C146.726 154.98 148.093 156.45 148.093 158.788C148.093 161.126 146.732 162.589 144.586 162.589ZM144.586 161.358C145.844 161.358 146.589 160.408 146.589 158.788C146.589 157.168 145.844 156.211 144.586 156.211C143.328 156.211 142.576 157.175 142.576 158.788C142.576 160.408 143.328 161.358 144.586 161.358ZM154.19 162.5H152.659V152.636H154.19V162.5ZM155.858 162.5V155.076H157.273V156.334H157.301C157.643 155.5 158.442 154.994 159.413 154.994C160.445 154.994 161.19 155.514 161.491 156.437H161.525C161.915 155.541 162.79 154.994 163.85 154.994C165.312 154.994 166.27 155.958 166.27 157.441V162.5H164.793V157.797C164.793 156.826 164.267 156.266 163.344 156.266C162.435 156.266 161.792 156.942 161.792 157.893V162.5H160.336V157.674C160.336 156.812 159.782 156.266 158.914 156.266C157.998 156.266 157.342 156.977 157.342 157.947V162.5H155.858ZM170.33 161.393C171.431 161.393 172.265 160.688 172.265 159.711V159.13L170.46 159.253C169.448 159.314 168.922 159.69 168.922 160.347C168.922 160.982 169.476 161.393 170.33 161.393ZM169.934 162.575C168.45 162.575 167.425 161.7 167.425 160.374C167.425 159.075 168.43 158.323 170.262 158.207L172.265 158.091V157.517C172.265 156.669 171.697 156.204 170.706 156.204C169.899 156.204 169.312 156.621 169.182 157.264H167.794C167.835 155.951 169.079 154.98 170.747 154.98C172.559 154.98 173.728 155.938 173.728 157.407V162.5H172.312V161.263H172.278C171.868 162.069 170.945 162.575 169.934 162.575ZM178.369 161.249C179.572 161.249 180.372 160.265 180.372 158.768C180.372 157.264 179.572 156.259 178.369 156.259C177.173 156.259 176.4 157.243 176.4 158.768C176.4 160.285 177.173 161.249 178.369 161.249ZM178.376 165.173C176.592 165.173 175.32 164.346 175.177 163.074H176.674C176.838 163.648 177.528 164.011 178.438 164.011C179.613 164.011 180.372 163.396 180.372 162.425V161.201H180.338C179.907 162.001 179.032 162.479 177.979 162.479C176.127 162.479 174.883 161.023 174.883 158.761C174.883 156.471 176.113 155.001 178.014 155.001C179.066 155.001 179.928 155.486 180.386 156.307H180.413V155.076H181.849V162.309C181.849 164.079 180.516 165.173 178.376 165.173ZM186.456 156.19C185.39 156.19 184.617 156.97 184.535 158.111H188.302C188.268 156.956 187.536 156.19 186.456 156.19ZM188.295 160.326H189.71C189.498 161.659 188.192 162.589 186.524 162.589C184.371 162.589 183.031 161.14 183.031 158.815C183.031 156.491 184.385 154.98 186.463 154.98C188.5 154.98 189.785 156.396 189.785 158.645V159.164H184.528V159.253C184.528 160.538 185.335 161.386 186.552 161.386C187.413 161.386 188.09 160.976 188.295 160.326Z" fill="#9DA4AE"/>
|
|
9
|
+
</g>
|
|
10
|
+
<defs>
|
|
11
|
+
<clipPath id="clip0_4160_16949">
|
|
12
|
+
<rect x="0.5" y="0.5" width="320" height="189" rx="6" fill="white"/>
|
|
13
|
+
</clipPath>
|
|
14
|
+
<clipPath id="clip1_4160_16949">
|
|
15
|
+
<rect width="184" height="117" fill="white" transform="translate(68.5 16.5)"/>
|
|
16
|
+
</clipPath>
|
|
17
|
+
</defs>
|
|
18
|
+
</svg>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
2
|
import Widget from '../index.jsx'
|
|
3
3
|
import { Carousel } from 'antd';
|
|
4
4
|
|
|
@@ -46,7 +46,7 @@ import { Carousel } from 'antd';
|
|
|
46
46
|
*
|
|
47
47
|
* @returns {JSX.Element} The rendered CarouselWidget component
|
|
48
48
|
*/
|
|
49
|
-
|
|
49
|
+
const CarouselWidget = forwardRef(({title, children, ...rest}, ref) => {
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Handles carousel slide change events
|
|
@@ -59,11 +59,13 @@ function CarouselWidget({title, children, ...rest}) {
|
|
|
59
59
|
title={title}
|
|
60
60
|
className="with-border-header h-w-btn-header"
|
|
61
61
|
>
|
|
62
|
-
<Carousel {...rest}>
|
|
62
|
+
<Carousel ref={ref} {...rest}>
|
|
63
63
|
{children}
|
|
64
64
|
</Carousel>
|
|
65
65
|
</Widget>
|
|
66
66
|
)
|
|
67
|
-
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
CarouselWidget.displayName = 'CarouselWidget';
|
|
68
70
|
|
|
69
71
|
export default CarouselWidget;
|
|
@@ -17,7 +17,23 @@ export default {
|
|
|
17
17
|
argTypes: {
|
|
18
18
|
height: {
|
|
19
19
|
control: { type: 'range', min: 200, max: 600, step: 50 },
|
|
20
|
-
description: 'Height of the carousel images'
|
|
20
|
+
description: 'Height of the carousel images',
|
|
21
|
+
},
|
|
22
|
+
activeDotColor: {
|
|
23
|
+
control: { type: 'color' },
|
|
24
|
+
description: 'Color of the active dot in the carousel',
|
|
25
|
+
},
|
|
26
|
+
inactiveDotColor: {
|
|
27
|
+
control: { type: 'color' },
|
|
28
|
+
description: 'Color of inactive dots in the carousel',
|
|
29
|
+
},
|
|
30
|
+
arrowIconColor: {
|
|
31
|
+
control: { type: 'color' },
|
|
32
|
+
description: 'Default color of arrow icons',
|
|
33
|
+
},
|
|
34
|
+
arrowHoverIconColor: {
|
|
35
|
+
control: { type: 'color' },
|
|
36
|
+
description: 'Color of arrow icons on hover',
|
|
21
37
|
},
|
|
22
38
|
},
|
|
23
39
|
};
|
|
@@ -78,6 +94,7 @@ export const Primary = {
|
|
|
78
94
|
args: {
|
|
79
95
|
title: "Nature Gallery",
|
|
80
96
|
height: 400,
|
|
97
|
+
arrows: true
|
|
81
98
|
},
|
|
82
99
|
};
|
|
83
100
|
|
|
@@ -90,4 +107,69 @@ export const WithFallbacks = {
|
|
|
90
107
|
height={350}
|
|
91
108
|
/>
|
|
92
109
|
),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const EmptyState = {
|
|
113
|
+
name: "Empty State (No Images)",
|
|
114
|
+
render: (args) => (
|
|
115
|
+
<div>
|
|
116
|
+
<p style={{ marginBottom: '16px', color: '#666' }}>
|
|
117
|
+
<strong>📷 Empty State:</strong> Shows placeholder when no images are provided. Notice how arrows, dots, and preview functionality are automatically disabled.
|
|
118
|
+
</p>
|
|
119
|
+
<ImageCarousel
|
|
120
|
+
title="Empty Gallery"
|
|
121
|
+
images={[]}
|
|
122
|
+
{...args}
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
),
|
|
126
|
+
args: {
|
|
127
|
+
height: 400,
|
|
128
|
+
activeDotColor: "#1890ff",
|
|
129
|
+
inactiveDotColor: "rgba(255, 255, 255, 0.3)"
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const CustomDotColors = {
|
|
134
|
+
name: "Custom Dot Colors",
|
|
135
|
+
render: (args) => (
|
|
136
|
+
<div>
|
|
137
|
+
<p style={{ marginBottom: '16px', color: '#666' }}>
|
|
138
|
+
<strong>🎨 Custom Styling:</strong> Demonstrating custom dot colors with activeDotColor and inactiveDotColor props.
|
|
139
|
+
</p>
|
|
140
|
+
<ImageCarousel
|
|
141
|
+
title="Colorful Gallery"
|
|
142
|
+
images={sampleImages}
|
|
143
|
+
{...args}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
),
|
|
147
|
+
args: {
|
|
148
|
+
height: 400,
|
|
149
|
+
activeDotColor: "#ff4757",
|
|
150
|
+
inactiveDotColor: "rgba(255, 71, 87, 0.2)"
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const CustomArrowColors = {
|
|
155
|
+
name: "Custom Arrow Colors",
|
|
156
|
+
render: (args) => (
|
|
157
|
+
<div>
|
|
158
|
+
<p style={{ marginBottom: '16px', color: '#666' }}>
|
|
159
|
+
<strong>🎯 Arrow Styling:</strong> Demonstrating custom arrow icon colors with hover effects.
|
|
160
|
+
</p>
|
|
161
|
+
<ImageCarousel
|
|
162
|
+
title="Interactive Arrows"
|
|
163
|
+
images={sampleImages}
|
|
164
|
+
{...args}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
),
|
|
168
|
+
args: {
|
|
169
|
+
height: 400,
|
|
170
|
+
arrowIconColor: "#333", // Dark gray normally
|
|
171
|
+
arrowHoverIconColor: "#ff6b6b", // Red on hover
|
|
172
|
+
activeDotColor: "#ff6b6b",
|
|
173
|
+
inactiveDotColor: "rgba(255, 107, 107, 0.2)"
|
|
174
|
+
},
|
|
93
175
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Button } from "antd";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
function CustomArrowButton({
|
|
5
|
+
icon,
|
|
6
|
+
onClick = () => {},
|
|
7
|
+
isLeft = false,
|
|
8
|
+
iconColor = "#666",
|
|
9
|
+
hoverIconColor = "#1890ff"
|
|
10
|
+
}) {
|
|
11
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
12
|
+
|
|
13
|
+
// Clone the icon and add color styling
|
|
14
|
+
const styledIcon = icon && typeof icon === 'object' ?
|
|
15
|
+
{ ...icon, props: { ...icon.props, style: { color: isHovered ? hoverIconColor : iconColor } } } :
|
|
16
|
+
icon;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Button
|
|
20
|
+
type="default"
|
|
21
|
+
shape="circle"
|
|
22
|
+
icon={styledIcon}
|
|
23
|
+
onClick={onClick}
|
|
24
|
+
style={{
|
|
25
|
+
position: "absolute",
|
|
26
|
+
right: !isLeft ? "40px" : "auto",
|
|
27
|
+
left: !isLeft ? "auto" : "40px",
|
|
28
|
+
top: "58%",
|
|
29
|
+
transform: "translateY(-50%)",
|
|
30
|
+
zIndex: 10,
|
|
31
|
+
backgroundColor: "rgba(255, 255, 255, 0.4)",
|
|
32
|
+
border: "1px solid rgba(0, 0, 0, 0.1)",
|
|
33
|
+
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)",
|
|
34
|
+
width: "40px",
|
|
35
|
+
height: "40px",
|
|
36
|
+
display: "flex",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
justifyContent: "center",
|
|
39
|
+
}}
|
|
40
|
+
onMouseEnter={(e) => {
|
|
41
|
+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 1)";
|
|
42
|
+
setIsHovered(true);
|
|
43
|
+
}}
|
|
44
|
+
onMouseLeave={(e) => {
|
|
45
|
+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.4)";
|
|
46
|
+
setIsHovered(false);
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default CustomArrowButton
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import React, { useState, useRef } from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
2
3
|
import CarouselWidget from "../CarouselWidget/index.jsx";
|
|
3
|
-
import { Image, Carousel } from "antd";
|
|
4
|
+
import { Image, Carousel, Button } from "antd";
|
|
5
|
+
import { LeftOutlined, RightOutlined } from "@ant-design/icons";
|
|
6
|
+
import CustomArrowButton from "./components/CustomArrowButton/index.js";
|
|
7
|
+
import { StyledCarouselWrapper } from "./style.js";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* @typedef {Object} ImageObject
|
|
@@ -36,6 +40,10 @@ import { Image, Carousel } from "antd";
|
|
|
36
40
|
* @param {(string|ImageObject)[]} props.images - Array of image URLs (strings) or image objects with src and alt properties
|
|
37
41
|
* @param {number} [props.height=400] - Height of the carousel images in pixels
|
|
38
42
|
* @param {string} [props.fallback="/assets/images/empty-box.svg"] - Fallback image URL when original image fails to load
|
|
43
|
+
* @param {string} [props.activeDotColor="#1890ff"] - Color of the active dot
|
|
44
|
+
* @param {string} [props.inactiveDotColor="rgba(255, 255, 255, 0.3)"] - Color of inactive dots
|
|
45
|
+
* @param {string} [props.arrowIconColor="#666"] - Default color of arrow icons
|
|
46
|
+
* @param {string} [props.arrowHoverIconColor="#1890ff"] - Color of arrow icons on hover
|
|
39
47
|
* @param {Object} [props.rest] - Additional props passed to the underlying CarouselWidget component
|
|
40
48
|
*
|
|
41
49
|
* @features
|
|
@@ -46,6 +54,8 @@ import { Image, Carousel } from "antd";
|
|
|
46
54
|
* - ✅ Support for both string URLs and image objects with alt text
|
|
47
55
|
* - ✅ Synchronized carousel and preview navigation
|
|
48
56
|
* - ✅ Infinite scrolling in both carousel and preview modes
|
|
57
|
+
* - ✅ Placeholder image for empty image arrays (/assets/images/no-image.svg)
|
|
58
|
+
* - ✅ Customizable dot colors via props
|
|
49
59
|
*
|
|
50
60
|
* @accessibility
|
|
51
61
|
* - Proper alt text support for screen readers
|
|
@@ -58,6 +68,10 @@ function ImageCarousel({
|
|
|
58
68
|
images,
|
|
59
69
|
height = 400,
|
|
60
70
|
fallback = "/assets/images/empty-box.svg",
|
|
71
|
+
activeDotColor = "#1890ff",
|
|
72
|
+
inactiveDotColor = "rgba(255, 255, 255, 0.3)",
|
|
73
|
+
arrowIconColor = "#666",
|
|
74
|
+
arrowHoverIconColor = "#1890ff",
|
|
61
75
|
...rest
|
|
62
76
|
}) {
|
|
63
77
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
@@ -68,11 +82,33 @@ function ImageCarousel({
|
|
|
68
82
|
setCurrent(index);
|
|
69
83
|
};
|
|
70
84
|
|
|
85
|
+
const goToPrevious = () => {
|
|
86
|
+
carouselRef.current?.prev();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const goToNext = () => {
|
|
90
|
+
carouselRef.current?.next();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Check if images array is empty or invalid
|
|
94
|
+
const hasImages = images && images.length > 0;
|
|
95
|
+
|
|
71
96
|
return (
|
|
72
97
|
<>
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
<StyledCarouselWrapper
|
|
99
|
+
activeDotColor={activeDotColor}
|
|
100
|
+
inactiveDotColor={inactiveDotColor}
|
|
101
|
+
>
|
|
102
|
+
<CarouselWidget
|
|
103
|
+
title={title}
|
|
104
|
+
{...rest}
|
|
105
|
+
arrows={false}
|
|
106
|
+
ref={carouselRef}
|
|
107
|
+
afterChange={handleCarouselChange}
|
|
108
|
+
infinite={hasImages}
|
|
109
|
+
dots={hasImages}
|
|
110
|
+
>
|
|
111
|
+
{hasImages ? images.map((image, index) => {
|
|
76
112
|
const imageSrc = typeof image === "string" ? image : image.src;
|
|
77
113
|
const imageAlt =
|
|
78
114
|
typeof image === "string"
|
|
@@ -98,23 +134,58 @@ function ImageCarousel({
|
|
|
98
134
|
/>
|
|
99
135
|
</div>
|
|
100
136
|
);
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
|
|
137
|
+
}) : (
|
|
138
|
+
// No images - show placeholder
|
|
139
|
+
<div>
|
|
140
|
+
<Image
|
|
141
|
+
src="/assets/images/no-image.svg"
|
|
142
|
+
alt="No images available"
|
|
143
|
+
height={height}
|
|
144
|
+
width="100%"
|
|
145
|
+
fallback={fallback}
|
|
146
|
+
preview={false}
|
|
147
|
+
style={{
|
|
148
|
+
objectFit: "contain",
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</CarouselWidget>
|
|
154
|
+
|
|
155
|
+
{hasImages && images.length > 1 && (
|
|
156
|
+
<>
|
|
157
|
+
<CustomArrowButton
|
|
158
|
+
icon={<LeftOutlined />}
|
|
159
|
+
onClick={goToPrevious}
|
|
160
|
+
isLeft={true}
|
|
161
|
+
iconColor={arrowIconColor}
|
|
162
|
+
hoverIconColor={arrowHoverIconColor}
|
|
163
|
+
/>
|
|
164
|
+
<CustomArrowButton
|
|
165
|
+
icon={<RightOutlined />}
|
|
166
|
+
onClick={goToNext}
|
|
167
|
+
iconColor={arrowIconColor}
|
|
168
|
+
hoverIconColor={arrowHoverIconColor}
|
|
169
|
+
/>
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
</StyledCarouselWrapper>
|
|
104
173
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
174
|
+
{/* Preview functionality - only show if there are images */}
|
|
175
|
+
{hasImages && (
|
|
176
|
+
<div style={{ display: "none" }}>
|
|
177
|
+
<Image.PreviewGroup
|
|
178
|
+
preview={{
|
|
179
|
+
visible: previewVisible,
|
|
180
|
+
current,
|
|
181
|
+
onVisibleChange: (vis) => setPreviewVisible(vis),
|
|
182
|
+
onChange: (idx) => {
|
|
183
|
+
setCurrent(idx);
|
|
184
|
+
carouselRef.current?.goTo(idx);
|
|
185
|
+
},
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
{images.map((image, index) => {
|
|
118
189
|
const imageSrc = typeof image === "string" ? image : image.src;
|
|
119
190
|
const imageAlt =
|
|
120
191
|
typeof image === "string"
|
|
@@ -122,9 +193,10 @@ function ImageCarousel({
|
|
|
122
193
|
: image.alt;
|
|
123
194
|
|
|
124
195
|
return <Image key={imageSrc} src={imageSrc} alt={imageAlt} />;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
196
|
+
})}
|
|
197
|
+
</Image.PreviewGroup>
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
128
200
|
</>
|
|
129
201
|
);
|
|
130
202
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
|
|
3
|
+
export const StyledCarouselWrapper = styled.div`
|
|
4
|
+
position: relative;
|
|
5
|
+
|
|
6
|
+
.ant-carousel .slick-dots li button {
|
|
7
|
+
background: ${props => props.inactiveDotColor} !important;
|
|
8
|
+
opacity: 1 !important;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ant-carousel .slick-dots li.slick-active button {
|
|
12
|
+
background: ${props => props.activeDotColor} !important;
|
|
13
|
+
opacity: 1 !important;
|
|
14
|
+
}
|
|
15
|
+
`;
|
package/.env
DELETED
package/.vscode/settings.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cSpell.words": ["cukura"],
|
|
3
|
-
"files.autoSave": "afterDelay",
|
|
4
|
-
"editor.wordWrap": "on",
|
|
5
|
-
"editor.autoClosingBrackets": "always",
|
|
6
|
-
"editor.autoClosingComments": "always",
|
|
7
|
-
"editor.autoClosingQuotes": "always",
|
|
8
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
9
|
-
"editor.formatOnPaste": true,
|
|
10
|
-
"editor.formatOnSave": true,
|
|
11
|
-
"notebook.defaultFormatter": "esbenp.prettier-vscode",
|
|
12
|
-
"javascript.format.semicolons": "insert"
|
|
13
|
-
}
|