@kitconcept/volto-light-theme 8.0.0-alpha.2 → 8.0.0-alpha.21

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 (101) hide show
  1. package/.changelog.draft +3 -4
  2. package/CHANGELOG.md +232 -0
  3. package/locales/de/LC_MESSAGES/volto.po +30 -115
  4. package/locales/en/LC_MESSAGES/volto.po +30 -115
  5. package/locales/es/LC_MESSAGES/volto.po +31 -116
  6. package/locales/eu/LC_MESSAGES/volto.po +58 -124
  7. package/locales/pt_BR/LC_MESSAGES/volto.po +38 -123
  8. package/locales/volto.pot +31 -116
  9. package/package.json +7 -4
  10. package/src/__mocks__/semantic-ui-react.ts +31 -0
  11. package/src/components/Blocks/Block/Edit.jsx +14 -6
  12. package/src/components/Blocks/Block/EditBlockWrapper.jsx +9 -3
  13. package/src/components/Blocks/Block/ErrorBoundary.test.tsx +55 -0
  14. package/src/components/Blocks/Block/ErrorBoundary.tsx +92 -0
  15. package/src/components/Blocks/Block/ErrorBoundaryMessage.tsx +66 -0
  16. package/src/components/Blocks/EventCalendar/Search/components/EventTemplate.tsx +1 -1
  17. package/src/components/Blocks/Image/Edit.jsx +1 -0
  18. package/src/components/Blocks/Listing/DefaultTemplate.jsx +12 -6
  19. package/src/components/Blocks/Listing/GridTemplate.jsx +16 -7
  20. package/src/components/Blocks/Listing/ListingBody.jsx +4 -1
  21. package/src/components/Blocks/Listing/SummaryTemplate.jsx +16 -7
  22. package/src/components/Blocks/Teaser/DefaultBody.tsx +25 -5
  23. package/src/components/Blocks/schema.ts +69 -0
  24. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +128 -0
  25. package/src/components/Breadcrumbs/Breadcrumbs.tsx +117 -0
  26. package/src/components/Caption/Caption.test.tsx +31 -0
  27. package/src/components/Caption/{Caption.jsx → Caption.tsx} +14 -21
  28. package/src/components/Footer/ColumnLinks.tsx +2 -2
  29. package/src/components/Footer/slots/Colophon.tsx +13 -1
  30. package/src/components/Footer/slots/CoreFooter.tsx +4 -2
  31. package/src/components/Header/Header.tsx +3 -3
  32. package/src/components/LanguageSelector/LanguageSelector.tsx +91 -0
  33. package/src/components/MobileNavigation/MobileNavigation.jsx +11 -9
  34. package/src/components/Navigation/Navigation.test.tsx +176 -0
  35. package/src/components/Navigation/{Navigation.jsx → Navigation.tsx} +77 -37
  36. package/src/components/StickyMenu/MobileCarouselArrowButton.tsx +81 -0
  37. package/src/components/StickyMenu/MobileStickyMenu.tsx +76 -0
  38. package/src/components/Summary/DefaultSummary.tsx +10 -3
  39. package/src/components/Summary/EventSummary.tsx +10 -3
  40. package/src/components/Summary/FileSummary.tsx +10 -3
  41. package/src/components/Summary/NewsItemSummary.tsx +10 -3
  42. package/src/components/Summary/PersonSummary.tsx +10 -3
  43. package/src/components/Summary/Summary.stories.tsx +46 -30
  44. package/src/components/Tags/Tags.test.tsx +71 -0
  45. package/src/components/Tags/{Tags.jsx → Tags.tsx} +9 -25
  46. package/src/components/Theme/EventView.jsx +4 -4
  47. package/src/components/Theme/NewsItemView.jsx +4 -4
  48. package/src/components/Theme/RenderBlocks.jsx +45 -37
  49. package/src/components/Theme/RenderBlocksV2.jsx +51 -20
  50. package/src/components/Widgets/ColorSwatch.stories.tsx +197 -0
  51. package/src/components/Widgets/ColorSwatch.test.tsx +188 -0
  52. package/src/components/Widgets/ColorSwatch.tsx +77 -39
  53. package/src/components/Widgets/SoftTextWidget.tsx +129 -0
  54. package/src/components/Widgets/SoftTextareaWidget.tsx +118 -0
  55. package/src/components/Widgets/ThemeColorSwatch.tsx +5 -9
  56. package/src/config/blocks.tsx +21 -29
  57. package/src/config/slots.ts +7 -0
  58. package/src/config/widgets.ts +5 -9
  59. package/src/customizations/volto/components/manage/DragDropList/DragDropList.jsx +263 -0
  60. package/src/customizations/volto/components/theme/LanguageSelector/LanguageSelector.tsx +10 -0
  61. package/src/helpers/styleDefinitions.test.tsx +30 -0
  62. package/src/helpers/styleDefinitions.ts +49 -0
  63. package/src/internalChecks.test.ts +94 -0
  64. package/src/primitives/Card/Card.stories.tsx +4 -1
  65. package/src/primitives/Card/Card.test.tsx +11 -33
  66. package/src/primitives/Card/Card.tsx +33 -43
  67. package/src/primitives/IconLinkList.tsx +53 -52
  68. package/src/theme/_bgcolor-blocks-layout.scss +43 -45
  69. package/src/theme/_content.scss +12 -13
  70. package/src/theme/_export_import.scss +94 -0
  71. package/src/theme/_footer.scss +64 -19
  72. package/src/theme/_header.scss +21 -4
  73. package/src/theme/_insets.scss +1 -1
  74. package/src/theme/_layout.scss +34 -15
  75. package/src/theme/_mobile-sticky-menu.scss +92 -0
  76. package/src/theme/_search-page.scss +249 -0
  77. package/src/theme/_typo-custom.scss +16 -5
  78. package/src/theme/_variables.scss +19 -4
  79. package/src/theme/_widgets.scss +15 -27
  80. package/src/theme/blocks/_accordion.scss +11 -4
  81. package/src/theme/blocks/_grid.scss +9 -77
  82. package/src/theme/blocks/_listing.scss +60 -126
  83. package/src/theme/blocks/_search.scss +3 -4
  84. package/src/theme/blocks/_table.scss +1 -0
  85. package/src/theme/blocks/_teaser.scss +7 -117
  86. package/src/theme/blocks/error-boundary.scss +11 -0
  87. package/src/theme/card.scss +107 -70
  88. package/src/theme/main.scss +5 -0
  89. package/src/theme/notfound.scss +27 -0
  90. package/src/theme/person.scss +28 -12
  91. package/src/theme/sticky-menu.scss +7 -5
  92. package/src/types.d.ts +1 -0
  93. package/vitest.config.mjs +4 -0
  94. package/razzle.extend.js +0 -38
  95. package/src/components/Blocks/schema.js +0 -44
  96. package/src/components/Breadcrumbs/Breadcrumbs.jsx +0 -118
  97. package/src/components/Widgets/AlignWidget.tsx +0 -84
  98. package/src/components/Widgets/BlockAlignment.tsx +0 -88
  99. package/src/components/Widgets/BlockWidth.tsx +0 -101
  100. package/src/components/Widgets/Buttons.tsx +0 -144
  101. package/src/components/Widgets/Size.tsx +0 -78
@@ -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
1
  import React, { useState, useEffect, useRef } from 'react';
4
- import PropTypes from 'prop-types';
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,31 +25,87 @@ 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;
90
+
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
+ );
48
100
 
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);
101
+ const closeMenu = () => {
102
+ setDesktopMenuOpen(null);
103
+ setCurrentOpenIndex(null);
104
+ };
52
105
 
53
106
  useEffect(() => {
54
- const handleClickOutside = (e) => {
55
- if (navigation.current && doesNodeContainClick(navigation.current, e))
107
+ const handleClickOutside = (event: MouseEvent) => {
108
+ if (navigation.current && doesNodeContainClick(navigation.current, event))
56
109
  return;
57
110
  closeMenu();
58
111
  };
@@ -70,11 +123,11 @@ const Navigation = ({ pathname }) => {
70
123
  }
71
124
  }, [pathname, token, dispatch]);
72
125
 
73
- const isActive = (url) => {
126
+ const isActive = (url: string) => {
74
127
  return (url === '' && pathname === '/') || (url !== '' && pathname === url);
75
128
  };
76
129
 
77
- const openMenu = (index) => {
130
+ const openMenu = (index: number) => {
78
131
  if (index === currentOpenIndex) {
79
132
  setDesktopMenuOpen(null);
80
133
  setCurrentOpenIndex(null);
@@ -84,14 +137,9 @@ const Navigation = ({ pathname }) => {
84
137
  }
85
138
  };
86
139
 
87
- const closeMenu = (index) => {
88
- setDesktopMenuOpen(null);
89
- setCurrentOpenIndex(null);
90
- };
91
-
92
140
  useEffect(() => {
93
- const handleEsc = (event) => {
94
- if (event.keyCode === 27) {
141
+ const handleEsc = (event: KeyboardEvent) => {
142
+ if (event.key === 'Escape' || event.keyCode === 27) {
95
143
  closeMenu();
96
144
  }
97
145
  };
@@ -113,7 +161,7 @@ const Navigation = ({ pathname }) => {
113
161
  <ul className="desktop-menu">
114
162
  {items.map((item, index) => (
115
163
  <li key={item.url}>
116
- {has_fat_menu ? (
164
+ {hasFatMenu ? (
117
165
  <>
118
166
  <button
119
167
  onClick={() => openMenu(index)}
@@ -123,7 +171,7 @@ const Navigation = ({ pathname }) => {
123
171
  (!desktopMenuOpen && pathname === item.url),
124
172
  })}
125
173
  aria-label={intl.formatMessage(messages.openFatMenu)}
126
- aria-expanded={desktopMenuOpen === index ? true : false}
174
+ aria-expanded={desktopMenuOpen === index}
127
175
  >
128
176
  {item.title}
129
177
  </button>
@@ -212,12 +260,4 @@ const Navigation = ({ pathname }) => {
212
260
  );
213
261
  };
214
262
 
215
- Navigation.propTypes = {
216
- pathname: PropTypes.string.isRequired,
217
- };
218
-
219
- Navigation.defaultProps = {
220
- token: null,
221
- };
222
-
223
- export default injectIntl(Navigation);
263
+ 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
  </>
@@ -1,10 +1,17 @@
1
+ import * as React from 'react';
1
2
  import { parseDateFromCatalog } from '@kitconcept/volto-light-theme/helpers/dates';
2
3
  import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
3
4
  import type { DefaultSummaryProps } from './DefaultSummary';
4
5
  import { smartTextRenderer } from '../../helpers/smartText';
5
6
 
6
7
  const NewsItemSummary = (props: DefaultSummaryProps) => {
7
- const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
8
+ const {
9
+ item,
10
+ LinkToItem = React.Fragment,
11
+ HeadingTag = 'div',
12
+ a11yLabelId,
13
+ hide_description,
14
+ } = props;
8
15
 
9
16
  const effective = parseDateFromCatalog(item.effective);
10
17
  const headline = [
@@ -31,9 +38,9 @@ const NewsItemSummary = (props: DefaultSummaryProps) => {
31
38
  <>
32
39
  {headline.length ? <div className="headline">{headline}</div> : null}
33
40
  <HeadingTag className="title" id={a11yLabelId}>
34
- {item.title ? item.title : item.id}
41
+ <LinkToItem>{item.title ? item.title : item.id}</LinkToItem>
35
42
  </HeadingTag>
36
- {!hide_description && (
43
+ {!hide_description && item?.description !== '' && (
37
44
  <p className="description">{smartTextRenderer(item.description)}</p>
38
45
  )}
39
46
  </>
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  import Icon from '@plone/volto/components/theme/Icon/Icon';
2
3
  import mailSVG from '@plone/volto/icons/email.svg';
3
4
  import locationSVG from '@plone/volto/icons/map.svg';
@@ -22,16 +23,22 @@ const messages = defineMessages({
22
23
  });
23
24
 
24
25
  const PersonSummary = (props: DefaultSummaryProps) => {
25
- const { item, HeadingTag = 'h3', a11yLabelId, hide_description } = props;
26
+ const {
27
+ item,
28
+ LinkToItem = React.Fragment,
29
+ HeadingTag = 'div',
30
+ a11yLabelId,
31
+ hide_description,
32
+ } = props;
26
33
  const intl = useIntl();
27
34
 
28
35
  return (
29
36
  <>
30
37
  {item?.head_title && <div className="headline">{item.head_title}</div>}
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