@eeacms/volto-eea-website-theme 3.12.0 → 3.13.0

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,11 @@ 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
+ ### [3.13.0](https://github.com/eea/volto-eea-website-theme/compare/3.12.0...3.13.0) - 4 November 2025
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - Bump version from 3.12.1 to 3.13.0 [David Ichim - [`c182fea`](https://github.com/eea/volto-eea-website-theme/commit/c182fea83aad1c0b224c37287a5c2935a2e893d0)]
7
12
  ### [3.12.0](https://github.com/eea/volto-eea-website-theme/compare/3.11.0...3.12.0) - 4 November 2025
8
13
 
9
14
  #### :bug: Bug Fixes
@@ -1,4 +1,4 @@
1
- require('dotenv').config({ path: __dirname + '/.env' })
1
+ require('dotenv').config({ path: __dirname + '/.env' });
2
2
 
3
3
  module.exports = {
4
4
  testMatch: ['**/src/addons/**/?(*.)+(spec|test).[jt]s?(x)'],
@@ -48,4 +48,4 @@ module.exports = {
48
48
  '<rootDir>/node_modules/@eeacms/volto-eea-website-theme/jest.setup.js',
49
49
  ],
50
50
  }),
51
- }
51
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-website-theme",
3
- "version": "3.12.0",
3
+ "version": "3.13.0",
4
4
  "description": "@eeacms/volto-eea-website-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -8,6 +8,8 @@ import { getNavigation } from '@plone/volto/actions';
8
8
  import { defineMessages, useIntl } from 'react-intl';
9
9
  import config from '@plone/volto/registry';
10
10
 
11
+ import { numbersToMenuItemColumns } from '@eeacms/volto-eea-design-system/ui/Header/utils';
12
+
11
13
  import upSVG from '@plone/volto/icons/up-key.svg';
12
14
  import downSVG from '@plone/volto/icons/down-key.svg';
13
15
 
@@ -36,30 +38,6 @@ const defaultRouteSettings = {
36
38
  // Don't include empty arrays in default settings
37
39
  };
38
40
 
39
- // Helper functions for menuItemColumns conversion (numbers to semantic UI format)
40
- const numberToColumnString = (num) => {
41
- const numbers = [
42
- '',
43
- 'one',
44
- 'two',
45
- 'three',
46
- 'four',
47
- 'five',
48
- 'six',
49
- 'seven',
50
- 'eight',
51
- 'nine',
52
- ];
53
- return numbers[num] ? `${numbers[num]} wide column` : '';
54
- };
55
-
56
- const numbersToMenuItemColumns = (numbers) => {
57
- if (!Array.isArray(numbers)) return [];
58
- return numbers
59
- .map((num) => numberToColumnString(parseInt(num)))
60
- .filter((col) => col !== '');
61
- };
62
-
63
41
  const columnStringToNumber = (colString) => {
64
42
  const numbers = {
65
43
  one: 1,
@@ -78,7 +56,7 @@ const columnStringToNumber = (colString) => {
78
56
  return match ? numbers[match[1]] : null;
79
57
  };
80
58
 
81
- const menuItemColumnsToNumbers = (columns) => {
59
+ export const menuItemColumnsToNumbers = (columns) => {
82
60
  if (!Array.isArray(columns)) return [];
83
61
  return columns
84
62
  .map((col) => columnStringToNumber(col))
@@ -420,6 +398,20 @@ const NavigationBehaviorWidget = (props) => {
420
398
  portal_type: ______,
421
399
  ...settings
422
400
  } = route;
401
+
402
+ // // Convert menuItemColumns from numbers back to semantic UI format for backend storage
403
+ if (
404
+ settings.menuItemColumns !== null &&
405
+ settings.menuItemColumns &&
406
+ Array.isArray(settings.menuItemColumns)
407
+ ) {
408
+ if (settings.menuItemColumns.length > 0) {
409
+ settings.menuItemColumns = numbersToMenuItemColumns(
410
+ settings.menuItemColumns,
411
+ );
412
+ }
413
+ }
414
+
423
415
  newSettings[routeId] = settings;
424
416
  });
425
417
 
@@ -490,6 +490,120 @@ describe('NavigationBehaviorWidget', () => {
490
490
  expect(screen.getByText('Test Route 2')).toBeInTheDocument();
491
491
  });
492
492
 
493
+ it('handles menuItemColumns as integers from backend (develop server format)', async () => {
494
+ const existingSettings = {
495
+ 'http://localhost:3000/test-route-1': {
496
+ hideChildrenFromNavigation: false,
497
+ menuItemColumns: [8, 4], // Integer format from develop server
498
+ },
499
+ };
500
+
501
+ render(
502
+ <Provider store={store}>
503
+ <NavigationBehaviorWidget
504
+ id="test"
505
+ value={JSON.stringify(existingSettings)}
506
+ onChange={mockOnChange}
507
+ />
508
+ </Provider>,
509
+ );
510
+
511
+ const accordionTitles = screen.getAllByTestId('accordion-title');
512
+ fireEvent.click(accordionTitles[0]);
513
+
514
+ await waitFor(() => {
515
+ expect(
516
+ screen.getByText('Hide Children From Navigation'),
517
+ ).toBeInTheDocument();
518
+ });
519
+
520
+ // Verify the widget displays the values correctly (converted to numbers for display)
521
+ // The widget should handle integer input and display it properly
522
+ });
523
+
524
+ it('handles menuItemColumns as semantic UI strings from backend (production format)', async () => {
525
+ const existingSettings = {
526
+ 'http://localhost:3000/test-route-1': {
527
+ hideChildrenFromNavigation: false,
528
+ menuItemColumns: ['eight wide column', 'four wide column'], // String format from production
529
+ },
530
+ };
531
+
532
+ render(
533
+ <Provider store={store}>
534
+ <NavigationBehaviorWidget
535
+ id="test"
536
+ value={JSON.stringify(existingSettings)}
537
+ onChange={mockOnChange}
538
+ />
539
+ </Provider>,
540
+ );
541
+
542
+ const accordionTitles = screen.getAllByTestId('accordion-title');
543
+ fireEvent.click(accordionTitles[0]);
544
+
545
+ await waitFor(() => {
546
+ expect(
547
+ screen.getByText('Hide Children From Navigation'),
548
+ ).toBeInTheDocument();
549
+ });
550
+
551
+ // Verify the widget displays the values correctly (converted from strings to numbers for display)
552
+ // The widget should handle semantic UI string input and convert it to numbers for display
553
+ });
554
+
555
+ it('converts menuItemColumns to semantic UI format when saving', async () => {
556
+ const existingSettings = {
557
+ 'http://localhost:3000/test-route-1': {
558
+ hideChildrenFromNavigation: false,
559
+ menuItemColumns: [2, 3], // Numbers that should be converted to semantic UI format
560
+ },
561
+ };
562
+
563
+ render(
564
+ <Provider store={store}>
565
+ <NavigationBehaviorWidget
566
+ id="test"
567
+ value={JSON.stringify(existingSettings)}
568
+ onChange={mockOnChange}
569
+ />
570
+ </Provider>,
571
+ );
572
+
573
+ const accordionTitles = screen.getAllByTestId('accordion-title');
574
+ fireEvent.click(accordionTitles[0]);
575
+
576
+ await waitFor(() => {
577
+ expect(
578
+ screen.getByText('Hide Children From Navigation'),
579
+ ).toBeInTheDocument();
580
+ });
581
+
582
+ // Simulate a change by clicking the checkbox
583
+ const checkboxes = screen.getAllByRole('checkbox');
584
+ fireEvent.click(checkboxes[0]);
585
+
586
+ await waitFor(() => {
587
+ expect(mockOnChange).toHaveBeenCalled();
588
+ const lastCallIndex = mockOnChange.mock.calls.length - 1;
589
+ const savedValue = JSON.parse(mockOnChange.mock.calls[lastCallIndex][1]);
590
+
591
+ // Find the route settings
592
+ const routeKey = Object.keys(savedValue).find((key) =>
593
+ key.includes('test-route-1'),
594
+ );
595
+ const route1Settings = savedValue[routeKey];
596
+
597
+ // Verify menuItemColumns are saved in semantic UI format, not as integers
598
+ if (route1Settings && route1Settings.menuItemColumns) {
599
+ route1Settings.menuItemColumns.forEach((col) => {
600
+ expect(typeof col).toBe('string');
601
+ expect(col).toMatch(/wide column$/);
602
+ });
603
+ }
604
+ });
605
+ });
606
+
493
607
  it('displays route paths in accordion titles', () => {
494
608
  render(
495
609
  <Provider store={store}>
package/src/config.js CHANGED
@@ -112,6 +112,8 @@ export const footerOpts = {
112
112
  tablet: 6,
113
113
  computer: 4,
114
114
  },
115
+ width: 270,
116
+ height: 100,
115
117
  },
116
118
  {
117
119
  url: 'https://www.eionet.europa.eu/',
@@ -123,6 +125,8 @@ export const footerOpts = {
123
125
  tablet: 6,
124
126
  computer: 4,
125
127
  },
128
+ width: 330,
129
+ height: 100,
126
130
  },
127
131
  ],
128
132
  social: [
@@ -240,6 +244,8 @@ export const headerOpts = {
240
244
  },
241
245
  ],
242
246
  },
247
+ logoWidth: 350,
248
+ logoHeight: 130,
243
249
  };
244
250
 
245
251
  export const languages = [
@@ -62,9 +62,7 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
62
62
  const navigationSettings = useSelector(
63
63
  (state) => state.navigationSettings?.settings || {},
64
64
  );
65
- const navigationLoaded = useSelector(
66
- (state) => state.navigationSettings?.loaded,
67
- );
65
+ const updateRequest = useSelector((state) => state.content.update);
68
66
 
69
67
  // Combine navigation settings from backend with config fallback
70
68
  const configLayouts = config.settings?.menuItemsLayouts || {};
@@ -112,6 +110,17 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
112
110
  });
113
111
  }
114
112
 
113
+ React.useEffect(() => {
114
+ if (
115
+ updateRequest?.loaded &&
116
+ removeTrailingSlash(updateRequest?.content?.['@id'] || '') ===
117
+ removeTrailingSlash(pathname)
118
+ ) {
119
+ dispatch(getNavigationSettings(pathname));
120
+ }
121
+ dispatch(getNavigationSettings(pathname));
122
+ }, [updateRequest, dispatch, pathname]);
123
+
115
124
  React.useEffect(() => {
116
125
  const base_url = getBaseUrl(pathname);
117
126
  const { settings } = config;
@@ -125,12 +134,7 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
125
134
  if (token !== previousToken) {
126
135
  dispatch(getNavigation(base_url, settings.navDepth));
127
136
  }
128
-
129
- // Fetch navigation settings
130
- if (!navigationLoaded) {
131
- dispatch(getNavigationSettings(pathname));
132
- }
133
- }, [pathname, token, dispatch, previousToken, navigationLoaded]);
137
+ }, [pathname, token, dispatch, previousToken]);
134
138
 
135
139
  return (
136
140
  <Header menuItems={items}>
@@ -215,6 +219,8 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
215
219
  title={eea.websiteTitle}
216
220
  alt={eea.organisationName}
217
221
  url={eea.logoTargetUrl}
222
+ height={headerOpts.logoHeight}
223
+ width={headerOpts.logoWidth}
218
224
  />
219
225
 
220
226
  {!!subsite && subsite.title && (