@plone/volto 19.3.1 → 19.4.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 +11 -0
- package/package.json +5 -5
- package/src/components/manage/Toolbar/Toolbar.jsx +2 -3
- package/src/components/manage/Toolbar/UserAvatar.test.tsx +72 -0
- package/src/components/manage/Toolbar/UserAvatar.tsx +27 -0
- package/src/components/theme/Search/Search.jsx +9 -1
- package/types/components/manage/Toolbar/UserAvatar.d.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,17 @@ myst:
|
|
|
17
17
|
|
|
18
18
|
<!-- towncrier release notes start -->
|
|
19
19
|
|
|
20
|
+
## 19.4.0 (2026-09-03)
|
|
21
|
+
|
|
22
|
+
### Feature
|
|
23
|
+
|
|
24
|
+
- Added a `UserAvatar` component, used by the toolbar to render the signed-in user's avatar. Add-ons can now change that avatar by shadowing this one small component, instead of shadowing the whole `Toolbar`. @ericof [#8404](https://github.com/plone/volto/issues/8404)
|
|
25
|
+
- Adding quanta support with semanticUI fallback for seach container. @TimoBroeskamp [#8411](https://github.com/plone/volto/issues/8411)
|
|
26
|
+
|
|
27
|
+
### Bugfix
|
|
28
|
+
|
|
29
|
+
- Fixed cursor position lost when clicking on a Slate block in edit, causing the cursor to jump to the start of the text. @wesleybl [#8402](https://github.com/plone/volto/issues/8402)
|
|
30
|
+
|
|
20
31
|
## 19.3.1 (2026-08-26)
|
|
21
32
|
|
|
22
33
|
### Bugfix
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "19.
|
|
12
|
+
"version": "19.4.0",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git@github.com:plone/volto.git"
|
|
@@ -175,10 +175,10 @@
|
|
|
175
175
|
"url": "^0.11.3",
|
|
176
176
|
"use-deep-compare-effect": "1.8.1",
|
|
177
177
|
"uuid": "^14.0.0",
|
|
178
|
-
"@plone/
|
|
179
|
-
"@plone/volto-slate": "19.0.4",
|
|
178
|
+
"@plone/registry": "3.0.1",
|
|
180
179
|
"@plone/scripts": "4.0.1",
|
|
181
|
-
"@plone/
|
|
180
|
+
"@plone/components": "4.2.1",
|
|
181
|
+
"@plone/volto-slate": "19.0.5"
|
|
182
182
|
},
|
|
183
183
|
"devDependencies": {
|
|
184
184
|
"@babel/core": "^7.28.5",
|
|
@@ -287,9 +287,9 @@
|
|
|
287
287
|
"webpack-dev-server": "^5.2.4",
|
|
288
288
|
"webpack-node-externals": "3.0.0",
|
|
289
289
|
"@plone/babel-preset-razzle": "^1.0.1",
|
|
290
|
+
"@plone/razzle": "1.0.1",
|
|
290
291
|
"@plone/razzle-dev-utils": "1.0.0",
|
|
291
292
|
"@plone/volto-coresandbox": "1.0.0",
|
|
292
|
-
"@plone/razzle": "1.0.1",
|
|
293
293
|
"@plone/types": "2.0.1"
|
|
294
294
|
},
|
|
295
295
|
"scripts": {
|
|
@@ -39,9 +39,9 @@ import unlockSVG from '@plone/volto/icons/unlock.svg';
|
|
|
39
39
|
import folderSVG from '@plone/volto/icons/folder.svg';
|
|
40
40
|
import addSVG from '@plone/volto/icons/add-document.svg';
|
|
41
41
|
import moreSVG from '@plone/volto/icons/more.svg';
|
|
42
|
-
import userSVG from '@plone/volto/icons/user.svg';
|
|
43
42
|
import backSVG from '@plone/volto/icons/back.svg';
|
|
44
43
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
44
|
+
import UserAvatar from './UserAvatar';
|
|
45
45
|
|
|
46
46
|
const messages = defineMessages({
|
|
47
47
|
edit: {
|
|
@@ -695,8 +695,7 @@ class Toolbar extends Component {
|
|
|
695
695
|
tabIndex={0}
|
|
696
696
|
id="toolbar-personal"
|
|
697
697
|
>
|
|
698
|
-
<
|
|
699
|
-
name={userSVG}
|
|
698
|
+
<UserAvatar
|
|
700
699
|
size="30px"
|
|
701
700
|
title={this.props.intl.formatMessage(
|
|
702
701
|
messages.personalTools,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import cameraSVG from '@plone/volto/icons/camera.svg';
|
|
4
|
+
|
|
5
|
+
import UserAvatar from './UserAvatar';
|
|
6
|
+
|
|
7
|
+
const renderUserAvatar = (
|
|
8
|
+
props: React.ComponentProps<typeof UserAvatar> = {},
|
|
9
|
+
) => render(<UserAvatar {...props} />);
|
|
10
|
+
|
|
11
|
+
describe('UserAvatar', () => {
|
|
12
|
+
it('renders the default user icon', () => {
|
|
13
|
+
const { asFragment } = renderUserAvatar();
|
|
14
|
+
expect(asFragment()).toMatchSnapshot();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('defaults to the toolbar button size', () => {
|
|
18
|
+
const { container } = renderUserAvatar();
|
|
19
|
+
const svg = container.querySelector('svg');
|
|
20
|
+
|
|
21
|
+
expect(svg).not.toBeNull();
|
|
22
|
+
expect(svg).toHaveClass('icon');
|
|
23
|
+
expect(svg).toHaveStyle({ height: '30px' });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders at the given size', () => {
|
|
27
|
+
const { container } = renderUserAvatar({ size: '96px' });
|
|
28
|
+
|
|
29
|
+
expect(container.querySelector('svg')).toHaveStyle({ height: '96px' });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies the given size to a custom icon too', () => {
|
|
33
|
+
const { container } = renderUserAvatar({ icon: cameraSVG, size: '96px' });
|
|
34
|
+
|
|
35
|
+
expect(container.querySelector('svg')).toHaveStyle({ height: '96px' });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('exposes the title to assistive technology', () => {
|
|
39
|
+
const { container } = renderUserAvatar({ title: 'Personal tools' });
|
|
40
|
+
|
|
41
|
+
expect(container.querySelector('svg > title')?.textContent).toBe(
|
|
42
|
+
'Personal tools',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('renders no title element when no title is given', () => {
|
|
47
|
+
const { container } = renderUserAvatar();
|
|
48
|
+
|
|
49
|
+
expect(container.querySelector('svg > title')).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders the given icon instead of the default one', () => {
|
|
53
|
+
const { container: withDefault } = renderUserAvatar();
|
|
54
|
+
const { container: withCamera } = renderUserAvatar({ icon: cameraSVG });
|
|
55
|
+
|
|
56
|
+
expect(withCamera.querySelector('svg')?.innerHTML).not.toBe(
|
|
57
|
+
withDefault.querySelector('svg')?.innerHTML,
|
|
58
|
+
);
|
|
59
|
+
expect(withCamera.innerHTML).toMatchSnapshot();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('keeps the title when a custom icon is given', () => {
|
|
63
|
+
const { container } = renderUserAvatar({
|
|
64
|
+
icon: cameraSVG,
|
|
65
|
+
title: 'Personal tools',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(container.querySelector('svg > title')?.textContent).toBe(
|
|
69
|
+
'Personal tools',
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UserAvatar component.
|
|
3
|
+
* @module components/manage/Toolbar/UserAvatar
|
|
4
|
+
*/
|
|
5
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
6
|
+
import userSVG from '@plone/volto/icons/user.svg';
|
|
7
|
+
|
|
8
|
+
export type UserAvatarProps = {
|
|
9
|
+
/** Title of the rendered icon (a11y). */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Icon to render in place of the default user glyph. */
|
|
12
|
+
icon?: typeof userSVG;
|
|
13
|
+
/** Size of the avatar (in px). Defaults to the toolbar button size. */
|
|
14
|
+
size?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Avatar of the currently signed-in user, as shown in the toolbar.
|
|
19
|
+
*
|
|
20
|
+
* Extracted so that add-ons can shadow this component alone — rendering a
|
|
21
|
+
* portrait, initials or a gravatar — instead of shadowing the whole toolbar.
|
|
22
|
+
*/
|
|
23
|
+
const UserAvatar = ({ title, icon, size = '30px' }: UserAvatarProps) => {
|
|
24
|
+
return <Icon name={icon || userSVG} size={size} title={title} />;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default UserAvatar;
|
|
@@ -5,7 +5,12 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
|
|
5
5
|
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
6
6
|
import { asyncConnect } from '@plone/volto/helpers/AsyncConnect';
|
|
7
7
|
import { createPortal } from 'react-dom';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Container as SemanticContainer,
|
|
10
|
+
Pagination,
|
|
11
|
+
Button,
|
|
12
|
+
Header,
|
|
13
|
+
} from 'semantic-ui-react';
|
|
9
14
|
import qs from 'query-string';
|
|
10
15
|
import classNames from 'classnames';
|
|
11
16
|
import config from '@plone/volto/registry';
|
|
@@ -85,6 +90,9 @@ const Search = (props) => {
|
|
|
85
90
|
|
|
86
91
|
const options = qs.parse(location.search);
|
|
87
92
|
|
|
93
|
+
const Container =
|
|
94
|
+
config.getComponent({ name: 'Container' }).component || SemanticContainer;
|
|
95
|
+
|
|
88
96
|
return (
|
|
89
97
|
<Container id="page-search">
|
|
90
98
|
<Helmet title={intl.formatMessage(messages.Search)} />
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import userSVG from '@plone/volto/icons/user.svg';
|
|
2
|
+
export type UserAvatarProps = {
|
|
3
|
+
/** Title of the rendered icon (a11y). */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Icon to render in place of the default user glyph. */
|
|
6
|
+
icon?: typeof userSVG;
|
|
7
|
+
/** Size of the avatar (in px). Defaults to the toolbar button size. */
|
|
8
|
+
size?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Avatar of the currently signed-in user, as shown in the toolbar.
|
|
12
|
+
*
|
|
13
|
+
* Extracted so that add-ons can shadow this component alone — rendering a
|
|
14
|
+
* portrait, initials or a gravatar — instead of shadowing the whole toolbar.
|
|
15
|
+
*/
|
|
16
|
+
declare const UserAvatar: ({ title, icon, size }: UserAvatarProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export default UserAvatar;
|