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.
@@ -21226,7 +21226,7 @@ const WidgetCard = _ref => {
21226
21226
  };
21227
21227
 
21228
21228
  const _excluded$e = ["title", "children"];
21229
- function CarouselWidget(_ref) {
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({}, rest), {}, {
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 _excluded$d = ["title", "images", "height", "fallback"];
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.jsx(CarouselWidget, _objectSpread2(_objectSpread2({
21267
- title: title
21268
- }, rest), {}, {
21269
- dots: false,
21270
- children: /*#__PURE__*/jsxRuntime.jsx(antd.Carousel, {
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: true,
21274
- children: images.map((image, index) => {
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
- })), /*#__PURE__*/jsxRuntime.jsx("div", {
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$current;
21420
+ var _carouselRef$current3;
21307
21421
  setCurrent(idx);
21308
- (_carouselRef$current = carouselRef.current) === null || _carouselRef$current === void 0 || _carouselRef$current.goTo(idx);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.206",
3
+ "version": "0.6.207",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -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
- function CarouselWidget({title, children, ...rest}) {
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
- <CarouselWidget title={title} {...rest} dots={false}>
74
- <Carousel ref={carouselRef} afterChange={handleCarouselChange} infinite>
75
- {images.map((image, index) => {
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
- </Carousel>
103
- </CarouselWidget>
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
- <div style={{ display: "none" }}>
106
- <Image.PreviewGroup
107
- preview={{
108
- visible: previewVisible,
109
- current,
110
- onVisibleChange: (vis) => setPreviewVisible(vis),
111
- onChange: (idx) => {
112
- setCurrent(idx);
113
- carouselRef.current?.goTo(idx);
114
- },
115
- }}
116
- >
117
- {images.map((image, index) => {
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
- </Image.PreviewGroup>
127
- </div>
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
@@ -1,8 +0,0 @@
1
- REACT_APP_API_KEY=
2
- REACT_APP_AUTH_DOMAIN=
3
- REACT_APP_PROJECT_ID=
4
- REACT_APP_STORAGE_BUCKED=
5
- REACT_APP_SENDER_ID=
6
- REACT_APP_APP_ID=
7
- REACT_APP_MEASUREMENT_ID=
8
- REACT_APP_VAPID_KEY=
@@ -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
- }