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 +8 -0
- package/.vscode/settings.json +13 -0
- package/dist/components/index.js +125 -13
- package/package.json +1 -1
- package/src/@daf/core/components/Dashboard/Widget/ImageCarousel/index.jsx +97 -24
- package/src/@daf/core/components/Dashboard/Widget/ProjectWidget/ProjectWidget.stories.jsx +86 -66
- package/src/styles/components/_analysisView.scss +30 -2
- package/src/@daf/core/components/Dashboard/Widget/ImageCarousel/2ndCarousel.js +0 -185
package/.env
ADDED
|
@@ -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
|
+
}
|
package/dist/components/index.js
CHANGED
|
@@ -21250,13 +21250,83 @@ const CarouselWidget = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
21250
21250
|
});
|
|
21251
21251
|
CarouselWidget.displayName = 'CarouselWidget';
|
|
21252
21252
|
|
|
21253
|
-
|
|
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.
|
|
21270
|
-
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
|
|
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:
|
|
21277
|
-
|
|
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
|
-
|
|
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$
|
|
21421
|
+
var _carouselRef$current3;
|
|
21310
21422
|
setCurrent(idx);
|
|
21311
|
-
(_carouselRef$
|
|
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,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
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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;
|