datastake-daf 0.6.210 → 0.6.212

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/.env ADDED
@@ -0,0 +1,8 @@
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=
@@ -0,0 +1,13 @@
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
+ }
@@ -21250,13 +21250,83 @@ const CarouselWidget = /*#__PURE__*/React.forwardRef((_ref, ref) => {
21250
21250
  });
21251
21251
  CarouselWidget.displayName = 'CarouselWidget';
21252
21252
 
21253
- const _excluded$d = ["title", "images", "height", "fallback"];
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
+ });
21302
+ }
21303
+
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", "customArrows"];
21254
21319
  function ImageCarousel(_ref) {
21255
21320
  let {
21256
21321
  title,
21257
21322
  images,
21258
21323
  height = 400,
21259
- 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",
21329
+ customArrows = false
21260
21330
  } = _ref,
21261
21331
  rest = _objectWithoutProperties(_ref, _excluded$d);
21262
21332
  const [previewVisible, setPreviewVisible] = React.useState(false);
@@ -21265,16 +21335,30 @@ function ImageCarousel(_ref) {
21265
21335
  const handleCarouselChange = index => {
21266
21336
  setCurrent(index);
21267
21337
  };
21338
+ const goToPrevious = () => {
21339
+ var _carouselRef$current;
21340
+ (_carouselRef$current = carouselRef.current) === null || _carouselRef$current === void 0 || _carouselRef$current.prev();
21341
+ };
21342
+ const goToNext = () => {
21343
+ var _carouselRef$current2;
21344
+ (_carouselRef$current2 = carouselRef.current) === null || _carouselRef$current2 === void 0 || _carouselRef$current2.next();
21345
+ };
21346
+
21347
+ // Check if images array is empty or invalid
21348
+ const hasImages = images && images.length > 0;
21268
21349
  return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
21269
- children: [/*#__PURE__*/jsxRuntime.jsx(CarouselWidget, _objectSpread2(_objectSpread2({
21270
- title: title
21271
- }, rest), {}, {
21272
- dots: false,
21273
- children: /*#__PURE__*/jsxRuntime.jsx(antd.Carousel, {
21350
+ children: [/*#__PURE__*/jsxRuntime.jsxs(StyledCarouselWrapper, {
21351
+ activeDotColor: activeDotColor,
21352
+ inactiveDotColor: inactiveDotColor,
21353
+ children: [/*#__PURE__*/jsxRuntime.jsx(CarouselWidget, _objectSpread2(_objectSpread2({
21354
+ title: title
21355
+ }, rest), {}, {
21356
+ arrows: false,
21274
21357
  ref: carouselRef,
21275
21358
  afterChange: handleCarouselChange,
21276
- infinite: true,
21277
- children: images.map((image, index) => {
21359
+ infinite: hasImages,
21360
+ dots: hasImages,
21361
+ children: hasImages ? images.map((image, index) => {
21278
21362
  const imageSrc = typeof image === "string" ? image : image.src;
21279
21363
  const imageAlt = typeof image === "string" ? "".concat(title, " - Image ").concat(index + 1) : image.alt;
21280
21364
  return /*#__PURE__*/jsxRuntime.jsx("div", {
@@ -21294,9 +21378,37 @@ function ImageCarousel(_ref) {
21294
21378
  }
21295
21379
  })
21296
21380
  }, imageSrc);
21381
+ }) :
21382
+ /*#__PURE__*/
21383
+ // No images - show placeholder
21384
+ jsxRuntime.jsx("div", {
21385
+ children: /*#__PURE__*/jsxRuntime.jsx(antd.Image, {
21386
+ src: "/assets/images/no-image.svg",
21387
+ alt: "No images available",
21388
+ height: height,
21389
+ width: "100%",
21390
+ fallback: fallback,
21391
+ preview: false,
21392
+ style: {
21393
+ objectFit: "contain"
21394
+ }
21395
+ })
21297
21396
  })
21298
- })
21299
- })), /*#__PURE__*/jsxRuntime.jsx("div", {
21397
+ })), hasImages && customArrows && images.length > 1 && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
21398
+ children: [/*#__PURE__*/jsxRuntime.jsx(CustomArrowButton, {
21399
+ icon: /*#__PURE__*/jsxRuntime.jsx(Icons.LeftOutlined, {}),
21400
+ onClick: goToPrevious,
21401
+ isLeft: true,
21402
+ iconColor: arrowIconColor,
21403
+ hoverIconColor: arrowHoverIconColor
21404
+ }), /*#__PURE__*/jsxRuntime.jsx(CustomArrowButton, {
21405
+ icon: /*#__PURE__*/jsxRuntime.jsx(Icons.RightOutlined, {}),
21406
+ onClick: goToNext,
21407
+ iconColor: arrowIconColor,
21408
+ hoverIconColor: arrowHoverIconColor
21409
+ })]
21410
+ })]
21411
+ }), hasImages && /*#__PURE__*/jsxRuntime.jsx("div", {
21300
21412
  style: {
21301
21413
  display: "none"
21302
21414
  },
@@ -21306,9 +21418,9 @@ function ImageCarousel(_ref) {
21306
21418
  current,
21307
21419
  onVisibleChange: vis => setPreviewVisible(vis),
21308
21420
  onChange: idx => {
21309
- var _carouselRef$current;
21421
+ var _carouselRef$current3;
21310
21422
  setCurrent(idx);
21311
- (_carouselRef$current = carouselRef.current) === null || _carouselRef$current === void 0 || _carouselRef$current.goTo(idx);
21423
+ (_carouselRef$current3 = carouselRef.current) === null || _carouselRef$current3 === void 0 || _carouselRef$current3.goTo(idx);
21312
21424
  }
21313
21425
  },
21314
21426
  children: images.map((image, index) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.210",
3
+ "version": "0.6.212",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -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,11 @@ 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",
75
+ customArrows = false,
61
76
  ...rest
62
77
  }) {
63
78
  const [previewVisible, setPreviewVisible] = useState(false);
@@ -68,11 +83,33 @@ function ImageCarousel({
68
83
  setCurrent(index);
69
84
  };
70
85
 
86
+ const goToPrevious = () => {
87
+ carouselRef.current?.prev();
88
+ };
89
+
90
+ const goToNext = () => {
91
+ carouselRef.current?.next();
92
+ };
93
+
94
+ // Check if images array is empty or invalid
95
+ const hasImages = images && images.length > 0;
96
+
71
97
  return (
72
98
  <>
73
- <CarouselWidget title={title} {...rest} dots={false}>
74
- <Carousel ref={carouselRef} afterChange={handleCarouselChange} infinite>
75
- {images.map((image, index) => {
99
+ <StyledCarouselWrapper
100
+ activeDotColor={activeDotColor}
101
+ inactiveDotColor={inactiveDotColor}
102
+ >
103
+ <CarouselWidget
104
+ title={title}
105
+ {...rest}
106
+ arrows={false}
107
+ ref={carouselRef}
108
+ afterChange={handleCarouselChange}
109
+ infinite={hasImages}
110
+ dots={hasImages}
111
+ >
112
+ {hasImages ? images.map((image, index) => {
76
113
  const imageSrc = typeof image === "string" ? image : image.src;
77
114
  const imageAlt =
78
115
  typeof image === "string"
@@ -98,23 +135,58 @@ function ImageCarousel({
98
135
  />
99
136
  </div>
100
137
  );
101
- })}
102
- </Carousel>
103
- </CarouselWidget>
138
+ }) : (
139
+ // No images - show placeholder
140
+ <div>
141
+ <Image
142
+ src="/assets/images/no-image.svg"
143
+ alt="No images available"
144
+ height={height}
145
+ width="100%"
146
+ fallback={fallback}
147
+ preview={false}
148
+ style={{
149
+ objectFit: "contain",
150
+ }}
151
+ />
152
+ </div>
153
+ )}
154
+ </CarouselWidget>
155
+
156
+ {(hasImages && customArrows && images.length > 1) && (
157
+ <>
158
+ <CustomArrowButton
159
+ icon={<LeftOutlined />}
160
+ onClick={goToPrevious}
161
+ isLeft={true}
162
+ iconColor={arrowIconColor}
163
+ hoverIconColor={arrowHoverIconColor}
164
+ />
165
+ <CustomArrowButton
166
+ icon={<RightOutlined />}
167
+ onClick={goToNext}
168
+ iconColor={arrowIconColor}
169
+ hoverIconColor={arrowHoverIconColor}
170
+ />
171
+ </>
172
+ )}
173
+ </StyledCarouselWrapper>
104
174
 
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) => {
175
+ {/* Preview functionality - only show if there are images */}
176
+ {hasImages && (
177
+ <div style={{ display: "none" }}>
178
+ <Image.PreviewGroup
179
+ preview={{
180
+ visible: previewVisible,
181
+ current,
182
+ onVisibleChange: (vis) => setPreviewVisible(vis),
183
+ onChange: (idx) => {
184
+ setCurrent(idx);
185
+ carouselRef.current?.goTo(idx);
186
+ },
187
+ }}
188
+ >
189
+ {images.map((image, index) => {
118
190
  const imageSrc = typeof image === "string" ? image : image.src;
119
191
  const imageAlt =
120
192
  typeof image === "string"
@@ -122,11 +194,12 @@ function ImageCarousel({
122
194
  : image.alt;
123
195
 
124
196
  return <Image key={imageSrc} src={imageSrc} alt={imageAlt} />;
125
- })}
126
- </Image.PreviewGroup>
127
- </div>
197
+ })}
198
+ </Image.PreviewGroup>
199
+ </div>
200
+ )}
128
201
  </>
129
202
  );
130
203
  }
131
204
 
132
- export default ImageCarousel;
205
+ export default ImageCarousel;
@@ -2,76 +2,96 @@ import ProjectWidget from "./index.jsx";
2
2
  import ThemeLayout from "../../../ThemeLayout/index.jsx";
3
3
  import Widget from "../index.jsx";
4
4
  import DashboardLayout from "../../DashboardLayout/index.jsx";
5
+ import DafHEader from "../../../Header/index.jsx";
5
6
  export default {
6
- title: "Dashboard/Widgets/ProjectWidget",
7
- component: ProjectWidget,
8
- tags: ["autodocs"],
9
- decorators: [
10
- (Story) => (
11
- <div style={{ margin: "3em" }}>
12
- <ThemeLayout>
13
- <Story />
14
- </ThemeLayout>
15
- </div>
16
- ),
17
- ],
7
+ title: "Dashboard/Widgets/ProjectWidget",
8
+ component: ProjectWidget,
9
+ tags: ["autodocs"],
10
+ decorators: [
11
+ (Story) => (
12
+ <div style={{ margin: "3em" }}>
13
+ <ThemeLayout>
14
+ <Story />
15
+ </ThemeLayout>
16
+ </div>
17
+ ),
18
+ ],
18
19
  };
19
20
 
20
21
  export const Primary = {
21
- name: "ProjectWidget",
22
- args: {
23
- onLinkClick: () => {},
24
- linkIcon: "Link",
25
- sdgList: [
26
- "noPoverty",
27
- "zeroHunger",
28
- "goodHealthWellbeing",
29
- "qualityEducation",
30
- "genderEquality",
31
- "cleanWaterSanitation",
32
- "affordableCleanEnergy",
33
- "decentWorkEconomicGrowth",
34
- "industryInnovationInfrastructure",
35
- "reducedInequalities",
36
- "sustainableCitiesCommunities",
37
- "responsibleConsumptionProduction",
38
- "climateAction",
39
- "lifeBelowWater",
40
- "lifeOnLand",
41
- "peaceJusticeStrongInstitutions",
42
- "partnershipsForGoals",
43
- ],
44
- items: [
45
- { label: "Item 1", render: () => <span>Value 1</span> },
46
- { label: "Item 2", render: () => <span>Value 2</span> },
47
- { label: "Item 3", render: () => <span>Value 3</span> },
48
- ],
49
- },
50
- render: (args) => {
51
- return (
52
- <DashboardLayout>
53
- <Widget title="Project Catalogue" className="with-border-header">
54
- <section>
55
- <ProjectWidget {...args} title="ALL SDGS" />
22
+ name: "ProjectWidget",
23
+ args: {
24
+ onLinkClick: () => {},
25
+ linkIcon: "Link",
26
+ sdgList: [
27
+ "noPoverty",
28
+ "zeroHunger",
29
+ "goodHealthWellbeing",
30
+ "qualityEducation",
31
+ "genderEquality",
32
+ "cleanWaterSanitation",
33
+ "affordableCleanEnergy",
34
+ "decentWorkEconomicGrowth",
35
+ "industryInnovationInfrastructure",
36
+ "reducedInequalities",
37
+ "sustainableCitiesCommunities",
38
+ "responsibleConsumptionProduction",
39
+ "climateAction",
40
+ "lifeBelowWater",
41
+ "lifeOnLand",
42
+ "peaceJusticeStrongInstitutions",
43
+ "partnershipsForGoals",
44
+ ],
45
+ items: [
46
+ { label: "Item 1", render: () => <span>Value 1</span> },
47
+ { label: "Item 2", render: () => <span>Value 2</span> },
48
+ { label: "Item 3", render: () => <span>Value 3</span> },
49
+ ],
50
+ },
51
+ render: (args) => {
52
+ return (
53
+ <DashboardLayout
54
+ header={
55
+ <DafHEader title="Dashboard" supportText="The Support text for the header" />
56
+ }
57
+ >
58
+ <section
59
+ style={{
60
+ overflowX: "auto",
61
+ maxWidth: "100%",
62
+ }}
63
+ >
64
+ <Widget title="Project Catalogue" className="with-border-header">
65
+ <section className="nowrap horizontal-scroll" style={{}}>
66
+ <ProjectWidget {...args} title="ALL SDGS" />
56
67
 
57
- <ProjectWidget
58
- {...args}
59
- title="SOME SDGS"
60
- sdgList={["noPoverty", "lifeBelowWater"]}
61
- image="data:image/jpeg;base64,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"
62
- />
68
+ <ProjectWidget
69
+ {...args}
70
+ title="SOME SDGS"
71
+ sdgList={["noPoverty", "lifeBelowWater"]}
72
+ image="data:image/jpeg;base64,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"
73
+ />
63
74
 
64
- <ProjectWidget
65
- {...args}
66
- sdgList={[]}
67
- title="No SDGS"
68
- image={
69
- "https://static0.srcdn.com/wordpress/wp-content/uploads/2023/03/the-main-characters-of-the-hangover-in-an-elevator.jpg"
70
- }
71
- />
72
- </section>
73
- </Widget>
74
- </DashboardLayout>
75
- );
76
- },
75
+ <ProjectWidget
76
+ {...args}
77
+ sdgList={[]}
78
+ title="No SDGS"
79
+ image={
80
+ "https://static0.srcdn.com/wordpress/wp-content/uploads/2023/03/the-main-characters-of-the-hangover-in-an-elevator.jpg"
81
+ }
82
+ />
83
+
84
+ <ProjectWidget {...args} />
85
+ <ProjectWidget {...args} />
86
+ <ProjectWidget {...args} />
87
+ </section>
88
+ </Widget>
89
+ </section>
90
+
91
+ <section>
92
+ <Widget title="FUCK"></Widget>
93
+ </section>
94
+ </DashboardLayout>
95
+ );
96
+ },
77
97
  };
@@ -191,7 +191,7 @@
191
191
  .content {
192
192
  width: 100%;
193
193
  flex: 1;
194
- overflow: auto;
194
+ // overflow: auto; Dont make the page overflow
195
195
  display: flex;
196
196
 
197
197
  .view-content {
@@ -301,6 +301,7 @@
301
301
  .daf-analysis-layout {
302
302
  display: flex;
303
303
  flex-direction: column;
304
+ overflow-x: hidden; // Dont make the page overflow
304
305
 
305
306
  .go-select-cont {
306
307
  padding: var(--size-lg);
@@ -355,6 +356,7 @@
355
356
  display: flex;
356
357
  flex-wrap: wrap;
357
358
  gap: var(--size-lg);
359
+
358
360
  //FUTURE ILVI - remove this
359
361
  @media (max-width: 850px) {
360
362
  flex-direction: column;
@@ -364,6 +366,7 @@
364
366
  .widget {
365
367
  flex: 1;
366
368
 
369
+
367
370
  &.dds-widget {
368
371
  min-width: 400px;
369
372
  }
@@ -374,6 +377,31 @@
374
377
  flex-wrap: nowrap;
375
378
  }
376
379
 
380
+ .horizontal-scroll {
381
+ overflow-x: auto;
382
+ overflow-y: hidden;
383
+ scrollbar-width: thin;
384
+ scrollbar-color: #c1c1c1 #f1f1f1;
385
+
386
+ &::-webkit-scrollbar {
387
+ height: 8px;
388
+ }
389
+
390
+ &::-webkit-scrollbar-track {
391
+ background: #f1f1f1;
392
+ border-radius: 4px;
393
+ }
394
+
395
+ &::-webkit-scrollbar-thumb {
396
+ background: #c1c1c1;
397
+ border-radius: 4px;
398
+
399
+ &:hover {
400
+ background: #a8a8a8;
401
+ }
402
+ }
403
+ }
404
+
377
405
  &.own-data-collapsed {
378
406
  .overflow-section {
379
407
  max-width: calc(100vw - 48px - 250px);
@@ -498,4 +526,4 @@
498
526
  }
499
527
  }
500
528
  }
501
- }
529
+ }
@@ -1,185 +0,0 @@
1
- import React, { useState, useRef } from "react";
2
- import styled from "styled-components";
3
- import CarouselWidget from "../CarouselWidget/index.jsx";
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";
8
-
9
- /**
10
- * @typedef {Object} ImageObject
11
- * @property {string} src - The source URL of the image
12
- * @property {string} alt - Alternative text for the image
13
- */
14
-
15
- /**
16
- * ImageCarousel Component - A responsive image carousel with preview functionality and keyboard navigation
17
- *
18
- * @component
19
- * @example
20
- * // Basic usage with string URLs
21
- * <ImageCarousel
22
- * title="My Gallery"
23
- * images={["image1.jpg", "image2.jpg"]}
24
- * />
25
- *
26
- * @example
27
- * // Advanced usage with image objects and custom height
28
- * <ImageCarousel
29
- * title="Nature Gallery"
30
- * images={[
31
- * { src: "landscape.jpg", alt: "Beautiful mountain landscape" },
32
- * { src: "forest.jpg", alt: "Dense forest path" }
33
- * ]}
34
- * height={300}
35
- * fallback="/custom-fallback.svg"
36
- * />
37
- *
38
- * @param {Object} props - Component props
39
- * @param {string} props.title - The title displayed in the widget header
40
- * @param {(string|ImageObject)[]} props.images - Array of image URLs (strings) or image objects with src and alt properties
41
- * @param {number} [props.height=400] - Height of the carousel images in pixels
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
47
- * @param {boolean} [props.customArrows=false] - Whether to show custom navigation arrows
48
- * @param {Object} [props.rest] - Additional props passed to the underlying CarouselWidget component
49
- *
50
- * @features
51
- * - ✅ Responsive image display with lazy loading
52
- * - ✅ Click-to-preview with full-screen image viewer
53
- * - ✅ Keyboard navigation in preview mode (← → arrow keys)
54
- * - ✅ Automatic fallback for broken images
55
- * - ✅ Support for both string URLs and image objects with alt text
56
- * - ✅ Synchronized carousel and preview navigation
57
- * - ✅ Infinite scrolling in both carousel and preview modes
58
- * - ✅ Placeholder image for empty image arrays (/assets/images/no-image.svg)
59
- * - ✅ Customizable dot colors via props
60
- *
61
- * @accessibility
62
- * - Proper alt text support for screen readers
63
- * - Keyboard navigation support
64
- * - Focus management in preview mode
65
- *
66
- */
67
- function ImageCarousel({
68
- title,
69
- images,
70
- height = 400,
71
- fallback = "/assets/images/empty-box.svg",
72
- activeDotColor = "#1890ff",
73
- inactiveDotColor = "rgba(255, 255, 255, 0.3)",
74
- arrowIconColor = "#666",
75
- arrowHoverIconColor = "#1890ff",
76
- customArrows = false,
77
- ...rest
78
- }) {
79
- const [previewVisible, setPreviewVisible] = useState(false);
80
- const [current, setCurrent] = useState(0);
81
- const carouselRef = useRef(null);
82
-
83
- const handleCarouselChange = (index) => {
84
- setCurrent(index);
85
- };
86
-
87
- const goToPrevious = () => {
88
- carouselRef.current?.prev();
89
- };
90
-
91
- const goToNext = () => {
92
- carouselRef.current?.next();
93
- };
94
-
95
-
96
- return (
97
- <>
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
109
- >
110
- {images.map((image, index) => {
111
- const imageSrc = typeof image === "string" ? image : image.src;
112
- const imageAlt =
113
- typeof image === "string"
114
- ? `${title} - Image ${index + 1}`
115
- : image.alt;
116
-
117
- return (
118
- <div key={imageSrc}>
119
- <Image
120
- src={imageSrc}
121
- alt={imageAlt}
122
- height={height}
123
- width="100%"
124
- fallback={fallback}
125
- loading="lazy"
126
- preview={{
127
- visible: false,
128
- }}
129
- onClick={() => {
130
- setCurrent(index);
131
- setPreviewVisible(true);
132
- }}
133
- />
134
- </div>
135
- );
136
- })}
137
- </CarouselWidget>
138
-
139
- {customArrows && images && images.length > 1 && (
140
- <>
141
- <CustomArrowButton
142
- icon={<LeftOutlined />}
143
- onClick={goToPrevious}
144
- isLeft={true}
145
- iconColor={arrowIconColor}
146
- hoverIconColor={arrowHoverIconColor}
147
- />
148
- <CustomArrowButton
149
- icon={<RightOutlined />}
150
- onClick={goToNext}
151
- iconColor={arrowIconColor}
152
- hoverIconColor={arrowHoverIconColor}
153
- />
154
- </>
155
- )}
156
- {/* </StyledCarouselWrapper> */}
157
-
158
- <div style={{ display: "none" }}>
159
- <Image.PreviewGroup
160
- preview={{
161
- visible: previewVisible,
162
- current,
163
- onVisibleChange: (vis) => setPreviewVisible(vis),
164
- onChange: (idx) => {
165
- setCurrent(idx);
166
- carouselRef.current?.goTo(idx);
167
- },
168
- }}
169
- >
170
- {images.map((image, index) => {
171
- const imageSrc = typeof image === "string" ? image : image.src;
172
- const imageAlt =
173
- typeof image === "string"
174
- ? `${title} - Image ${index + 1}`
175
- : image.alt;
176
-
177
- return <Image key={imageSrc} src={imageSrc} alt={imageAlt} />;
178
- })}
179
- </Image.PreviewGroup>
180
- </div>
181
- </>
182
- );
183
- }
184
-
185
- export default ImageCarousel;