@kitconcept/volto-light-theme 8.0.0-alpha.3 → 8.0.0-alpha.30

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 (200) hide show
  1. package/.changelog.draft +6 -9
  2. package/CHANGELOG.md +310 -0
  3. package/locales/af/LC_MESSAGES/volto.po +645 -0
  4. package/locales/ar/LC_MESSAGES/volto.po +645 -0
  5. package/locales/bg/LC_MESSAGES/volto.po +645 -0
  6. package/locales/bn/LC_MESSAGES/volto.po +645 -0
  7. package/locales/ca/LC_MESSAGES/volto.po +645 -0
  8. package/locales/cs/LC_MESSAGES/volto.po +645 -0
  9. package/locales/cy/LC_MESSAGES/volto.po +645 -0
  10. package/locales/da/LC_MESSAGES/volto.po +645 -0
  11. package/locales/de/LC_MESSAGES/volto.po +83 -167
  12. package/locales/el/LC_MESSAGES/volto.po +645 -0
  13. package/locales/en/LC_MESSAGES/volto.po +30 -115
  14. package/locales/en_AU/LC_MESSAGES/volto.po +645 -0
  15. package/locales/en_GB/LC_MESSAGES/volto.po +645 -0
  16. package/locales/eo/LC_MESSAGES/volto.po +645 -0
  17. package/locales/es/LC_MESSAGES/volto.po +75 -160
  18. package/locales/et/LC_MESSAGES/volto.po +645 -0
  19. package/locales/eu/LC_MESSAGES/volto.po +59 -125
  20. package/locales/fa/LC_MESSAGES/volto.po +645 -0
  21. package/locales/fi/LC_MESSAGES/volto.po +645 -0
  22. package/locales/fr/LC_MESSAGES/volto.po +645 -0
  23. package/locales/fu/LC_MESSAGES/volto.po +645 -0
  24. package/locales/ga/LC_MESSAGES/volto.po +645 -0
  25. package/locales/gl/LC_MESSAGES/volto.po +645 -0
  26. package/locales/he/LC_MESSAGES/volto.po +645 -0
  27. package/locales/hi/LC_MESSAGES/volto.po +645 -0
  28. package/locales/hr/LC_MESSAGES/volto.po +645 -0
  29. package/locales/hu/LC_MESSAGES/volto.po +645 -0
  30. package/locales/hy/LC_MESSAGES/volto.po +645 -0
  31. package/locales/id/LC_MESSAGES/volto.po +645 -0
  32. package/locales/it/LC_MESSAGES/volto.po +645 -0
  33. package/locales/ja/LC_MESSAGES/volto.po +645 -0
  34. package/locales/ka/LC_MESSAGES/volto.po +645 -0
  35. package/locales/kn/LC_MESSAGES/volto.po +645 -0
  36. package/locales/ko/LC_MESSAGES/volto.po +645 -0
  37. package/locales/lt/LC_MESSAGES/volto.po +645 -0
  38. package/locales/lv/LC_MESSAGES/volto.po +645 -0
  39. package/locales/mi/LC_MESSAGES/volto.po +645 -0
  40. package/locales/mk_MK/LC_MESSAGES/volto.po +645 -0
  41. package/locales/ms/LC_MESSAGES/volto.po +645 -0
  42. package/locales/mt/LC_MESSAGES/volto.po +645 -0
  43. package/locales/my/LC_MESSAGES/volto.po +645 -0
  44. package/locales/nl/LC_MESSAGES/volto.po +645 -0
  45. package/locales/nl_BE/LC_MESSAGES/volto.po +645 -0
  46. package/locales/nn/LC_MESSAGES/volto.po +645 -0
  47. package/locales/no/LC_MESSAGES/volto.po +645 -0
  48. package/locales/pl/LC_MESSAGES/volto.po +645 -0
  49. package/locales/pt/LC_MESSAGES/volto.po +645 -0
  50. package/locales/pt_BR/LC_MESSAGES/volto.po +38 -123
  51. package/locales/rm/LC_MESSAGES/volto.po +645 -0
  52. package/locales/ro/LC_MESSAGES/volto.po +645 -0
  53. package/locales/ru/LC_MESSAGES/volto.po +645 -0
  54. package/locales/sk/LC_MESSAGES/volto.po +645 -0
  55. package/locales/sl/LC_MESSAGES/volto.po +645 -0
  56. package/locales/sm/LC_MESSAGES/volto.po +645 -0
  57. package/locales/sq/LC_MESSAGES/volto.po +645 -0
  58. package/locales/sr/LC_MESSAGES/volto.po +645 -0
  59. package/locales/sr_Cyrl/LC_MESSAGES/volto.po +645 -0
  60. package/locales/sr_Latn/LC_MESSAGES/volto.po +645 -0
  61. package/locales/sv/LC_MESSAGES/volto.po +645 -0
  62. package/locales/sw/LC_MESSAGES/volto.po +645 -0
  63. package/locales/ta/LC_MESSAGES/volto.po +645 -0
  64. package/locales/te/LC_MESSAGES/volto.po +645 -0
  65. package/locales/th/LC_MESSAGES/volto.po +645 -0
  66. package/locales/tl/LC_MESSAGES/volto.po +645 -0
  67. package/locales/to/LC_MESSAGES/volto.po +645 -0
  68. package/locales/tr/LC_MESSAGES/volto.po +645 -0
  69. package/locales/uk/LC_MESSAGES/volto.po +645 -0
  70. package/locales/vi/LC_MESSAGES/volto.po +645 -0
  71. package/locales/volto.pot +31 -116
  72. package/locales/zh_CN/LC_MESSAGES/volto.po +645 -0
  73. package/locales/zh_HK/LC_MESSAGES/volto.po +645 -0
  74. package/locales/zh_TW/LC_MESSAGES/volto.po +645 -0
  75. package/package.json +7 -4
  76. package/src/__mocks__/semantic-ui-react.ts +31 -0
  77. package/src/components/Blocks/Block/EditBlockWrapper.jsx +9 -3
  78. package/src/components/Blocks/Button/schema.js +12 -0
  79. package/src/components/Blocks/EventCalendar/Search/components/EventTemplate.tsx +1 -1
  80. package/src/components/Blocks/Image/Edit.jsx +9 -32
  81. package/src/components/Blocks/Image/View.jsx +9 -26
  82. package/src/components/Blocks/Image/adapter.js +28 -14
  83. package/src/components/Blocks/Image/adapter.test.js +156 -0
  84. package/src/components/Blocks/Image/schema.js +21 -7
  85. package/src/components/Blocks/Listing/DefaultTemplate.jsx +12 -6
  86. package/src/components/Blocks/Listing/GridTemplate.jsx +17 -7
  87. package/src/components/Blocks/Listing/ListingBody.jsx +4 -1
  88. package/src/components/Blocks/Listing/SummaryTemplate.jsx +17 -7
  89. package/src/components/Blocks/Maps/MapsSidebar.jsx +68 -0
  90. package/src/components/Blocks/Maps/View.jsx +37 -0
  91. package/src/components/Blocks/Maps/adapter.js +27 -0
  92. package/src/components/Blocks/Maps/adapter.test.js +63 -0
  93. package/src/components/Blocks/Maps/schema.js +42 -2
  94. package/src/components/Blocks/Separator/schema.js +12 -0
  95. package/src/components/Blocks/Teaser/DefaultBody.tsx +35 -6
  96. package/src/components/Blocks/Video/VideoSidebar.jsx +68 -0
  97. package/src/components/Blocks/Video/View.jsx +38 -0
  98. package/src/components/Blocks/Video/adapter.js +28 -0
  99. package/src/components/Blocks/Video/adapter.test.js +63 -0
  100. package/src/components/Blocks/Video/schema.js +42 -2
  101. package/src/components/Blocks/schema.ts +69 -0
  102. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +128 -0
  103. package/src/components/Breadcrumbs/Breadcrumbs.tsx +117 -0
  104. package/src/components/Caption/Caption.test.tsx +31 -0
  105. package/src/components/Caption/{Caption.jsx → Caption.tsx} +14 -21
  106. package/src/components/Footer/ColumnLinks.tsx +2 -2
  107. package/src/components/Footer/Footer.tsx +2 -2
  108. package/src/components/Footer/slots/Colophon.tsx +13 -1
  109. package/src/components/Footer/slots/CoreFooter.tsx +4 -2
  110. package/src/components/Footer/slots/FollowUsLogoAndLinks.tsx +12 -23
  111. package/src/components/Header/Header.tsx +3 -3
  112. package/src/components/LanguageSelector/LanguageSelector.tsx +91 -0
  113. package/src/components/MobileNavigation/MobileNavigation.jsx +11 -9
  114. package/src/components/Navigation/Navigation.test.tsx +176 -0
  115. package/src/components/Navigation/{Navigation.jsx → Navigation.tsx} +89 -42
  116. package/src/components/StickyMenu/MobileCarouselArrowButton.tsx +81 -0
  117. package/src/components/StickyMenu/MobileStickyMenu.tsx +76 -0
  118. package/src/components/Summary/DefaultSummary.tsx +10 -3
  119. package/src/components/Summary/EventSummary.tsx +10 -3
  120. package/src/components/Summary/FileSummary.tsx +10 -3
  121. package/src/components/Summary/NewsItemSummary.tsx +10 -3
  122. package/src/components/Summary/PersonSummary.tsx +10 -3
  123. package/src/components/Summary/Summary.stories.tsx +46 -30
  124. package/src/components/Tags/Tags.test.tsx +71 -0
  125. package/src/components/Tags/{Tags.jsx → Tags.tsx} +9 -25
  126. package/src/components/Theme/EventView.jsx +4 -4
  127. package/src/components/Theme/ImageView.jsx +8 -1
  128. package/src/components/Theme/NewsItemView.jsx +4 -4
  129. package/src/components/Theme/RenderBlocksV2.jsx +38 -15
  130. package/src/components/Widgets/ColorSwatch.stories.tsx +197 -0
  131. package/src/components/Widgets/ColorSwatch.test.tsx +188 -0
  132. package/src/components/Widgets/ColorSwatch.tsx +77 -39
  133. package/src/components/Widgets/ObjectList.tsx +37 -27
  134. package/src/components/Widgets/SoftTextWidget.tsx +129 -0
  135. package/src/components/Widgets/SoftTextareaWidget.tsx +118 -0
  136. package/src/components/Widgets/ThemeColorSwatch.tsx +5 -9
  137. package/src/config/blocks.tsx +83 -28
  138. package/src/config/classExtenders.ts +11 -10
  139. package/src/config/settings.ts +6 -0
  140. package/src/config/slots.ts +7 -0
  141. package/src/config/widgets.ts +5 -9
  142. package/src/customizations/volto/components/manage/Blocks/Maps/MapsSidebar.jsx +10 -0
  143. package/src/customizations/volto/components/manage/Blocks/Maps/View.jsx +10 -0
  144. package/src/customizations/volto/components/manage/Blocks/Video/VideoSidebar.jsx +10 -0
  145. package/src/customizations/volto/components/manage/Blocks/Video/View.jsx +10 -0
  146. package/src/customizations/volto/components/manage/DragDropList/DragDropList.jsx +263 -0
  147. package/src/customizations/volto/components/theme/LanguageSelector/LanguageSelector.tsx +10 -0
  148. package/src/helpers/styleDefinitions.test.tsx +30 -0
  149. package/src/helpers/styleDefinitions.ts +49 -0
  150. package/src/helpers/useLiveData.ts +7 -2
  151. package/src/index.ts +15 -0
  152. package/src/internalChecks.test.ts +94 -0
  153. package/src/primitives/Card/Card.stories.tsx +4 -1
  154. package/src/primitives/Card/Card.test.tsx +11 -33
  155. package/src/primitives/Card/Card.tsx +37 -44
  156. package/src/primitives/IconLinkList.tsx +53 -52
  157. package/src/primitives/LinkIconButton.tsx +52 -0
  158. package/src/reducers/errorContext.ts +14 -0
  159. package/src/reducers/index.ts +7 -0
  160. package/src/theme/_bgcolor-blocks-layout.scss +48 -46
  161. package/src/theme/_content.scss +12 -13
  162. package/src/theme/_export_import.scss +94 -0
  163. package/src/theme/_footer.scss +131 -64
  164. package/src/theme/_header.scss +25 -5
  165. package/src/theme/_insets.scss +1 -1
  166. package/src/theme/_layout.scss +41 -77
  167. package/src/theme/_mobile-sticky-menu.scss +92 -0
  168. package/src/theme/_search-page.scss +250 -0
  169. package/src/theme/_typo-custom.scss +24 -8
  170. package/src/theme/_variables.scss +40 -4
  171. package/src/theme/_widgets.scss +6 -17
  172. package/src/theme/blocks/_accordion.scss +11 -4
  173. package/src/theme/blocks/_form.scss +350 -0
  174. package/src/theme/blocks/_grid.scss +10 -77
  175. package/src/theme/blocks/_highlight.scss +10 -7
  176. package/src/theme/blocks/_image.scss +99 -184
  177. package/src/theme/blocks/_listing.scss +61 -128
  178. package/src/theme/blocks/_maps.scss +60 -34
  179. package/src/theme/blocks/_search.scss +3 -4
  180. package/src/theme/blocks/_table.scss +1 -0
  181. package/src/theme/blocks/_teaser.scss +7 -117
  182. package/src/theme/blocks/_toc.scss +2 -1
  183. package/src/theme/card.scss +136 -69
  184. package/src/theme/main.scss +4 -0
  185. package/src/theme/notfound.scss +2 -0
  186. package/src/theme/person.scss +7 -1
  187. package/src/theme/sticky-menu.scss +7 -5
  188. package/src/transforms/to6.ts +5 -49
  189. package/src/transforms/to8.test.js +201 -0
  190. package/src/transforms/to8.ts +109 -0
  191. package/src/types.d.ts +1 -0
  192. package/vitest.config.mjs +28 -3
  193. package/razzle.extend.js +0 -38
  194. package/src/components/Blocks/schema.js +0 -44
  195. package/src/components/Breadcrumbs/Breadcrumbs.jsx +0 -118
  196. package/src/components/Widgets/AlignWidget.tsx +0 -84
  197. package/src/components/Widgets/BlockAlignment.tsx +0 -88
  198. package/src/components/Widgets/BlockWidth.tsx +0 -101
  199. package/src/components/Widgets/Buttons.tsx +0 -167
  200. package/src/components/Widgets/Size.tsx +0 -78
@@ -14,7 +14,7 @@ import arrowRightSVG from '@plone/volto/icons/right-key.svg';
14
14
  import arrowLeftSVG from '@plone/volto/icons/left-key.svg';
15
15
  import { MobileNavigationToggler } from './MobileNavigationToggler';
16
16
  import { MobileToolsFooter } from './MobileToolsFooter';
17
-
17
+ import { useClient } from '@plone/volto/hooks/client/useClient';
18
18
  const messages = defineMessages({
19
19
  closeMobileMenu: {
20
20
  id: 'Close menu',
@@ -43,7 +43,7 @@ const MenuItem = ({
43
43
  pathname,
44
44
  }) => {
45
45
  const [isSubMenuOpen, setSubMenuOpen] = useState(false);
46
-
46
+ const isClient = useClient();
47
47
  const openSubMenu = useCallback((e) => {
48
48
  e.stopPropagation();
49
49
  setSubMenuOpen(true);
@@ -68,9 +68,10 @@ const MenuItem = ({
68
68
  );
69
69
  const formData = useSelector((state) => state.form.global);
70
70
 
71
- const has_intranet_header = !isEmpty(formData)
72
- ? formData.has_intranet_header
73
- : headerSettings?.has_intranet_header;
71
+ const has_intranet_header =
72
+ isClient && !isEmpty(formData)
73
+ ? formData.has_intranet_header
74
+ : headerSettings?.has_intranet_header;
74
75
 
75
76
  return (
76
77
  <li className={section.url === pathname ? 'current' : ''}>
@@ -154,16 +155,17 @@ const MobileNavigation = (props) => {
154
155
  const currentLang = useSelector((state) => state.intl.locale);
155
156
  const items = useSelector((state) => state.navigation.items || []);
156
157
  const history = useHistory();
157
-
158
+ const isClient = useClient();
158
159
  const headerSettings = useSelector(
159
160
  (state) =>
160
161
  state.content.data?.['@components']?.inherit?.['voltolighttheme.header']
161
162
  ?.data,
162
163
  );
163
164
  const formData = useSelector((state) => state.form.global);
164
- const has_intranet_header = !isEmpty(formData)
165
- ? formData.has_intranet_header
166
- : headerSettings?.has_intranet_header;
165
+ const has_intranet_header =
166
+ isClient && !isEmpty(formData)
167
+ ? formData.has_intranet_header
168
+ : headerSettings?.has_intranet_header;
167
169
 
168
170
  const Footer = props.MobileToolsFooter || MobileToolsFooter;
169
171
 
@@ -0,0 +1,176 @@
1
+ import React from 'react';
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { render, screen, fireEvent } from '@testing-library/react';
4
+ import configureStore from 'redux-mock-store';
5
+ import { Provider } from 'react-intl-redux';
6
+ import { MemoryRouter } from 'react-router-dom';
7
+
8
+ import Navigation from './Navigation';
9
+ import { getNavigation } from '@plone/volto/actions/navigation/navigation';
10
+ import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
11
+ import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
12
+
13
+ vi.mock('../../helpers/doesNodeContainClick', () => ({
14
+ __esModule: true,
15
+ default: () => false,
16
+ }));
17
+
18
+ vi.mock('@plone/volto/components/theme/Icon/Icon', () => ({
19
+ __esModule: true,
20
+ default: () => <span data-testid="icon" />,
21
+ }));
22
+
23
+ vi.mock('@plone/volto/components/theme/Navigation/NavItem', () => ({
24
+ __esModule: true,
25
+ default: ({ item }: { item: { title: string } }) => (
26
+ <span data-testid="nav-item">{item.title}</span>
27
+ ),
28
+ }));
29
+
30
+ vi.mock('@plone/volto/actions/navigation/navigation', () => ({
31
+ getNavigation: vi.fn(() => ({ type: 'GET_NAVIGATION' })),
32
+ }));
33
+
34
+ vi.mock('@plone/volto/helpers/Url/Url', () => ({
35
+ getBaseUrl: vi.fn((path: string) => path),
36
+ }));
37
+
38
+ vi.mock('@plone/volto/helpers/Utils/Utils', () => ({
39
+ hasApiExpander: vi.fn(() => false),
40
+ }));
41
+
42
+ vi.mock('@plone/volto/registry', () => ({
43
+ __esModule: true,
44
+ default: {
45
+ settings: {
46
+ navDepth: 3,
47
+ },
48
+ },
49
+ }));
50
+
51
+ vi.mock('@plone/volto/icons/clear.svg', () => ({
52
+ __esModule: true,
53
+ default: 'clear.svg',
54
+ }));
55
+
56
+ const mockStore = configureStore();
57
+
58
+ const mockedHasApiExpander = vi.mocked(hasApiExpander);
59
+ const mockedGetNavigation = vi.mocked(getNavigation);
60
+ const mockedGetBaseUrl = vi.mocked(getBaseUrl);
61
+
62
+ type TestNavigationItem = {
63
+ title: string;
64
+ url: string;
65
+ nav_title?: string;
66
+ items?: TestNavigationItem[];
67
+ };
68
+
69
+ const baseItems: TestNavigationItem[] = [
70
+ { title: 'About', url: '/about' },
71
+ { title: 'News', url: '/news' },
72
+ ];
73
+
74
+ const renderNavigation = ({
75
+ items = baseItems,
76
+ hasFatMenuHeader = false,
77
+ formFatMenu,
78
+ pathname = '/news',
79
+ }: {
80
+ items?: TestNavigationItem[];
81
+ hasFatMenuHeader?: boolean;
82
+ formFatMenu?: boolean;
83
+ pathname?: string;
84
+ } = {}) => {
85
+ const store = mockStore({
86
+ content: {
87
+ data: {
88
+ '@components': {
89
+ inherit: {
90
+ 'voltolighttheme.header': {
91
+ data: { has_fat_menu: hasFatMenuHeader },
92
+ },
93
+ },
94
+ },
95
+ },
96
+ },
97
+ form: {
98
+ global:
99
+ formFatMenu === undefined ? undefined : { has_fat_menu: formFatMenu },
100
+ },
101
+ intl: {
102
+ locale: 'en',
103
+ },
104
+ userSession: {
105
+ token: null,
106
+ },
107
+ navigation: {
108
+ items,
109
+ },
110
+ });
111
+
112
+ const renderResult = render(
113
+ <Provider store={store}>
114
+ <MemoryRouter>
115
+ <Navigation pathname={pathname} />
116
+ </MemoryRouter>
117
+ </Provider>,
118
+ );
119
+
120
+ return { store, ...renderResult };
121
+ };
122
+
123
+ describe('Navigation', () => {
124
+ beforeEach(() => {
125
+ vi.clearAllMocks();
126
+ mockedHasApiExpander.mockReturnValue(false);
127
+ mockedGetNavigation.mockReturnValue({ type: 'GET_NAVIGATION' });
128
+ mockedGetBaseUrl.mockImplementation((path: string) => path);
129
+ });
130
+
131
+ it('renders simple nav items when fat menu is disabled', () => {
132
+ renderNavigation({ hasFatMenuHeader: false });
133
+
134
+ const navItems = screen.getAllByTestId('nav-item');
135
+ expect(navItems).toHaveLength(baseItems.length);
136
+ expect(navItems[0]).toHaveTextContent('About');
137
+ });
138
+
139
+ it('expands and collapses the fat menu', () => {
140
+ const { container } = renderNavigation({
141
+ hasFatMenuHeader: true,
142
+ items: [
143
+ {
144
+ title: 'Sections',
145
+ url: '/sections',
146
+ items: [
147
+ {
148
+ title: 'First',
149
+ url: '/sections/first',
150
+ items: [],
151
+ },
152
+ ],
153
+ },
154
+ ],
155
+ });
156
+
157
+ const openButtons = screen.getAllByRole('button', {
158
+ name: 'Open menu',
159
+ });
160
+ fireEvent.click(openButtons[0]);
161
+ expect(container.querySelector('.submenu.active')).not.toBeNull();
162
+
163
+ const closeButton = screen.getByLabelText('Close menu');
164
+ fireEvent.click(closeButton);
165
+ expect(container.querySelector('.submenu.active')).toBeNull();
166
+ });
167
+
168
+ it('fetches navigation data when no expander is present', () => {
169
+ mockedGetBaseUrl.mockReturnValue('/base');
170
+ const { store } = renderNavigation({ pathname: '/blog' });
171
+
172
+ expect(mockedGetBaseUrl).toHaveBeenCalledWith('/blog');
173
+ expect(mockedGetNavigation).toHaveBeenCalledWith('/base', 3);
174
+ expect(store.getActions()).toEqual([{ type: 'GET_NAVIGATION' }]);
175
+ });
176
+ });
@@ -1,12 +1,9 @@
1
- // SemanticUI-free pre-@plone/components
2
-
3
- import React, { useState, useEffect, useRef } from 'react';
4
- import PropTypes from 'prop-types';
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
5
2
  import isEmpty from 'lodash/isEmpty';
6
3
  import { useDispatch, useSelector, shallowEqual } from 'react-redux';
7
4
  import { NavLink } from 'react-router-dom';
8
5
  import doesNodeContainClick from '../../helpers/doesNodeContainClick';
9
- import { useIntl, defineMessages, injectIntl } from 'react-intl';
6
+ import { useIntl, defineMessages } from 'react-intl';
10
7
  import cx from 'classnames';
11
8
  import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
12
9
  import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
@@ -28,41 +25,103 @@ const messages = defineMessages({
28
25
  },
29
26
  });
30
27
 
31
- const Navigation = ({ pathname }) => {
32
- const [desktopMenuOpen, setDesktopMenuOpen] = useState(null);
33
- const [currentOpenIndex, setCurrentOpenIndex] = useState(null);
34
- const navigation = useRef(null);
28
+ type NavigationItem = {
29
+ title: string;
30
+ nav_title?: string;
31
+ url: string;
32
+ items?: NavigationItem[];
33
+ };
34
+
35
+ type NavigationProps = {
36
+ pathname: string;
37
+ };
38
+
39
+ type HeaderSettings = {
40
+ has_fat_menu?: boolean;
41
+ };
42
+
43
+ type RootState = {
44
+ content: {
45
+ data?: {
46
+ '@components'?: {
47
+ inherit?: {
48
+ 'voltolighttheme.header'?: {
49
+ data?: HeaderSettings;
50
+ };
51
+ };
52
+ };
53
+ };
54
+ };
55
+ form: {
56
+ global?: {
57
+ has_fat_menu?: boolean;
58
+ [key: string]: unknown;
59
+ };
60
+ };
61
+ intl: {
62
+ locale: string;
63
+ };
64
+ userSession: {
65
+ token?: string | null;
66
+ };
67
+ navigation: {
68
+ items: NavigationItem[];
69
+ };
70
+ };
71
+
72
+ const Navigation = ({ pathname }: NavigationProps) => {
73
+ const [desktopMenuOpen, setDesktopMenuOpen] = useState<number | null>(null);
74
+ const [currentOpenIndex, setCurrentOpenIndex] = useState<number | null>(null);
75
+ const navigation = useRef<HTMLElement | null>(null);
35
76
  const dispatch = useDispatch();
36
77
  const intl = useIntl();
37
78
  const headerSettings = useSelector(
38
- (state) =>
79
+ (state: RootState) =>
39
80
  state.content.data?.['@components']?.inherit?.['voltolighttheme.header']
40
81
  ?.data,
41
82
  );
42
- const formData = useSelector((state) => state.form.global);
83
+ const formData = useSelector((state: RootState) => state.form.global);
43
84
 
44
- const has_fat_menu =
45
- !isEmpty(formData) && formData?.has_fat_menu
85
+ const hasFatMenuSetting =
86
+ !isEmpty(formData) && formData?.has_fat_menu !== undefined
46
87
  ? formData.has_fat_menu
47
88
  : headerSettings?.has_fat_menu;
89
+ const hasFatMenu = hasFatMenuSetting ?? false;
48
90
 
49
- const lang = useSelector((state) => state.intl.locale);
50
- const token = useSelector((state) => state.userSession.token, shallowEqual);
51
- const items = useSelector((state) => state.navigation.items, shallowEqual);
91
+ const lang = useSelector((state: RootState) => state.intl.locale);
92
+ const token = useSelector(
93
+ (state: RootState) => state.userSession.token,
94
+ shallowEqual,
95
+ );
96
+ const items = useSelector(
97
+ (state: RootState) => state.navigation.items,
98
+ shallowEqual,
99
+ );
52
100
 
53
- useEffect(() => {
54
- const handleClickOutside = (e) => {
55
- if (navigation.current && doesNodeContainClick(navigation.current, e))
101
+ const closeMenu = useCallback(() => {
102
+ setDesktopMenuOpen(null);
103
+ setCurrentOpenIndex(null);
104
+ }, [setDesktopMenuOpen, setCurrentOpenIndex]);
105
+
106
+ const handleClickOutside = useCallback(
107
+ (e: MouseEvent) => {
108
+ const target = e.target;
109
+ if (
110
+ (navigation.current && doesNodeContainClick(navigation.current, e)) ||
111
+ (target instanceof Element && target.parentElement === null)
112
+ )
56
113
  return;
57
114
  closeMenu();
58
- };
115
+ },
116
+ [closeMenu],
117
+ );
59
118
 
119
+ useEffect(() => {
60
120
  document.addEventListener('mousedown', handleClickOutside, false);
61
-
62
121
  return () => {
63
122
  document.removeEventListener('mousedown', handleClickOutside, false);
64
123
  };
65
- }, []);
124
+ }, [handleClickOutside]);
66
125
 
67
126
  useEffect(() => {
68
127
  if (!hasApiExpander('navigation', getBaseUrl(pathname))) {
@@ -70,11 +129,11 @@ const Navigation = ({ pathname }) => {
70
129
  }
71
130
  }, [pathname, token, dispatch]);
72
131
 
73
- const isActive = (url) => {
132
+ const isActive = (url: string) => {
74
133
  return (url === '' && pathname === '/') || (url !== '' && pathname === url);
75
134
  };
76
135
 
77
- const openMenu = (index) => {
136
+ const openMenu = (index: number) => {
78
137
  if (index === currentOpenIndex) {
79
138
  setDesktopMenuOpen(null);
80
139
  setCurrentOpenIndex(null);
@@ -84,14 +143,9 @@ const Navigation = ({ pathname }) => {
84
143
  }
85
144
  };
86
145
 
87
- const closeMenu = (index) => {
88
- setDesktopMenuOpen(null);
89
- setCurrentOpenIndex(null);
90
- };
91
-
92
146
  useEffect(() => {
93
- const handleEsc = (event) => {
94
- if (event.keyCode === 27) {
147
+ const handleEsc = (event: KeyboardEvent) => {
148
+ if (event.key === 'Escape' || event.keyCode === 27) {
95
149
  closeMenu();
96
150
  }
97
151
  };
@@ -100,6 +154,7 @@ const Navigation = ({ pathname }) => {
100
154
  return () => {
101
155
  window.removeEventListener('keydown', handleEsc);
102
156
  };
157
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
158
  }, []);
104
159
 
105
160
  return (
@@ -113,7 +168,7 @@ const Navigation = ({ pathname }) => {
113
168
  <ul className="desktop-menu">
114
169
  {items.map((item, index) => (
115
170
  <li key={item.url}>
116
- {has_fat_menu ? (
171
+ {hasFatMenu ? (
117
172
  <>
118
173
  <button
119
174
  onClick={() => openMenu(index)}
@@ -123,7 +178,7 @@ const Navigation = ({ pathname }) => {
123
178
  (!desktopMenuOpen && pathname === item.url),
124
179
  })}
125
180
  aria-label={intl.formatMessage(messages.openFatMenu)}
126
- aria-expanded={desktopMenuOpen === index ? true : false}
181
+ aria-expanded={desktopMenuOpen === index}
127
182
  >
128
183
  {item.title}
129
184
  </button>
@@ -212,12 +267,4 @@ const Navigation = ({ pathname }) => {
212
267
  );
213
268
  };
214
269
 
215
- Navigation.propTypes = {
216
- pathname: PropTypes.string.isRequired,
217
- };
218
-
219
- Navigation.defaultProps = {
220
- token: null,
221
- };
222
-
223
- export default injectIntl(Navigation);
270
+ export default Navigation;
@@ -0,0 +1,81 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { EmblaCarouselType } from 'embla-carousel';
3
+ import type { ComponentPropsWithRef } from 'react';
4
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
5
+ import rightArrowSVG from '@plone/volto/icons/right-key.svg';
6
+ import leftArrowSVG from '@plone/volto/icons/left-key.svg';
7
+
8
+ type UsePrevNextButtonsType = {
9
+ prevBtnDisabled: boolean;
10
+ nextBtnDisabled: boolean;
11
+ onPrevButtonClick: () => void;
12
+ onNextButtonClick: () => void;
13
+ };
14
+
15
+ export const usePrevNextButtons = (
16
+ emblaApi: EmblaCarouselType | undefined,
17
+ ): UsePrevNextButtonsType => {
18
+ const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
19
+ const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
20
+
21
+ const onPrevButtonClick = useCallback(() => {
22
+ if (!emblaApi) return;
23
+ emblaApi.scrollPrev();
24
+ }, [emblaApi]);
25
+
26
+ const onNextButtonClick = useCallback(() => {
27
+ if (!emblaApi) return;
28
+ emblaApi.scrollNext();
29
+ }, [emblaApi]);
30
+
31
+ const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
32
+ setPrevBtnDisabled(!emblaApi.canScrollPrev());
33
+ setNextBtnDisabled(!emblaApi.canScrollNext());
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ if (!emblaApi) return;
38
+
39
+ onSelect(emblaApi);
40
+ emblaApi.on('reInit', onSelect).on('select', onSelect);
41
+ }, [emblaApi, onSelect]);
42
+
43
+ return {
44
+ prevBtnDisabled,
45
+ nextBtnDisabled,
46
+ onPrevButtonClick,
47
+ onNextButtonClick,
48
+ };
49
+ };
50
+
51
+ type PropType = ComponentPropsWithRef<'button'>;
52
+
53
+ export const PrevButton: React.FC<PropType> = (props) => {
54
+ const { children, ...restProps } = props;
55
+
56
+ return (
57
+ <button
58
+ className="embla__button embla__button--prev"
59
+ type="button"
60
+ {...restProps}
61
+ >
62
+ <Icon name={leftArrowSVG} size="48px" />
63
+ {children}
64
+ </button>
65
+ );
66
+ };
67
+
68
+ export const NextButton: React.FC<PropType> = (props) => {
69
+ const { children, ...restProps } = props;
70
+
71
+ return (
72
+ <button
73
+ className="embla__button embla__button--next"
74
+ type="button"
75
+ {...restProps}
76
+ >
77
+ <Icon name={rightArrowSVG} size="48px" />
78
+ {children}
79
+ </button>
80
+ );
81
+ };
@@ -0,0 +1,76 @@
1
+ import { useLiveData } from '@kitconcept/volto-light-theme/helpers/useLiveData';
2
+ import { IconLinkListTemplate } from '@kitconcept/volto-light-theme/primitives/IconLinkList';
3
+ import type { StickyMenuSettings } from '../../types';
4
+ import useEmblaCarousel from 'embla-carousel-react';
5
+ import type { EmblaOptionsType } from 'embla-carousel';
6
+
7
+ import {
8
+ usePrevNextButtons,
9
+ PrevButton,
10
+ NextButton,
11
+ } from './MobileCarouselArrowButton';
12
+ import type { Content } from '@plone/types';
13
+
14
+ const MobileStickyMenu = ({ content }: { content: Content }) => {
15
+ const options: EmblaOptionsType = {};
16
+ const [emblaRef, emblaApi] = useEmblaCarousel(options);
17
+ const {
18
+ prevBtnDisabled,
19
+ nextBtnDisabled,
20
+ onPrevButtonClick,
21
+ onNextButtonClick,
22
+ } = usePrevNextButtons(emblaApi);
23
+ const showMobileStickyMenu = useLiveData<StickyMenuSettings['sticky_menu']>(
24
+ content,
25
+ 'kitconcept.sticky_menu',
26
+ 'enable_mobile_sticky_menu',
27
+ );
28
+ const menuData = useLiveData<StickyMenuSettings['sticky_menu']>(
29
+ content,
30
+ 'kitconcept.sticky_menu',
31
+ 'sticky_menu',
32
+ );
33
+
34
+ const sticky_menu_color = useLiveData<
35
+ StickyMenuSettings['sticky_menu_color']
36
+ >(content, 'kitconcept.sticky_menu', 'sticky_menu_color');
37
+
38
+ const sticky_menu_foreground_color = useLiveData<
39
+ StickyMenuSettings['sticky_menu_foreground_color']
40
+ >(content, 'kitconcept.sticky_menu', 'sticky_menu_foreground_color');
41
+
42
+ if (!showMobileStickyMenu) {
43
+ return null;
44
+ }
45
+
46
+ return (
47
+ <div
48
+ className="mobile-sticky-menu"
49
+ role="navigation"
50
+ aria-label="Mobile Sticky menu"
51
+ style={
52
+ {
53
+ '--sticky-menu-color': sticky_menu_color,
54
+ '--sticky-menu-foreground-color': sticky_menu_foreground_color,
55
+ } as React.CSSProperties
56
+ }
57
+ >
58
+ <section className="embla">
59
+ <div className="embla__viewport" ref={emblaRef}>
60
+ <div className="embla__container">
61
+ {menuData &&
62
+ menuData.map((item) => (
63
+ <ul className="embla__slide" key={item['@id']}>
64
+ <IconLinkListTemplate item={item} />
65
+ </ul>
66
+ ))}
67
+ </div>
68
+ </div>
69
+ <PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />
70
+ <NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
71
+ </section>
72
+ </div>
73
+ );
74
+ };
75
+
76
+ export default MobileStickyMenu;
@@ -4,6 +4,7 @@ import { smartTextRenderer } from '../../helpers/smartText';
4
4
 
5
5
  export type DefaultSummaryProps = {
6
6
  item: Partial<ObjectBrowserItem>;
7
+ LinkToItem?: React.ElementType;
7
8
  HeadingTag?: React.ElementType;
8
9
  a11yLabelId?: string;
9
10
  hide_description?: boolean;
@@ -14,14 +15,20 @@ export type SummaryComponentType = React.ComponentType<DefaultSummaryProps> & {
14
15
  };
15
16
 
16
17
  const DefaultSummary = (props: DefaultSummaryProps) => {
17
- const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
18
+ const {
19
+ item,
20
+ LinkToItem = React.Fragment,
21
+ HeadingTag = 'div',
22
+ a11yLabelId,
23
+ hide_description,
24
+ } = props;
18
25
  return (
19
26
  <>
20
27
  {item?.head_title && <div className="headline">{item.head_title}</div>}
21
28
  <HeadingTag className="title" id={a11yLabelId}>
22
- {item.title ? item.title : item.id}
29
+ <LinkToItem>{item.title ? item.title : item.id}</LinkToItem>
23
30
  </HeadingTag>
24
- {!hide_description && (
31
+ {!hide_description && item?.description !== '' && (
25
32
  <p className="description">{smartTextRenderer(item.description)}</p>
26
33
  )}
27
34
  </>
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  import {
2
3
  parseDateFromCatalog,
3
4
  formatDateRange,
@@ -7,7 +8,13 @@ import type { DefaultSummaryProps } from './DefaultSummary';
7
8
  import { smartTextRenderer } from '../../helpers/smartText';
8
9
 
9
10
  const EventSummary = (props: DefaultSummaryProps) => {
10
- const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
11
+ const {
12
+ item,
13
+ LinkToItem = React.Fragment,
14
+ HeadingTag = 'div',
15
+ a11yLabelId,
16
+ hide_description,
17
+ } = props;
11
18
  const start = parseDateFromCatalog(item.start);
12
19
  const end = parseDateFromCatalog(item.end);
13
20
  const headline = [
@@ -29,9 +36,9 @@ const EventSummary = (props: DefaultSummaryProps) => {
29
36
  <>
30
37
  {headline.length ? <div className="headline">{headline}</div> : null}
31
38
  <HeadingTag className="title" id={a11yLabelId}>
32
- {item.title ? item.title : item.id}
39
+ <LinkToItem>{item.title ? item.title : item.id}</LinkToItem>
33
40
  </HeadingTag>
34
- {!hide_description && (
41
+ {!hide_description && item?.description !== '' && (
35
42
  <p className="description">{smartTextRenderer(item.description)}</p>
36
43
  )}
37
44
  </>
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  import FileType from '@kitconcept/volto-light-theme/helpers/Filetype';
2
3
  import type { DefaultSummaryProps } from './DefaultSummary';
3
4
  import { smartTextRenderer } from '../../helpers/smartText';
@@ -21,15 +22,21 @@ const FileHeadline = (props: { item: any }) => {
21
22
  };
22
23
 
23
24
  const FileSummary = (props: DefaultSummaryProps) => {
24
- const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
25
+ const {
26
+ item,
27
+ LinkToItem = React.Fragment,
28
+ HeadingTag = 'div',
29
+ a11yLabelId,
30
+ hide_description,
31
+ } = props;
25
32
 
26
33
  return (
27
34
  <>
28
35
  <FileHeadline item={item} />
29
36
  <HeadingTag className="title" id={a11yLabelId}>
30
- {item.title ? item.title : item.id}
37
+ <LinkToItem>{item.title ? item.title : item.id}</LinkToItem>
31
38
  </HeadingTag>
32
- {!hide_description && (
39
+ {!hide_description && item?.description !== '' && (
33
40
  <p className="description">{smartTextRenderer(item.description)}</p>
34
41
  )}
35
42
  </>