@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.
- package/.changelog.draft +3 -4
- package/CHANGELOG.md +232 -0
- package/locales/de/LC_MESSAGES/volto.po +30 -115
- package/locales/en/LC_MESSAGES/volto.po +30 -115
- package/locales/es/LC_MESSAGES/volto.po +31 -116
- package/locales/eu/LC_MESSAGES/volto.po +58 -124
- package/locales/pt_BR/LC_MESSAGES/volto.po +38 -123
- package/locales/volto.pot +31 -116
- package/package.json +7 -4
- package/src/__mocks__/semantic-ui-react.ts +31 -0
- package/src/components/Blocks/Block/Edit.jsx +14 -6
- package/src/components/Blocks/Block/EditBlockWrapper.jsx +9 -3
- package/src/components/Blocks/Block/ErrorBoundary.test.tsx +55 -0
- package/src/components/Blocks/Block/ErrorBoundary.tsx +92 -0
- package/src/components/Blocks/Block/ErrorBoundaryMessage.tsx +66 -0
- package/src/components/Blocks/EventCalendar/Search/components/EventTemplate.tsx +1 -1
- package/src/components/Blocks/Image/Edit.jsx +1 -0
- package/src/components/Blocks/Listing/DefaultTemplate.jsx +12 -6
- package/src/components/Blocks/Listing/GridTemplate.jsx +16 -7
- package/src/components/Blocks/Listing/ListingBody.jsx +4 -1
- package/src/components/Blocks/Listing/SummaryTemplate.jsx +16 -7
- package/src/components/Blocks/Teaser/DefaultBody.tsx +25 -5
- package/src/components/Blocks/schema.ts +69 -0
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +128 -0
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +117 -0
- package/src/components/Caption/Caption.test.tsx +31 -0
- package/src/components/Caption/{Caption.jsx → Caption.tsx} +14 -21
- package/src/components/Footer/ColumnLinks.tsx +2 -2
- package/src/components/Footer/slots/Colophon.tsx +13 -1
- package/src/components/Footer/slots/CoreFooter.tsx +4 -2
- package/src/components/Header/Header.tsx +3 -3
- package/src/components/LanguageSelector/LanguageSelector.tsx +91 -0
- package/src/components/MobileNavigation/MobileNavigation.jsx +11 -9
- package/src/components/Navigation/Navigation.test.tsx +176 -0
- package/src/components/Navigation/{Navigation.jsx → Navigation.tsx} +77 -37
- package/src/components/StickyMenu/MobileCarouselArrowButton.tsx +81 -0
- package/src/components/StickyMenu/MobileStickyMenu.tsx +76 -0
- package/src/components/Summary/DefaultSummary.tsx +10 -3
- package/src/components/Summary/EventSummary.tsx +10 -3
- package/src/components/Summary/FileSummary.tsx +10 -3
- package/src/components/Summary/NewsItemSummary.tsx +10 -3
- package/src/components/Summary/PersonSummary.tsx +10 -3
- package/src/components/Summary/Summary.stories.tsx +46 -30
- package/src/components/Tags/Tags.test.tsx +71 -0
- package/src/components/Tags/{Tags.jsx → Tags.tsx} +9 -25
- package/src/components/Theme/EventView.jsx +4 -4
- package/src/components/Theme/NewsItemView.jsx +4 -4
- package/src/components/Theme/RenderBlocks.jsx +45 -37
- package/src/components/Theme/RenderBlocksV2.jsx +51 -20
- package/src/components/Widgets/ColorSwatch.stories.tsx +197 -0
- package/src/components/Widgets/ColorSwatch.test.tsx +188 -0
- package/src/components/Widgets/ColorSwatch.tsx +77 -39
- package/src/components/Widgets/SoftTextWidget.tsx +129 -0
- package/src/components/Widgets/SoftTextareaWidget.tsx +118 -0
- package/src/components/Widgets/ThemeColorSwatch.tsx +5 -9
- package/src/config/blocks.tsx +21 -29
- package/src/config/slots.ts +7 -0
- package/src/config/widgets.ts +5 -9
- package/src/customizations/volto/components/manage/DragDropList/DragDropList.jsx +263 -0
- package/src/customizations/volto/components/theme/LanguageSelector/LanguageSelector.tsx +10 -0
- package/src/helpers/styleDefinitions.test.tsx +30 -0
- package/src/helpers/styleDefinitions.ts +49 -0
- package/src/internalChecks.test.ts +94 -0
- package/src/primitives/Card/Card.stories.tsx +4 -1
- package/src/primitives/Card/Card.test.tsx +11 -33
- package/src/primitives/Card/Card.tsx +33 -43
- package/src/primitives/IconLinkList.tsx +53 -52
- package/src/theme/_bgcolor-blocks-layout.scss +43 -45
- package/src/theme/_content.scss +12 -13
- package/src/theme/_export_import.scss +94 -0
- package/src/theme/_footer.scss +64 -19
- package/src/theme/_header.scss +21 -4
- package/src/theme/_insets.scss +1 -1
- package/src/theme/_layout.scss +34 -15
- package/src/theme/_mobile-sticky-menu.scss +92 -0
- package/src/theme/_search-page.scss +249 -0
- package/src/theme/_typo-custom.scss +16 -5
- package/src/theme/_variables.scss +19 -4
- package/src/theme/_widgets.scss +15 -27
- package/src/theme/blocks/_accordion.scss +11 -4
- package/src/theme/blocks/_grid.scss +9 -77
- package/src/theme/blocks/_listing.scss +60 -126
- package/src/theme/blocks/_search.scss +3 -4
- package/src/theme/blocks/_table.scss +1 -0
- package/src/theme/blocks/_teaser.scss +7 -117
- package/src/theme/blocks/error-boundary.scss +11 -0
- package/src/theme/card.scss +107 -70
- package/src/theme/main.scss +5 -0
- package/src/theme/notfound.scss +27 -0
- package/src/theme/person.scss +28 -12
- package/src/theme/sticky-menu.scss +7 -5
- package/src/types.d.ts +1 -0
- package/vitest.config.mjs +4 -0
- package/razzle.extend.js +0 -38
- package/src/components/Blocks/schema.js +0 -44
- package/src/components/Breadcrumbs/Breadcrumbs.jsx +0 -118
- package/src/components/Widgets/AlignWidget.tsx +0 -84
- package/src/components/Widgets/BlockAlignment.tsx +0 -88
- package/src/components/Widgets/BlockWidth.tsx +0 -101
- package/src/components/Widgets/Buttons.tsx +0 -144
- 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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
101
|
+
const closeMenu = () => {
|
|
102
|
+
setDesktopMenuOpen(null);
|
|
103
|
+
setCurrentOpenIndex(null);
|
|
104
|
+
};
|
|
52
105
|
|
|
53
106
|
useEffect(() => {
|
|
54
|
-
const handleClickOutside = (
|
|
55
|
-
if (navigation.current && doesNodeContainClick(navigation.current,
|
|
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
|
-
{
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|