@eeacms/volto-eea-design-system 1.61.3 → 1.61.5
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
CHANGED
|
@@ -4,6 +4,23 @@ 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.61.5](https://github.com/eea/volto-eea-design-system/compare/1.61.4...1.61.5) - 6 October 2026
|
|
8
|
+
|
|
9
|
+
#### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
- feat(subsite): use optional useAISearchIcon for subsite passed via headerSearch Controlpanel refs#308136 [nileshgulia1 - [`5c5bebc`](https://github.com/eea/volto-eea-design-system/commit/5c5bebcb47e99168553d3f811493ec7d98858c0e)]
|
|
12
|
+
|
|
13
|
+
#### :nail_care: Enhancements
|
|
14
|
+
|
|
15
|
+
- refactor: only turn off the site wide ai search action when subsite wants it. [nileshgulia1 - [`1cf5bed`](https://github.com/eea/volto-eea-design-system/commit/1cf5bed4bb4f5942635aabf2009f128171704a80)]
|
|
16
|
+
|
|
17
|
+
### [1.61.4](https://github.com/eea/volto-eea-design-system/compare/1.61.3...1.61.4) - 6 October 2026
|
|
18
|
+
|
|
19
|
+
#### :rocket: New Features
|
|
20
|
+
|
|
21
|
+
- feat: add hideAction button if set in headerConfig refs#308136 [nileshgulia1 - [`03a1fc1`](https://github.com/eea/volto-eea-design-system/commit/03a1fc189347966660748ad9f6cad4efd2306a08)]
|
|
22
|
+
- feat: add support for queryParams if used with a volto search block #refs308136 [nileshgulia1 - [`99e25b9`](https://github.com/eea/volto-eea-design-system/commit/99e25b97b62b4969e73b95086fd8585a7aceb8c3)]
|
|
23
|
+
|
|
7
24
|
### [1.61.3](https://github.com/eea/volto-eea-design-system/compare/1.61.2...1.61.3) - 1 October 2026
|
|
8
25
|
|
|
9
26
|
#### :hammer_and_wrench: Others
|
package/package.json
CHANGED
package/src/ui/Header/Header.jsx
CHANGED
|
@@ -17,7 +17,11 @@ import burgerIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/asse
|
|
|
17
17
|
import HeaderSearchPopUp from './HeaderSearchPopUp';
|
|
18
18
|
import HeaderMenuPopUp from './HeaderMenuPopUp';
|
|
19
19
|
import { useAISummaryToggle } from './useAISummaryToggle';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
findBestMatchingMenuItem,
|
|
22
|
+
getSearchInput,
|
|
23
|
+
isMenuItemActive,
|
|
24
|
+
} from './utils';
|
|
21
25
|
import PropTypes from 'prop-types';
|
|
22
26
|
import omit from 'lodash/omit';
|
|
23
27
|
import { isInternalURL } from '@plone/volto/helpers/Url/Url';
|
|
@@ -162,10 +166,16 @@ const Main = ({
|
|
|
162
166
|
const [burger, setBurger] = React.useState('');
|
|
163
167
|
const searchInputRef = React.useRef(null);
|
|
164
168
|
const [isClient, setIsClient] = React.useState();
|
|
165
|
-
const
|
|
169
|
+
const siteUseAISearchIcon = useSelector(
|
|
166
170
|
(state) =>
|
|
167
171
|
state.reduxAsyncConnect?.headerSettings?.useAISearchIcon || false,
|
|
168
172
|
);
|
|
173
|
+
// The search box matching the current path can turn the site-wide icon off
|
|
174
|
+
const useAISearchIcon =
|
|
175
|
+
siteUseAISearchIcon &&
|
|
176
|
+
(getSearchInput(headerSearchBox || [], { pathname: pathname || '' })
|
|
177
|
+
.useAISearchIcon ??
|
|
178
|
+
true);
|
|
169
179
|
|
|
170
180
|
const [aiSummaryEnabled] = useAISummaryToggle();
|
|
171
181
|
|
|
@@ -772,12 +772,13 @@ test('menu click outside behavior with different active states', async () => {
|
|
|
772
772
|
});
|
|
773
773
|
|
|
774
774
|
describe('header search icon and AI summary state', () => {
|
|
775
|
-
const renderMain = () =>
|
|
775
|
+
const renderMain = (props = {}) =>
|
|
776
776
|
renderWithProvider(
|
|
777
777
|
<Router history={createMemoryHistory()}>
|
|
778
778
|
<Header>
|
|
779
779
|
<Header.Main
|
|
780
780
|
pathname="/test"
|
|
781
|
+
{...props}
|
|
781
782
|
menuItems={[
|
|
782
783
|
{ '@id': '/test', url: '/test', title: 'Test', items: [] },
|
|
783
784
|
]}
|
|
@@ -824,4 +825,37 @@ describe('header search icon and AI summary state', () => {
|
|
|
824
825
|
'search-line.svg',
|
|
825
826
|
);
|
|
826
827
|
});
|
|
828
|
+
|
|
829
|
+
describe('search box useAISearchIcon override', () => {
|
|
830
|
+
const headerSearchBox = [
|
|
831
|
+
{ type: 'search-input', path: '/search' },
|
|
832
|
+
{
|
|
833
|
+
type: 'search-input',
|
|
834
|
+
matchpath: '/en/epanet',
|
|
835
|
+
path: '/en/epanet/site-search',
|
|
836
|
+
useAISearchIcon: false,
|
|
837
|
+
},
|
|
838
|
+
];
|
|
839
|
+
|
|
840
|
+
it('shows the plain magnifier in the search box path that turns the AI icon off', () => {
|
|
841
|
+
mockState.reduxAsyncConnect.headerSettings.useAISearchIcon = true;
|
|
842
|
+
|
|
843
|
+
const { container } = renderMain({
|
|
844
|
+
pathname: '/en/epanet/about',
|
|
845
|
+
headerSearchBox,
|
|
846
|
+
});
|
|
847
|
+
expect(
|
|
848
|
+
container.querySelector('img[alt="Global search"]'),
|
|
849
|
+
).toHaveAttribute('src', 'search-line.svg');
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it('keeps the site-wide AI sparkle outside that path', () => {
|
|
853
|
+
mockState.reduxAsyncConnect.headerSettings.useAISearchIcon = true;
|
|
854
|
+
|
|
855
|
+
const { container } = renderMain({ pathname: '/en', headerSearchBox });
|
|
856
|
+
expect(
|
|
857
|
+
container.querySelector('img[alt="Global search"]'),
|
|
858
|
+
).toHaveAttribute('src', 'ai-search.svg');
|
|
859
|
+
});
|
|
860
|
+
});
|
|
827
861
|
});
|
|
@@ -10,6 +10,7 @@ import { handleEnterKeyPress } from '@eeacms/volto-eea-design-system/helpers';
|
|
|
10
10
|
import searchSVG from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/search-line.svg';
|
|
11
11
|
import aiSearchIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/ai-search.svg';
|
|
12
12
|
import { useAISummaryToggle } from './useAISummaryToggle';
|
|
13
|
+
import { getSearchInput } from './utils';
|
|
13
14
|
|
|
14
15
|
const getRandomItems = (arr, max) => {
|
|
15
16
|
return (
|
|
@@ -19,18 +20,6 @@ const getRandomItems = (arr, max) => {
|
|
|
19
20
|
);
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
const getSearchInput = (config, location) => {
|
|
23
|
-
return (
|
|
24
|
-
config.filter(
|
|
25
|
-
(v) =>
|
|
26
|
-
v.type === 'search-input' &&
|
|
27
|
-
location.pathname.match(v.matchpath ? v.matchpath : v.path),
|
|
28
|
-
)[0] ||
|
|
29
|
-
config.filter((v) => v.type === 'search-input')[0] ||
|
|
30
|
-
{}
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
23
|
const serializeAction = (action, location) => {
|
|
35
24
|
if (action.type === 'info' && isArray(action.content)) {
|
|
36
25
|
const matchedContent =
|
|
@@ -75,13 +64,18 @@ function HeaderSearchPopUp({
|
|
|
75
64
|
const nodeRef = React.useRef();
|
|
76
65
|
|
|
77
66
|
const searchInput = getSearchInput(headerSearchBox, location);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
67
|
+
// A search box with hideActions shows no info text or buttons below it
|
|
68
|
+
const actions = searchInput.hideActions
|
|
69
|
+
? []
|
|
70
|
+
: headerSearchBox
|
|
71
|
+
.filter((v) => ['info', 'button-link'].includes(v.type))
|
|
72
|
+
.map((v) => serializeAction(v, location)) || [];
|
|
82
73
|
|
|
83
74
|
const {
|
|
84
75
|
path = '',
|
|
76
|
+
// URL parameter that carries the search text, e.g. 'SearchableText' for
|
|
77
|
+
// a page with Volto's Search block
|
|
78
|
+
queryParam = 'q',
|
|
85
79
|
placeholder = 'Search',
|
|
86
80
|
suggestionsTitle,
|
|
87
81
|
maxToShow,
|
|
@@ -92,10 +86,13 @@ function HeaderSearchPopUp({
|
|
|
92
86
|
getRandomItems(suggestions, maxToShow),
|
|
93
87
|
);
|
|
94
88
|
|
|
95
|
-
const
|
|
89
|
+
const siteUseAISearchIcon = useSelector(
|
|
96
90
|
(state) =>
|
|
97
91
|
state.reduxAsyncConnect?.headerSettings?.useAISearchIcon || false,
|
|
98
92
|
);
|
|
93
|
+
// A search box can turn the site-wide icon off, e.g. a subsite search
|
|
94
|
+
const useAISearchIcon =
|
|
95
|
+
siteUseAISearchIcon && (searchInput.useAISearchIcon ?? true);
|
|
99
96
|
|
|
100
97
|
const [aiSummaryEnabled] = useAISummaryToggle();
|
|
101
98
|
|
|
@@ -112,7 +109,7 @@ function HeaderSearchPopUp({
|
|
|
112
109
|
|
|
113
110
|
const onSubmit = (event) => {
|
|
114
111
|
const text = searchInputRef?.current?.inputRef?.current?.value;
|
|
115
|
-
history.push(`${path}
|
|
112
|
+
history.push(`${path}?${queryParam}=${text}`);
|
|
116
113
|
|
|
117
114
|
if (window?.searchContext?.resetSearch) {
|
|
118
115
|
window.searchContext.resetSearch({ searchTerm: text });
|
|
@@ -168,7 +165,7 @@ function HeaderSearchPopUp({
|
|
|
168
165
|
return (
|
|
169
166
|
<List.Item key={i}>
|
|
170
167
|
<Link
|
|
171
|
-
to={`${path}
|
|
168
|
+
to={`${path}?${queryParam}=${item}`}
|
|
172
169
|
onClick={() => onClickHandler(item)}
|
|
173
170
|
>
|
|
174
171
|
{item}
|
|
@@ -111,6 +111,48 @@ describe('HeaderSearchPopUp', () => {
|
|
|
111
111
|
expect(history.location.pathname).toBe('/search');
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
const submitText = (headerSearchBox, text) => {
|
|
115
|
+
const searchInputRef = React.createRef();
|
|
116
|
+
const { container } = render(
|
|
117
|
+
<Provider store={mockStore}>
|
|
118
|
+
<Router history={history}>
|
|
119
|
+
<HeaderSearchPopUp
|
|
120
|
+
headerSearchBox={headerSearchBox}
|
|
121
|
+
onClose={mockOnClose}
|
|
122
|
+
searchInputRef={searchInputRef}
|
|
123
|
+
triggerRefs={[]}
|
|
124
|
+
/>
|
|
125
|
+
</Router>
|
|
126
|
+
</Provider>,
|
|
127
|
+
);
|
|
128
|
+
fireEvent.change(screen.getByPlaceholderText('Search'), {
|
|
129
|
+
target: { value: text },
|
|
130
|
+
});
|
|
131
|
+
fireEvent.submit(container.querySelector('form'));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
it('should submit the search text as q by default', () => {
|
|
135
|
+
submitText(sampleHeaderSearchBox, 'plenary');
|
|
136
|
+
expect(history.location.pathname).toBe('/search');
|
|
137
|
+
expect(history.location.search).toBe('?q=plenary');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should submit the search text as the entry queryParam', () => {
|
|
141
|
+
submitText(
|
|
142
|
+
[
|
|
143
|
+
{
|
|
144
|
+
type: 'search-input',
|
|
145
|
+
path: '/en/epanet/site-search',
|
|
146
|
+
queryParam: 'SearchableText',
|
|
147
|
+
placeholder: 'Search',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
'plenary',
|
|
151
|
+
);
|
|
152
|
+
expect(history.location.pathname).toBe('/en/epanet/site-search');
|
|
153
|
+
expect(history.location.search).toBe('?SearchableText=plenary');
|
|
154
|
+
});
|
|
155
|
+
|
|
114
156
|
it('should navigate to the suggestion when a suggestion is clicked', () => {
|
|
115
157
|
window.searchContext = { resetSearch: jest.fn() };
|
|
116
158
|
|
|
@@ -297,4 +339,62 @@ describe('HeaderSearchPopUp', () => {
|
|
|
297
339
|
);
|
|
298
340
|
});
|
|
299
341
|
});
|
|
342
|
+
|
|
343
|
+
describe('search box useAISearchIcon override', () => {
|
|
344
|
+
beforeEach(() => {
|
|
345
|
+
window.localStorage.clear();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const renderWithEntry = (entry) =>
|
|
349
|
+
render(
|
|
350
|
+
<Provider store={mockStore}>
|
|
351
|
+
<Router history={history}>
|
|
352
|
+
<HeaderSearchPopUp
|
|
353
|
+
headerSearchBox={[{ ...sampleHeaderSearchBox[0], ...entry }]}
|
|
354
|
+
onClose={mockOnClose}
|
|
355
|
+
triggerRefs={[]}
|
|
356
|
+
/>
|
|
357
|
+
</Router>
|
|
358
|
+
</Provider>,
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
it('shows the plain magnifier when the search box turns the AI icon off', () => {
|
|
362
|
+
mockState.reduxAsyncConnect.headerSettings.useAISearchIcon = true;
|
|
363
|
+
|
|
364
|
+
const { container } = renderWithEntry({ useAISearchIcon: false });
|
|
365
|
+
expect(container.querySelector('img')).toHaveAttribute(
|
|
366
|
+
'src',
|
|
367
|
+
'search-line.svg',
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('hideActions', () => {
|
|
373
|
+
const renderWith = (headerSearchBox) =>
|
|
374
|
+
render(
|
|
375
|
+
<Provider store={mockStore}>
|
|
376
|
+
<Router history={history}>
|
|
377
|
+
<HeaderSearchPopUp
|
|
378
|
+
headerSearchBox={headerSearchBox}
|
|
379
|
+
onClose={mockOnClose}
|
|
380
|
+
triggerRefs={[]}
|
|
381
|
+
/>
|
|
382
|
+
</Router>
|
|
383
|
+
</Provider>,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
it('shows the actions by default', () => {
|
|
387
|
+
renderWith(sampleHeaderSearchBox);
|
|
388
|
+
expect(screen.getByText('Advanced Search')).toBeInTheDocument();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('hides the actions when the matched search input has hideActions', () => {
|
|
392
|
+
renderWith([
|
|
393
|
+
{ ...sampleHeaderSearchBox[0], hideActions: true },
|
|
394
|
+
...sampleHeaderSearchBox.slice(1),
|
|
395
|
+
]);
|
|
396
|
+
expect(screen.queryByText('Advanced Search')).toBeNull();
|
|
397
|
+
expect(screen.getByPlaceholderText('Search')).toBeInTheDocument();
|
|
398
|
+
});
|
|
399
|
+
});
|
|
300
400
|
});
|
package/src/ui/Header/utils.js
CHANGED
|
@@ -91,3 +91,24 @@ export const numbersToMenuItemColumns = (numbers) => {
|
|
|
91
91
|
})
|
|
92
92
|
.filter((col) => col !== '');
|
|
93
93
|
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Finds the search-input entry of the header search box config that matches
|
|
97
|
+
* the current path (its matchpath, else its path), falling back to the first
|
|
98
|
+
* search-input entry.
|
|
99
|
+
*
|
|
100
|
+
* @param {Array} config - headerSearchBox entries
|
|
101
|
+
* @param {Object} location - object with a pathname
|
|
102
|
+
* @returns {Object} The matched search-input entry, or {}
|
|
103
|
+
*/
|
|
104
|
+
export const getSearchInput = (config, location) => {
|
|
105
|
+
return (
|
|
106
|
+
config.filter(
|
|
107
|
+
(v) =>
|
|
108
|
+
v.type === 'search-input' &&
|
|
109
|
+
location.pathname.match(v.matchpath ? v.matchpath : v.path),
|
|
110
|
+
)[0] ||
|
|
111
|
+
config.filter((v) => v.type === 'search-input')[0] ||
|
|
112
|
+
{}
|
|
113
|
+
);
|
|
114
|
+
};
|