@eeacms/volto-cca-policy 1.0.25 → 1.0.27

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.
Files changed (26) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/package.json +1 -1
  3. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueCardItem.jsx +16 -24
  4. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueCardItem.test.jsx +21 -2
  5. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapView.jsx +9 -6
  6. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapView.test.jsx +50 -0
  7. package/src/components/Search/NavigatorCatalogue/utils.js +22 -0
  8. package/src/components/Search/NavigatorGuide/NavigatorGuideContentView.jsx +153 -135
  9. package/src/components/Search/NavigatorGuide/NavigatorGuideContentView.test.jsx +108 -2
  10. package/src/components/Search/NavigatorGuide/utils.js +15 -0
  11. package/src/components/Search/NavigatorGuide/utils.test.js +18 -1
  12. package/src/components/index.js +2 -0
  13. package/src/components/manage/Blocks/ChatbotCatalogue/astUtils.js +129 -3
  14. package/src/components/manage/Blocks/ChatbotCatalogue/astUtils.test.js +207 -0
  15. package/src/components/manage/Blocks/ChatbotCatalogue/docCards.js +8 -0
  16. package/src/components/manage/Blocks/ChatbotCatalogue/docCards.test.js +72 -0
  17. package/src/components/manage/Blocks/ChatbotCatalogue/nextSteps.js +4 -0
  18. package/src/components/manage/Blocks/ChatbotCatalogue/nextSteps.test.js +48 -0
  19. package/src/components/theme/CompareTools/CompareToolsPanel.jsx +1 -1
  20. package/src/components/theme/CompareTools/CompareToolsView.jsx +1 -1
  21. package/src/components/theme/Content/Sections/MetadataItemList.jsx +7 -24
  22. package/src/components/theme/TagOverflowPopup.jsx +28 -0
  23. package/src/components/theme/Views/RelatedTools.jsx +7 -25
  24. package/src/search/navigator_guide/facets.js +1 -0
  25. package/src/search/navigator_guide/guideSteps.js +1 -1
  26. package/theme/globals/navigator.less +11 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ 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.0.27](https://github.com/eea/volto-cca-policy/compare/1.0.26...1.0.27) - 21 September 2026
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix(navigator): country filtering on map view - refs #304610 [kreafox - [`2b8c52d`](https://github.com/eea/volto-cca-policy/commit/2b8c52dee15ed7a43a90eb2214db6557d7d3c964)]
12
+
13
+ ### [1.0.26](https://github.com/eea/volto-cca-policy/compare/1.0.25...1.0.26) - 21 September 2026
14
+
15
+ #### :rocket: New Features
16
+
17
+ - feat(navigator): implement TagOverflowPopup component and replace Popup in navigator [kreafox - [`42a66c8`](https://github.com/eea/volto-cca-policy/commit/42a66c826f2e19ebd0102fc05a2df5ccd1648f14)]
18
+
19
+ #### :bug: Bug Fixes
20
+
21
+ - fix(navigator): rename adaptation support cycle step and improve guide me layout [kreafox - [`2806e1b`](https://github.com/eea/volto-cca-policy/commit/2806e1b258dc3b1172c21a567f5d76328d855bb9)]
22
+ - fix(navigator): update climate impacts to climate hazards - refs #304609 [kreafox - [`cf2af49`](https://github.com/eea/volto-cca-policy/commit/cf2af49836f3b5a1b33621ae29650b14fb38c078)]
23
+
24
+ #### :nail_care: Enhancements
25
+
26
+ - refactor: update import paths for ToolThumbnail and TagOverflowPopup components [kreafox - [`c174abe`](https://github.com/eea/volto-cca-policy/commit/c174abeafcfb8040e43cb496a3f0b10747c057a8)]
27
+ - refactor(navigator): consolidate ToolThumbnail import paths and update related components [kreafox - [`5996b0b`](https://github.com/eea/volto-cca-policy/commit/5996b0b28814cb24c4167d9eb72cf6c632eb2a36)]
28
+
29
+ #### :house: Internal changes
30
+
31
+ - style: Automated code fix [eea-jenkins - [`851dcd2`](https://github.com/eea/volto-cca-policy/commit/851dcd27a6fd162e723b2e6e8fe542d8ab3bc8d1)]
32
+
7
33
  ### [1.0.25](https://github.com/eea/volto-cca-policy/compare/1.0.24...1.0.25) - 17 September 2026
8
34
 
9
35
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
- import { Checkbox, Icon, Popup } from 'semantic-ui-react';
2
+ import { Checkbox, Icon } from 'semantic-ui-react';
3
3
  import { defineMessages, useIntl } from 'react-intl';
4
4
  import ExternalLink from '@eeacms/search/components/Result/ExternalLink';
5
5
  import ResultContext from '@eeacms/search/components/Result/ResultContext';
6
- import ToolThumbnail from '../../theme/ToolThumbnail/ToolThumbnail';
6
+ import TagOverflowPopup from '@eeacms/volto-cca-policy/components/theme/TagOverflowPopup';
7
+ import ToolThumbnail from '@eeacms/volto-cca-policy/components/theme/ToolThumbnail/ToolThumbnail';
7
8
  import { getToolThumbnailUrl } from '../../theme/ToolThumbnail/utils';
8
9
  import {
9
10
  getCompareToolTitle,
@@ -78,27 +79,10 @@ const TagGroup = ({ typeLabel, values, type, maxItems }) => {
78
79
  </span>
79
80
  ))}
80
81
  {remaining > 0 && (
81
- <Popup
82
- className="catalogue-tag-popup"
83
- content={
84
- <div className="catalogue-tag-tooltip">
85
- <ul>
86
- {hidden.map((value) => (
87
- <li key={`${type}-hidden-${value}`}>{value}</li>
88
- ))}
89
- </ul>
90
- </div>
91
- }
92
- position="bottom left"
93
- trigger={
94
- <button
95
- type="button"
96
- className={`navigator-tag ${type} more`}
97
- aria-label={`${typeLabel}: ${hidden.join(', ')}`}
98
- >
99
- + {remaining}
100
- </button>
101
- }
82
+ <TagOverflowPopup
83
+ items={hidden}
84
+ className={`navigator-tag ${type} more`}
85
+ ariaLabel={`${typeLabel}: ${hidden.join(', ')}`}
102
86
  />
103
87
  )}
104
88
  </div>
@@ -107,6 +91,7 @@ const TagGroup = ({ typeLabel, values, type, maxItems }) => {
107
91
 
108
92
  const CycleElements = ({ intl, values }) => {
109
93
  const visible = values.slice(0, 3);
94
+ const hidden = values.slice(3);
110
95
 
111
96
  if (!visible.length) return null;
112
97
 
@@ -123,6 +108,13 @@ const CycleElements = ({ intl, values }) => {
123
108
  {value}
124
109
  </span>
125
110
  ))}
111
+ {hidden.length > 0 && (
112
+ <TagOverflowPopup
113
+ items={hidden}
114
+ className="navigator-tag cycle-element more"
115
+ ariaLabel={`${intl.formatMessage(messages.cycle)}: ${hidden.join(', ')}`}
116
+ />
117
+ )}
126
118
  </div>
127
119
  );
128
120
  };
@@ -141,7 +133,7 @@ const NavigatorCatalogueCardItem = (props) => {
141
133
  .join(', ');
142
134
  const toolProvider = result?._result?.tool_provider?.raw;
143
135
  const adaptationSupportCycleSteps = rawValueAsArray(
144
- result.adaptation_support_cycle_step,
136
+ result.cca_adaptation_support_cycle_step,
145
137
  )
146
138
  .map((value) => {
147
139
  const title = value?.title || value;
@@ -94,7 +94,7 @@ describe('NavigatorCatalogueCardItem', () => {
94
94
  'Datasets and indicators',
95
95
  ],
96
96
  },
97
- adaptation_support_cycle_step: {
97
+ cca_adaptation_support_cycle_step: {
98
98
  raw: [
99
99
  { title: 'Step 1: Preparing the ground' },
100
100
  { title: 'Step 2: Assessing risks' },
@@ -131,7 +131,10 @@ describe('NavigatorCatalogueCardItem', () => {
131
131
  expect(screen.getByText('Resilience')).toBeInTheDocument();
132
132
  expect(screen.getByText('Adaptation')).toBeInTheDocument();
133
133
  expect(screen.getByText('Step 1')).toBeInTheDocument();
134
- expect(screen.queryByText('Step 4')).not.toBeInTheDocument();
134
+ expect(
135
+ screen.getByRole('button', { name: 'Cycle: Step 4' }),
136
+ ).toHaveTextContent('+ 1');
137
+ expect(screen.getByText('Step 4')).toBeInTheDocument();
135
138
  expect(
136
139
  screen.getByTitle(
137
140
  'Maps and graphs, Reports and decision support, Datasets and indicators',
@@ -152,6 +155,22 @@ describe('NavigatorCatalogueCardItem', () => {
152
155
  });
153
156
  });
154
157
 
158
+ it('shows the cycle steps from the field used by the catalogue filter', () => {
159
+ renderCard({
160
+ title: 'Climate planning tool',
161
+ href: 'https://example.com/tool',
162
+ cca_adaptation_support_cycle_step: {
163
+ raw: ['Step 2: Assessing risks'],
164
+ },
165
+ adaptation_support_cycle_step: {
166
+ raw: [{ title: 'Step 1: Preparing the ground' }],
167
+ },
168
+ });
169
+
170
+ expect(screen.getByText('Step 2')).toBeInTheDocument();
171
+ expect(screen.queryByText('Step 1')).not.toBeInTheDocument();
172
+ });
173
+
155
174
  it('renders sparse results and disables comparison without a UID', () => {
156
175
  useCompareTools.mockReturnValue({
157
176
  isSelected: true,
@@ -22,7 +22,9 @@ import {
22
22
  getColorForCount,
23
23
  normalizeCountryName,
24
24
  buildCountryCounts,
25
+ filterCountryCounts,
25
26
  mapLegendItems,
27
+ setCountryFilter,
26
28
  } from './utils';
27
29
 
28
30
  const messages = defineMessages({
@@ -147,10 +149,7 @@ const NavigatorCatalogueMapViewInner = (props) => {
147
149
  // Handler: replace country filter + switch to list view
148
150
  const handleExploreTools = React.useCallback(
149
151
  (countryName) => {
150
- const field = 'cca_geographic_countries.keyword';
151
- // Clear existing country filter, then set the new one
152
- searchContext?.removeFilter(field);
153
- searchContext?.addFilter(field, countryName, 'any');
152
+ setCountryFilter(searchContext, countryName);
154
153
  // Switch to listing view
155
154
  views.setActiveViewId('listing');
156
155
  },
@@ -159,8 +158,12 @@ const NavigatorCatalogueMapViewInner = (props) => {
159
158
 
160
159
  // Build lookup: countryName -> count
161
160
  const countryCounts = React.useMemo(
162
- () => buildCountryCounts(searchContext?.facets),
163
- [searchContext?.facets],
161
+ () =>
162
+ filterCountryCounts(
163
+ buildCountryCounts(searchContext?.facets),
164
+ searchContext?.filters,
165
+ ),
166
+ [searchContext?.facets, searchContext?.filters],
164
167
  );
165
168
 
166
169
  const [tileWMSSources, setTileWMSSources] = React.useState(null);
@@ -3,7 +3,9 @@ import {
3
3
  getColorForCount,
4
4
  normalizeCountryName,
5
5
  buildCountryCounts,
6
+ filterCountryCounts,
6
7
  mapLegendItems,
8
+ setCountryFilter,
7
9
  } from './utils';
8
10
 
9
11
  describe('getColorForCount', () => {
@@ -135,6 +137,54 @@ describe('buildCountryCounts', () => {
135
137
  });
136
138
  });
137
139
 
140
+ describe('setCountryFilter', () => {
141
+ it('replaces the current country selection', () => {
142
+ const searchContext = {
143
+ setFilter: jest.fn(),
144
+ addFilter: jest.fn(),
145
+ removeFilter: jest.fn(),
146
+ };
147
+
148
+ setCountryFilter(searchContext, 'Germany');
149
+
150
+ expect(searchContext.setFilter).toHaveBeenCalledWith(
151
+ 'cca_geographic_countries.keyword',
152
+ 'Germany',
153
+ 'any',
154
+ );
155
+ expect(searchContext.addFilter).not.toHaveBeenCalled();
156
+ expect(searchContext.removeFilter).not.toHaveBeenCalled();
157
+ });
158
+ });
159
+
160
+ describe('filterCountryCounts', () => {
161
+ const countryCounts = { Spain: 3, France: 7, Germany: 5 };
162
+
163
+ it('shows all country counts without a country filter', () => {
164
+ expect(filterCountryCounts(countryCounts, [])).toEqual(countryCounts);
165
+ });
166
+
167
+ it('shows only countries selected in the country filter', () => {
168
+ expect(
169
+ filterCountryCounts(countryCounts, [
170
+ {
171
+ field: 'cca_geographic_countries.keyword',
172
+ values: ['Spain'],
173
+ type: 'any',
174
+ },
175
+ ]),
176
+ ).toEqual({ Spain: 3 });
177
+ });
178
+
179
+ it('ignores filters for other fields', () => {
180
+ expect(
181
+ filterCountryCounts(countryCounts, [
182
+ { field: 'cca_climate_impacts.keyword', values: ['Flooding'] },
183
+ ]),
184
+ ).toEqual(countryCounts);
185
+ });
186
+ });
187
+
138
188
  describe('mapLegendItems', () => {
139
189
  it('has 5 legend items', () => {
140
190
  expect(mapLegendItems).toHaveLength(5);
@@ -47,6 +47,28 @@ export const buildCountryCounts = (facets) => {
47
47
  return counts;
48
48
  };
49
49
 
50
+ export const filterCountryCounts = (countryCounts, filters) => {
51
+ const selectedCountries =
52
+ filters?.find(({ field }) => field === 'cca_geographic_countries.keyword')
53
+ ?.values || [];
54
+
55
+ if (!selectedCountries.length) return countryCounts;
56
+
57
+ return Object.fromEntries(
58
+ selectedCountries
59
+ .filter((country) => countryCounts[country] !== undefined)
60
+ .map((country) => [country, countryCounts[country]]),
61
+ );
62
+ };
63
+
64
+ export const setCountryFilter = (searchContext, countryName) => {
65
+ searchContext?.setFilter(
66
+ 'cca_geographic_countries.keyword',
67
+ countryName,
68
+ 'any',
69
+ );
70
+ };
71
+
50
72
  /**
51
73
  * Legend items for the map view
52
74
  */
@@ -6,10 +6,11 @@ import { defineMessages, useIntl } from 'react-intl';
6
6
  import { Button, Checkbox, Icon, Loader, Message } from 'semantic-ui-react';
7
7
  import URLManager from '@elastic/search-ui/lib/cjs/URLManager';
8
8
  import { useSearchContext } from '@eeacms/search/lib/hocs';
9
- import ToolThumbnail from '../../theme/ToolThumbnail/ToolThumbnail';
9
+ import ToolThumbnail from '@eeacms/volto-cca-policy/components/theme/ToolThumbnail/ToolThumbnail';
10
10
  import guideSteps from '../../../search/navigator_guide/guideSteps';
11
11
  import { navigatorGuideStepAtom } from '../../../state';
12
- import { mergeGuideOptions } from './utils';
12
+ import { mergeGuideOptions, sortAdaptationSteps } from './utils';
13
+ import { rawValueAsArray } from '../NavigatorCatalogue/utils';
13
14
  import useGuideFacetOptions from './useGuideFacetOptions';
14
15
  import { getNavigatorCataloguePageURL } from '../../Search/NavigatorCatalogue/utils';
15
16
 
@@ -95,7 +96,7 @@ const isStepSelected = (filters, field) =>
95
96
 
96
97
  const previewTagTypes = {
97
98
  adaptationSectors: 'sector',
98
- climateImpacts: 'hazard',
99
+ climateHazards: 'hazard',
99
100
  adaptationStage: 'adaptation-stage',
100
101
  coverage: 'coverage',
101
102
  };
@@ -130,31 +131,39 @@ const NavigatorGuideContentView = ({ appConfig }) => {
130
131
  const hasSelections = steps.some(({ field }) =>
131
132
  isStepSelected(filters, field),
132
133
  );
133
- const options = mergeGuideOptions(
134
+ const mergedOptions = mergeGuideOptions(
134
135
  allFacetOptions?.[step?.field],
135
136
  getFacetOptions(facets, step?.field),
136
137
  selectedValues,
137
138
  hasSelections,
138
139
  );
140
+ const options =
141
+ step?.id === 'adaptationStage'
142
+ ? sortAdaptationSteps(mergedOptions)
143
+ : mergedOptions;
139
144
  const isLastStep = activeStep === steps.length - 1;
140
145
  const selectedStepLabels = steps
141
146
  .filter(({ field }) => isStepSelected(filters, field))
142
147
  .map(({ label }) =>
143
148
  intl.formatMessage(label).toLocaleLowerCase(currentLang),
144
149
  );
145
- const selectedPreviewTags = steps.flatMap((item) => {
146
- const values =
147
- (filters || []).find((filter) => filter.field === item.field)?.values ||
148
- [];
150
+ const selectedPreviewTags = (result) =>
151
+ steps.flatMap((item) => {
152
+ const values =
153
+ (filters || []).find((filter) => filter.field === item.field)?.values ||
154
+ [];
149
155
 
150
- return values.map((value) => ({
151
- label: item.id === 'adaptationStage' ? value.split(':')[0] : value,
152
- type: previewTagTypes[item.id],
153
- }));
154
- });
155
- const visiblePreviewTags = selectedPreviewTags.slice(0, 3);
156
- const remainingPreviewTags =
157
- selectedPreviewTags.length - visiblePreviewTags.length;
156
+ const resultValues = rawValueAsArray(
157
+ result[item.field.replace('.keyword', '')],
158
+ ).map((value) => value?.title || value);
159
+
160
+ return values
161
+ .filter((value) => resultValues.includes(value))
162
+ .map((value) => ({
163
+ label: item.id === 'adaptationStage' ? value.split(':')[0] : value,
164
+ type: previewTagTypes[item.id],
165
+ }));
166
+ });
158
167
 
159
168
  React.useEffect(() => {
160
169
  if (storedActiveStep !== activeStep) {
@@ -204,88 +213,94 @@ const NavigatorGuideContentView = ({ appConfig }) => {
204
213
 
205
214
  <div className="navigator-guide-layout">
206
215
  <section className="navigator-guide-wizard">
207
- <div className="navigator-guide-progress">
208
- {steps.map((item, index) => (
209
- <React.Fragment key={item.id}>
210
- <Button
211
- className={`navigator-guide-progress-step${
212
- index === activeStep ? ' active' : ''
213
- }${index < activeStep ? ' completed' : ''}`}
214
- aria-current={index === activeStep ? 'step' : undefined}
215
- onClick={() => setActiveStep(index)}
216
- >
217
- <span>
218
- {isStepSelected(filters, item.field) ? (
219
- <Icon className="ri-check-line" />
220
- ) : (
221
- index + 1
216
+ <div className="navigator-guide-wizard-content">
217
+ <div className="navigator-guide-progress-wrapper">
218
+ <div className="navigator-guide-progress">
219
+ {steps.map((item, index) => (
220
+ <React.Fragment key={item.id}>
221
+ <Button
222
+ className={`navigator-guide-progress-step${
223
+ index === activeStep ? ' active' : ''
224
+ }${index < activeStep ? ' completed' : ''}`}
225
+ aria-current={index === activeStep ? 'step' : undefined}
226
+ onClick={() => setActiveStep(index)}
227
+ >
228
+ <span>
229
+ {isStepSelected(filters, item.field) ? (
230
+ <Icon className="ri-check-line" />
231
+ ) : (
232
+ index + 1
233
+ )}
234
+ </span>
235
+ {intl.formatMessage(item.label)}
236
+ </Button>
237
+ {index < steps.length - 1 && (
238
+ <span
239
+ className={`navigator-guide-progress-connector${
240
+ index < activeStep ? ' completed' : ''
241
+ }`}
242
+ aria-hidden="true"
243
+ />
222
244
  )}
223
- </span>
224
- {intl.formatMessage(item.label)}
225
- </Button>
226
- {index < steps.length - 1 && (
227
- <span
228
- className={`navigator-guide-progress-connector${
229
- index < activeStep ? ' completed' : ''
230
- }`}
231
- aria-hidden="true"
232
- />
233
- )}
234
- </React.Fragment>
235
- ))}
236
- </div>
237
- <div
238
- className="navigator-guide-progress-bar"
239
- role="progressbar"
240
- aria-valuemin="1"
241
- aria-valuemax={steps.length}
242
- aria-valuenow={activeStep + 1}
243
- >
244
- <div
245
- className="navigator-guide-progress-bar-fill"
246
- style={{ width: `${((activeStep + 1) / steps.length) * 100}%` }}
247
- />
248
- </div>
245
+ </React.Fragment>
246
+ ))}
247
+ </div>
248
+ <div
249
+ className="navigator-guide-progress-bar"
250
+ role="progressbar"
251
+ aria-valuemin="1"
252
+ aria-valuemax={steps.length}
253
+ aria-valuenow={activeStep + 1}
254
+ >
255
+ <div
256
+ className="navigator-guide-progress-bar-fill"
257
+ style={{
258
+ width: `${((activeStep + 1) / steps.length) * 100}%`,
259
+ }}
260
+ />
261
+ </div>
262
+ <div className="navigator-guide-step-meta">
263
+ <span className="navigator-guide-step-number">
264
+ {intl.formatMessage(messages.stepProgress, {
265
+ current: activeStep + 1,
266
+ total: steps.length,
267
+ })}
268
+ </span>
269
+ <span>{intl.formatMessage(messages.selectAllThatApply)}</span>
270
+ </div>
271
+ </div>
249
272
 
250
- <div className="navigator-guide-step-meta">
251
- <span className="navigator-guide-step-number">
252
- {intl.formatMessage(messages.stepProgress, {
253
- current: activeStep + 1,
254
- total: steps.length,
255
- })}
256
- </span>
257
- <span>{intl.formatMessage(messages.selectAllThatApply)}</span>
258
- </div>
259
- <h3>{intl.formatMessage(step.title)}</h3>
260
- {step.description && <p>{intl.formatMessage(step.description)}</p>}
273
+ <h3>{intl.formatMessage(step.title)}</h3>
274
+ {step.description && <p>{intl.formatMessage(step.description)}</p>}
261
275
 
262
- {isLoading ? (
263
- <div className="navigator-guide-options-loading">
264
- <Loader active inline />
265
- </div>
266
- ) : options.length > 0 ? (
267
- <div className="navigator-guide-options">
268
- {options.map((option) => (
269
- <label
270
- key={option.value}
271
- className={`navigator-guide-option${
272
- selectedValues.includes(option.value) ? ' selected' : ''
273
- }${option.disabled ? ' disabled' : ''}`}
274
- aria-disabled={option.disabled || undefined}
275
- >
276
- <Checkbox
277
- checked={selectedValues.includes(option.value)}
278
- disabled={option.disabled}
279
- onChange={() => toggleValue(option.value)}
280
- />
281
- <span>{option.value}</span>
282
- <small>{option.count}</small>
283
- </label>
284
- ))}
285
- </div>
286
- ) : (
287
- <Message>{intl.formatMessage(messages.noOptions)}</Message>
288
- )}
276
+ {isLoading ? (
277
+ <div className="navigator-guide-options-loading">
278
+ <Loader active inline />
279
+ </div>
280
+ ) : options.length > 0 ? (
281
+ <div className="navigator-guide-options">
282
+ {options.map((option) => (
283
+ <label
284
+ key={option.value}
285
+ className={`navigator-guide-option${
286
+ selectedValues.includes(option.value) ? ' selected' : ''
287
+ }${option.disabled ? ' disabled' : ''}`}
288
+ aria-disabled={option.disabled || undefined}
289
+ >
290
+ <Checkbox
291
+ checked={selectedValues.includes(option.value)}
292
+ disabled={option.disabled}
293
+ onChange={() => toggleValue(option.value)}
294
+ />
295
+ <span>{option.value}</span>
296
+ <small>{option.count}</small>
297
+ </label>
298
+ ))}
299
+ </div>
300
+ ) : (
301
+ <Message>{intl.formatMessage(messages.noOptions)}</Message>
302
+ )}
303
+ </div>
289
304
 
290
305
  <div className="navigator-guide-actions">
291
306
  <Button
@@ -346,53 +361,56 @@ const NavigatorGuideContentView = ({ appConfig }) => {
346
361
  <div className="navigator-guide-preview-results">
347
362
  {(results || [])
348
363
  .slice(0, appConfig.previewResultsLimit)
349
- .map((result) => (
350
- <div
351
- className="navigator-guide-preview-result"
352
- key={result._original?._id || result.href}
353
- >
354
- <ToolThumbnail
355
- result={result}
356
- fallbackIcon="ri-stack-line"
357
- />
358
- <div className="navigator-guide-preview-result-content">
359
- <small
360
- className="navigator-tool-provider"
361
- title={result?._result?.tool_provider?.raw}
362
- >
363
- {result?._result?.tool_provider?.raw}
364
- </small>
365
- <a
366
- href={result.href}
367
- target="_blank"
368
- rel="noopener noreferrer"
369
- className="navigator-guide-preview-result-title"
370
- title={result.title}
371
- >
372
- <h5>{result.title}</h5>
373
- </a>
374
- {selectedPreviewTags.length > 0 && (
375
- <div className="navigator-guide-preview-tags">
376
- {visiblePreviewTags.map(
377
- ({ label, type }, index) => (
364
+ .map((result) => {
365
+ const tags = selectedPreviewTags(result);
366
+ const visibleTags = tags.slice(0, 3);
367
+ const remainingTags = tags.length - visibleTags.length;
368
+ return (
369
+ <div
370
+ className="navigator-guide-preview-result"
371
+ key={result._original?._id || result.href}
372
+ >
373
+ <ToolThumbnail
374
+ result={result}
375
+ fallbackIcon="ri-stack-line"
376
+ />
377
+ <div className="navigator-guide-preview-result-content">
378
+ <small
379
+ className="navigator-tool-provider"
380
+ title={result?._result?.tool_provider?.raw}
381
+ >
382
+ {result?._result?.tool_provider?.raw}
383
+ </small>
384
+ <a
385
+ href={result.href}
386
+ target="_blank"
387
+ rel="noopener noreferrer"
388
+ className="navigator-guide-preview-result-title"
389
+ title={result.title}
390
+ >
391
+ <h5>{result.title}</h5>
392
+ </a>
393
+ {tags.length > 0 && (
394
+ <div className="navigator-guide-preview-tags">
395
+ {visibleTags.map(({ label, type }, index) => (
378
396
  <span
379
397
  className={`navigator-tag ${type}`}
380
398
  key={`${type}-${label}-${index}`}
381
399
  >
382
400
  {label}
383
401
  </span>
384
- ),
385
- )}
386
- {remainingPreviewTags > 0 && (
387
- <span className="navigator-guide-preview-tag-more">
388
- + {remainingPreviewTags}
389
- </span>
390
- )}
391
- </div>
392
- )}
402
+ ))}
403
+ {remainingTags > 0 && (
404
+ <span className="navigator-guide-preview-tag-more">
405
+ + {remainingTags}
406
+ </span>
407
+ )}
408
+ </div>
409
+ )}
410
+ </div>
393
411
  </div>
394
- </div>
395
- ))}
412
+ );
413
+ })}
396
414
  </div>
397
415
  <Button
398
416
  labelPosition="right"