@eeacms/volto-bise-policy 1.3.11 → 1.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -1
- package/package.json +2 -1
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyCard.jsx +404 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyCard.test.jsx +366 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.jsx +192 -91
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.test.jsx +205 -58
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.jsx +329 -178
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.test.jsx +243 -123
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMapLazy.jsx +61 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMapLazy.test.jsx +89 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyResults.jsx +128 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyResults.test.jsx +160 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FacetPopover.jsx +180 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FacetPopover.test.jsx +125 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FilterPanel.jsx +171 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/FilterPanel.test.jsx +181 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/Pagination.jsx +81 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/filters.js +350 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/filters.test.js +399 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/hooks.js +28 -4
- package/src/components/manage/Blocks/CaseStudyExplorer/hooks.test.js +73 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/messages.js +138 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/styles.less +1157 -317
- package/src/components/manage/Blocks/CaseStudyExplorer/testUtils.js +44 -46
- package/src/components/manage/Blocks/CaseStudyExplorer/utils.js +68 -184
- package/src/components/manage/Blocks/CaseStudyExplorer/utils.test.js +91 -158
- package/src/index.js +39 -0
- package/src/openlayers.js +51 -0
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.jsx +0 -383
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.test.jsx +0 -90
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.jsx +0 -342
- package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.test.jsx +0 -196
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.jsx +0 -24
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.test.jsx +0 -49
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.jsx +0 -100
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.test.jsx +0 -189
- package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.jsx +0 -111
- package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.test.jsx +0 -159
- package/src/components/manage/Blocks/CaseStudyExplorer/styles.less_old +0 -201
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,43 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
-
### [1.3.
|
|
7
|
+
### [1.3.13](https://github.com/eea/volto-bise-policy/compare/1.3.12...1.3.13) - 5 November 2026
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix(CaseStudyExplorer): use addAppURL for case study map [laszlocseh - [`6c7787c`](https://github.com/eea/volto-bise-policy/commit/6c7787cbdbda2ebe86388eff8ff00a2f4fa680b2)]
|
|
12
|
+
|
|
13
|
+
### [1.3.12](https://github.com/eea/volto-bise-policy/compare/1.3.11...1.3.12) - 5 November 2026
|
|
14
|
+
|
|
15
|
+
#### :rocket: New Features
|
|
16
|
+
|
|
17
|
+
- feat(CaseStudyMap): add selection highlight and title popup [laszlocseh - [`52c3d89`](https://github.com/eea/volto-bise-policy/commit/52c3d894f3da4ecf741ddf0fd9333298cca23187)]
|
|
18
|
+
- feat(CaseStudyExplorer): jump to the page of a case study selected on the map [laszlocseh - [`9c2fd56`](https://github.com/eea/volto-bise-policy/commit/9c2fd5660348bcace5aa4150082a3184ec8c0363)]
|
|
19
|
+
- feat(CaseStudyExplorer): add dynamic ecosystem chip overflow popup [laszlocseh - [`feb69e2`](https://github.com/eea/volto-bise-policy/commit/feb69e24a5cd1330e6fc7f78aaa64d38ca94c47f)]
|
|
20
|
+
|
|
21
|
+
#### :bug: Bug Fixes
|
|
22
|
+
|
|
23
|
+
- fix(CaseStudyExplorer): replace useId with a custom hook [laszlocseh - [`45fbbd0`](https://github.com/eea/volto-bise-policy/commit/45fbbd08c9f7be705d86cc37eda505b4a5ca6667)]
|
|
24
|
+
- fix(CaseStudyExplorer): remove media placeholder and adjust map marker size [laszlocseh - [`59a037f`](https://github.com/eea/volto-bise-policy/commit/59a037f718efb17975acd2b7bd084411af17513c)]
|
|
25
|
+
- fix(CaseStudyExplorer): hide pagination when there are no results [laszlocseh - [`2f07403`](https://github.com/eea/volto-bise-policy/commit/2f074037c98a181f5a40f7dc7832f41104660191)]
|
|
26
|
+
- fix: es lint [laszlocseh - [`f226bb7`](https://github.com/eea/volto-bise-policy/commit/f226bb740cad4d83615cd8eca22c091fdeff112c)]
|
|
27
|
+
|
|
28
|
+
#### :nail_care: Enhancements
|
|
29
|
+
|
|
30
|
+
- change(CaseStudyExplorer): update filters, design [laszlocseh - [`470a068`](https://github.com/eea/volto-bise-policy/commit/470a068a352e4388d131eb58cfadcf42e94bde87)]
|
|
31
|
+
|
|
32
|
+
#### :house: Internal changes
|
|
33
|
+
|
|
34
|
+
- style: Automated code fix [eea-jenkins - [`30fc35a`](https://github.com/eea/volto-bise-policy/commit/30fc35a061321bc19679f98adb74030c16a85c41)]
|
|
35
|
+
- style(CaseStudyExplorer): remove duplicate attribute [laszlocseh - [`538b42e`](https://github.com/eea/volto-bise-policy/commit/538b42e3e1c01544a865f778c6cd60582a14a1c4)]
|
|
36
|
+
- style: Automated code fix [eea-jenkins - [`0e2b827`](https://github.com/eea/volto-bise-policy/commit/0e2b8274a79aa0e1acab7725b293a69a3a595a37)]
|
|
37
|
+
- style: Automated code fix [eea-jenkins - [`c2f8597`](https://github.com/eea/volto-bise-policy/commit/c2f8597a3d54d8be36a6e34c6e9a8cbbfae33064)]
|
|
38
|
+
|
|
39
|
+
#### :hammer_and_wrench: Others
|
|
40
|
+
|
|
41
|
+
- test(CaseStudyExplorer): fix hooks.test.js [laszlocseh - [`c6d1c03`](https://github.com/eea/volto-bise-policy/commit/c6d1c039593072c1eaef56cb9d642699d61b1b84)]
|
|
42
|
+
- test(CaseStudyExplorer): update test expectations for UI changes [laszlocseh - [`43e2f34`](https://github.com/eea/volto-bise-policy/commit/43e2f349f724a7d6957f01fe8863da1f57740660)]
|
|
43
|
+
### [1.3.11](https://github.com/eea/volto-bise-policy/compare/1.3.10...1.3.11) - 29 October 2026
|
|
8
44
|
|
|
9
45
|
#### :house: Internal changes
|
|
10
46
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-bise-policy",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.13",
|
|
4
4
|
"description": "@eeacms/volto-bise-policy: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@plone-collective/volto-authomatic": "2.0.1",
|
|
36
36
|
"dompurify": "^3.3.1",
|
|
37
37
|
"esri-loader": "3.7.0",
|
|
38
|
+
"ol": "6.6.x",
|
|
38
39
|
"plotly.js": "^2.28.0",
|
|
39
40
|
"querystring": "0.2.1",
|
|
40
41
|
"react-plotly.js": "2.6.0"
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
import cx from 'classnames';
|
|
4
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
5
|
+
|
|
6
|
+
import messages from './messages';
|
|
7
|
+
import {
|
|
8
|
+
asValues,
|
|
9
|
+
// getArticleInfo,
|
|
10
|
+
getFacetValues,
|
|
11
|
+
valueLabel,
|
|
12
|
+
} from './filters';
|
|
13
|
+
import { getStatusColor, visibleChipCount } from './utils';
|
|
14
|
+
|
|
15
|
+
/** Rows of chips a card shows before collapsing the rest into "+N". */
|
|
16
|
+
const ECOSYSTEM_ROWS = 2;
|
|
17
|
+
const CHIP_GAP_FALLBACK = 8;
|
|
18
|
+
let nextPanelId = 0;
|
|
19
|
+
|
|
20
|
+
// Volto may run on React versions that do not yet provide useId. Keep IDs
|
|
21
|
+
// stable for the lifetime of a component instance without relying on that API.
|
|
22
|
+
function usePanelId() {
|
|
23
|
+
const id = React.useRef(null);
|
|
24
|
+
if (id.current === null) id.current = `cse-panel-${++nextPanelId}`;
|
|
25
|
+
return id.current;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Measure before the browser paints where possible, without warning on the
|
|
29
|
+
// server-rendered first pass.
|
|
30
|
+
const useIsomorphicLayoutEffect =
|
|
31
|
+
typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
|
32
|
+
|
|
33
|
+
// const STATUS_LABELS = {
|
|
34
|
+
// planned: messages.statusPlanned,
|
|
35
|
+
// ongoing: messages.statusOngoing,
|
|
36
|
+
// completed: messages.statusCompleted,
|
|
37
|
+
// };
|
|
38
|
+
|
|
39
|
+
function StatusBadge({ status }) {
|
|
40
|
+
// const intl = useIntl();
|
|
41
|
+
// const label = STATUS_LABELS[status];
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<span className="cse-card-status">
|
|
45
|
+
<span
|
|
46
|
+
className="cse-card-status-dot"
|
|
47
|
+
style={{ backgroundColor: getStatusColor(status) }}
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
/>
|
|
50
|
+
{/* <span className="cse-card-status-label">
|
|
51
|
+
{label ? intl.formatMessage(label) : status}
|
|
52
|
+
</span> */}
|
|
53
|
+
</span>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Ecosystem chips, wrapped over at most two rows. How many fit depends on the
|
|
59
|
+
* card width, so the full list is mirrored into a hidden, absolutely
|
|
60
|
+
* positioned list that is measured with real layout; the chips that overflow
|
|
61
|
+
* two rows collapse into a "+N" trigger that opens a popup listing exactly the
|
|
62
|
+
* hidden values.
|
|
63
|
+
*
|
|
64
|
+
* Without a layout engine (jsdom, SSR) nothing can be measured, so every chip
|
|
65
|
+
* is rendered and no trigger appears.
|
|
66
|
+
*/
|
|
67
|
+
function EcosystemChips({ ecosystems }) {
|
|
68
|
+
const intl = useIntl();
|
|
69
|
+
const [visibleCount, setVisibleCount] = React.useState(ecosystems.length);
|
|
70
|
+
const [open, setOpen] = React.useState(false);
|
|
71
|
+
const measureRef = React.useRef(null);
|
|
72
|
+
const triggerRef = React.useRef(null);
|
|
73
|
+
const panelRef = React.useRef(null);
|
|
74
|
+
const panelId = usePanelId();
|
|
75
|
+
|
|
76
|
+
const measure = React.useCallback(() => {
|
|
77
|
+
const list = measureRef.current;
|
|
78
|
+
if (!list) return;
|
|
79
|
+
|
|
80
|
+
const chips = Array.from(list.querySelectorAll('.cse-card-chip'));
|
|
81
|
+
const width = list.clientWidth;
|
|
82
|
+
|
|
83
|
+
// `visibility: hidden` boxes still measure, but jsdom reports 0 for all of
|
|
84
|
+
// them: keep every chip visible instead of guessing.
|
|
85
|
+
if (!width) {
|
|
86
|
+
setVisibleCount(ecosystems.length);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const gap =
|
|
91
|
+
parseFloat(window.getComputedStyle(list).columnGap) || CHIP_GAP_FALLBACK;
|
|
92
|
+
const tops = chips.map((chip) => chip.offsetTop);
|
|
93
|
+
const rows = [...new Set(tops)].sort((a, b) => a - b);
|
|
94
|
+
const moreChip = list.querySelector('.cse-card-chip-more-measure');
|
|
95
|
+
|
|
96
|
+
setVisibleCount(
|
|
97
|
+
visibleChipCount(
|
|
98
|
+
chips.map((chip, index) => ({
|
|
99
|
+
row: rows.indexOf(tops[index]),
|
|
100
|
+
right: chip.offsetLeft + chip.offsetWidth,
|
|
101
|
+
})),
|
|
102
|
+
{
|
|
103
|
+
rows: ECOSYSTEM_ROWS,
|
|
104
|
+
gap,
|
|
105
|
+
moreWidth: moreChip?.offsetWidth || 0,
|
|
106
|
+
width,
|
|
107
|
+
},
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
}, [ecosystems.length]);
|
|
111
|
+
|
|
112
|
+
useIsomorphicLayoutEffect(() => {
|
|
113
|
+
measure();
|
|
114
|
+
|
|
115
|
+
if (typeof ResizeObserver === 'undefined') return undefined;
|
|
116
|
+
const observer = new ResizeObserver(measure);
|
|
117
|
+
if (measureRef.current) observer.observe(measureRef.current);
|
|
118
|
+
|
|
119
|
+
return () => observer.disconnect();
|
|
120
|
+
}, [measure]);
|
|
121
|
+
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
if (!open) return undefined;
|
|
124
|
+
|
|
125
|
+
const onDocumentClick = (event) => {
|
|
126
|
+
if (!panelRef.current?.parentElement?.contains(event.target)) {
|
|
127
|
+
setOpen(false);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const onKeyDown = (event) => {
|
|
131
|
+
if (event.key !== 'Escape') return;
|
|
132
|
+
setOpen(false);
|
|
133
|
+
triggerRef.current?.focus();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
document.addEventListener('click', onDocumentClick);
|
|
137
|
+
document.addEventListener('keydown', onKeyDown);
|
|
138
|
+
panelRef.current?.focus();
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
document.removeEventListener('click', onDocumentClick);
|
|
142
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
143
|
+
};
|
|
144
|
+
}, [open]);
|
|
145
|
+
|
|
146
|
+
if (!ecosystems.length) return null;
|
|
147
|
+
|
|
148
|
+
const shown = Math.min(visibleCount, ecosystems.length);
|
|
149
|
+
const hidden = ecosystems.length - shown;
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className="cse-card-ecosystems-wrap">
|
|
153
|
+
<ul className="cse-card-ecosystems">
|
|
154
|
+
{ecosystems.slice(0, shown).map((ecosystem, index) => (
|
|
155
|
+
<li className="cse-card-chip" key={`${ecosystem}-${index}`}>
|
|
156
|
+
{ecosystem}
|
|
157
|
+
</li>
|
|
158
|
+
))}
|
|
159
|
+
{hidden > 0 ? (
|
|
160
|
+
<li className="cse-card-chip cse-card-chip-more">
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
ref={triggerRef}
|
|
164
|
+
className="cse-card-chip-more-trigger"
|
|
165
|
+
aria-expanded={open}
|
|
166
|
+
aria-controls={panelId}
|
|
167
|
+
aria-haspopup="dialog"
|
|
168
|
+
onClick={() => setOpen((wasOpen) => !wasOpen)}
|
|
169
|
+
>
|
|
170
|
+
{intl.formatMessage(messages.moreEcosystems, { count: hidden })}
|
|
171
|
+
<span className="cse-visually-hidden">
|
|
172
|
+
{' '}
|
|
173
|
+
{intl.formatMessage(messages.hiddenEcosystems)}
|
|
174
|
+
</span>
|
|
175
|
+
</button>
|
|
176
|
+
</li>
|
|
177
|
+
) : null}
|
|
178
|
+
</ul>
|
|
179
|
+
|
|
180
|
+
{hidden > 0 && open ? (
|
|
181
|
+
<div
|
|
182
|
+
ref={panelRef}
|
|
183
|
+
id={panelId}
|
|
184
|
+
className="cse-card-ecosystems-panel"
|
|
185
|
+
role="dialog"
|
|
186
|
+
aria-label={intl.formatMessage(messages.hiddenEcosystems)}
|
|
187
|
+
tabIndex={-1}
|
|
188
|
+
>
|
|
189
|
+
<ul className="cse-card-ecosystems cse-card-ecosystems-panel-list">
|
|
190
|
+
{ecosystems.slice(shown).map((ecosystem, index) => (
|
|
191
|
+
<li className="cse-card-chip" key={`${ecosystem}-${index}`}>
|
|
192
|
+
{ecosystem}
|
|
193
|
+
</li>
|
|
194
|
+
))}
|
|
195
|
+
</ul>
|
|
196
|
+
</div>
|
|
197
|
+
) : null}
|
|
198
|
+
|
|
199
|
+
<ul
|
|
200
|
+
ref={measureRef}
|
|
201
|
+
className="cse-card-ecosystems cse-card-ecosystems-measure"
|
|
202
|
+
aria-hidden="true"
|
|
203
|
+
>
|
|
204
|
+
{ecosystems.map((ecosystem, index) => (
|
|
205
|
+
<li className="cse-card-chip" key={`${ecosystem}-${index}`}>
|
|
206
|
+
{ecosystem}
|
|
207
|
+
</li>
|
|
208
|
+
))}
|
|
209
|
+
<li className="cse-card-chip cse-card-chip-more-measure">
|
|
210
|
+
{intl.formatMessage(messages.moreEcosystems, {
|
|
211
|
+
count: ecosystems.length,
|
|
212
|
+
})}
|
|
213
|
+
</li>
|
|
214
|
+
</ul>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Related measures, collapsed behind a full-width disclosure bar. The bar
|
|
221
|
+
* owns the count; the panel stays mounted so its height can animate, and is
|
|
222
|
+
* `inert` while collapsed so its links stay out of the tab order. Nothing is
|
|
223
|
+
* rendered at all for a case study without measures.
|
|
224
|
+
*/
|
|
225
|
+
function RelatedMeasures({ measures }) {
|
|
226
|
+
const intl = useIntl();
|
|
227
|
+
const panelId = usePanelId();
|
|
228
|
+
const [open, setOpen] = React.useState(false);
|
|
229
|
+
|
|
230
|
+
if (!measures.length) return null;
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className="cse-card-measures">
|
|
234
|
+
<button
|
|
235
|
+
type="button"
|
|
236
|
+
className="cse-card-measures-toggle"
|
|
237
|
+
aria-expanded={open}
|
|
238
|
+
aria-controls={panelId}
|
|
239
|
+
onClick={() => setOpen((value) => !value)}
|
|
240
|
+
>
|
|
241
|
+
<span className="cse-card-measures-count">
|
|
242
|
+
{intl.formatNumber(measures.length)}
|
|
243
|
+
</span>
|
|
244
|
+
<span className="cse-card-measures-title">
|
|
245
|
+
{intl.formatMessage(messages.relatedMeasures)}
|
|
246
|
+
</span>
|
|
247
|
+
<span className="cse-card-measures-chevron">
|
|
248
|
+
<i className="icon ri-arrow-down-s-line" aria-hidden="true" />
|
|
249
|
+
</span>
|
|
250
|
+
</button>
|
|
251
|
+
<div
|
|
252
|
+
id={panelId}
|
|
253
|
+
className="cse-card-measures-panel"
|
|
254
|
+
data-open={open ? 'true' : 'false'}
|
|
255
|
+
inert={open ? undefined : ''}
|
|
256
|
+
>
|
|
257
|
+
<div className="cse-card-measures-panel-inner">
|
|
258
|
+
<ul className="cse-card-measures-list">
|
|
259
|
+
{measures.map((measure) => (
|
|
260
|
+
<li key={measure.id || measure.path || measure.title}>
|
|
261
|
+
<a
|
|
262
|
+
className="cse-card-measure-link"
|
|
263
|
+
href={measure.path}
|
|
264
|
+
title={measure.title}
|
|
265
|
+
>
|
|
266
|
+
<i className="icon ri-file-list-3-line" aria-hidden="true" />
|
|
267
|
+
<span className="cse-card-measure-title">
|
|
268
|
+
{measure.title}
|
|
269
|
+
</span>
|
|
270
|
+
<i
|
|
271
|
+
className="icon ri-arrow-right-s-line"
|
|
272
|
+
aria-hidden="true"
|
|
273
|
+
/>
|
|
274
|
+
</a>
|
|
275
|
+
</li>
|
|
276
|
+
))}
|
|
277
|
+
</ul>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* One result, laid out as image | main content, with a full-width related
|
|
286
|
+
* measures disclosure bar underneath. The title, the image and the footer's
|
|
287
|
+
* corner icon are all "open in a new tab" affordances; selecting a card (from
|
|
288
|
+
* a map pin) only highlights it, while "Show on map" is the reverse link to
|
|
289
|
+
* the map. Once the card is the selected one, that same button reads "Reset
|
|
290
|
+
* map" and clears the selection again.
|
|
291
|
+
*/
|
|
292
|
+
function CaseStudyCard({ item, selected = false, onShowOnMap, onResetMap }) {
|
|
293
|
+
const intl = useIntl();
|
|
294
|
+
const properties = React.useMemo(() => item?.properties || {}, [item]);
|
|
295
|
+
// const { directives } = React.useMemo(
|
|
296
|
+
// () => getArticleInfo(properties),
|
|
297
|
+
// [properties],
|
|
298
|
+
// );
|
|
299
|
+
const ecosystems = React.useMemo(
|
|
300
|
+
() => getFacetValues(properties, 'ecosystem').map(valueLabel),
|
|
301
|
+
[properties],
|
|
302
|
+
);
|
|
303
|
+
const measures = React.useMemo(
|
|
304
|
+
() => asValues(properties.measures),
|
|
305
|
+
[properties],
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<article
|
|
310
|
+
className={cx('cse-card', {
|
|
311
|
+
selected,
|
|
312
|
+
'cse-card-no-media': !properties.image,
|
|
313
|
+
})}
|
|
314
|
+
id={`cse-card-${encodeURIComponent(properties.path || '')}`}
|
|
315
|
+
data-selected={selected ? 'true' : undefined}
|
|
316
|
+
>
|
|
317
|
+
{properties.image ? (
|
|
318
|
+
<a
|
|
319
|
+
className="cse-card-media"
|
|
320
|
+
href={properties.path}
|
|
321
|
+
tabIndex={-1}
|
|
322
|
+
aria-hidden="true"
|
|
323
|
+
>
|
|
324
|
+
<Image src={properties.image} alt="" loading="lazy" />
|
|
325
|
+
</a>
|
|
326
|
+
) : null}
|
|
327
|
+
|
|
328
|
+
<div className="cse-card-content">
|
|
329
|
+
<div className="cse-card-head">
|
|
330
|
+
<h3 className="cse-card-title">
|
|
331
|
+
<StatusBadge status={properties.current_status} />
|
|
332
|
+
<a
|
|
333
|
+
href={properties.path}
|
|
334
|
+
target="_blank"
|
|
335
|
+
rel="noopener noreferrer"
|
|
336
|
+
title={properties.title}
|
|
337
|
+
>
|
|
338
|
+
{properties.title}
|
|
339
|
+
<span className="cse-visually-hidden">
|
|
340
|
+
{' '}
|
|
341
|
+
{intl.formatMessage(messages.opensInNewTab)}
|
|
342
|
+
</span>
|
|
343
|
+
</a>
|
|
344
|
+
</h3>
|
|
345
|
+
<a
|
|
346
|
+
className="cse-card-open"
|
|
347
|
+
href={properties.path}
|
|
348
|
+
target="_blank"
|
|
349
|
+
rel="noopener noreferrer"
|
|
350
|
+
aria-label={intl.formatMessage(messages.openCaseStudy)}
|
|
351
|
+
title={intl.formatMessage(messages.openCaseStudy)}
|
|
352
|
+
>
|
|
353
|
+
<i className="icon ri-external-link-line" aria-hidden="true" />
|
|
354
|
+
<span className="cse-visually-hidden">
|
|
355
|
+
{intl.formatMessage(messages.opensInNewTab)}
|
|
356
|
+
</span>
|
|
357
|
+
</a>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div className="cse-card-meta">
|
|
361
|
+
<StatusBadge status={properties.current_status} />
|
|
362
|
+
{/* {directives.length ? (
|
|
363
|
+
<>
|
|
364
|
+
<span className="cse-card-separator" aria-hidden="true">
|
|
365
|
+
·
|
|
366
|
+
</span>
|
|
367
|
+
<span className="cse-card-directives">
|
|
368
|
+
{directives.map((directive) => (
|
|
369
|
+
<span className="cse-card-chip" key={directive}>
|
|
370
|
+
{directive}
|
|
371
|
+
</span>
|
|
372
|
+
))}
|
|
373
|
+
</span>
|
|
374
|
+
</>
|
|
375
|
+
) : null} */}
|
|
376
|
+
<button
|
|
377
|
+
type="button"
|
|
378
|
+
className="cse-card-show-on-map"
|
|
379
|
+
onClick={() =>
|
|
380
|
+
selected ? onResetMap?.(item) : onShowOnMap?.(item)
|
|
381
|
+
}
|
|
382
|
+
>
|
|
383
|
+
<i
|
|
384
|
+
className={cx(
|
|
385
|
+
'icon',
|
|
386
|
+
selected ? 'ri-map-2-line' : 'ri-map-pin-line',
|
|
387
|
+
)}
|
|
388
|
+
aria-hidden="true"
|
|
389
|
+
/>
|
|
390
|
+
{intl.formatMessage(
|
|
391
|
+
selected ? messages.resetMap : messages.showOnMap,
|
|
392
|
+
)}
|
|
393
|
+
</button>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<EcosystemChips ecosystems={ecosystems} />
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<RelatedMeasures measures={measures} />
|
|
400
|
+
</article>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export default React.memo(CaseStudyCard);
|