@kitconcept/volto-light-theme 8.0.0-alpha.2 → 8.0.0-alpha.3
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 +12 -3
- package/CHANGELOG.md +16 -0
- package/package.json +3 -3
- package/src/components/Blocks/Block/Edit.jsx +14 -6
- 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/Theme/RenderBlocks.jsx +45 -37
- package/src/components/Theme/RenderBlocksV2.jsx +26 -18
- package/src/components/Widgets/Buttons.tsx +35 -12
- package/src/theme/_widgets.scss +9 -10
- package/src/theme/blocks/_listing.scss +5 -0
- package/src/theme/blocks/error-boundary.scss +11 -0
- package/src/theme/main.scss +2 -0
- package/src/theme/notfound.scss +25 -0
- package/src/theme/person.scss +21 -11
package/.changelog.draft
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
## 8.0.0-alpha.
|
|
1
|
+
## 8.0.0-alpha.3 (2025-11-04)
|
|
2
2
|
|
|
3
3
|
### Feature
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
5
|
+
- Recoverable Block Error Boundaries. @sneridagh [#708](https://github.com/kitconcept/volto-light-theme/pull/708)
|
|
6
|
+
- Update `Buttons` widget to the one proposed for the Volto PR: #7555 @sneridagh
|
|
7
|
+
|
|
8
|
+
### Bugfix
|
|
9
|
+
|
|
10
|
+
- [#705](https://github.com/kitconcept/volto-light-theme/pull/705)
|
|
11
|
+
- Fix Teaser Blocks for Person type email spacing in 4 columns gridBlock @Tishasoumya-02
|
|
12
|
+
|
|
13
|
+
### Internal
|
|
14
|
+
|
|
15
|
+
- Add css for NotFound Page @Tishasoumya-02
|
|
7
16
|
|
|
8
17
|
|
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- towncrier release notes start -->
|
|
10
10
|
|
|
11
|
+
## 8.0.0-alpha.3 (2025-11-04)
|
|
12
|
+
|
|
13
|
+
### Feature
|
|
14
|
+
|
|
15
|
+
- Recoverable Block Error Boundaries. @sneridagh [#708](https://github.com/kitconcept/volto-light-theme/pull/708)
|
|
16
|
+
- Update `Buttons` widget to the one proposed for the Volto PR: #7555 @sneridagh
|
|
17
|
+
|
|
18
|
+
### Bugfix
|
|
19
|
+
|
|
20
|
+
- [#705](https://github.com/kitconcept/volto-light-theme/pull/705)
|
|
21
|
+
- Fix Teaser Blocks for Person type email spacing in 4 columns gridBlock @Tishasoumya-02
|
|
22
|
+
|
|
23
|
+
### Internal
|
|
24
|
+
|
|
25
|
+
- Add css for NotFound Page @Tishasoumya-02
|
|
26
|
+
|
|
11
27
|
## 8.0.0-alpha.2 (2025-10-29)
|
|
12
28
|
|
|
13
29
|
### Feature
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitconcept/volto-light-theme",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.3",
|
|
4
4
|
"description": "Volto Light Theme by kitconcept",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"release-it": "^19.0.3",
|
|
46
46
|
"typescript": "^5.7.3",
|
|
47
47
|
"vitest": "^3.1.2",
|
|
48
|
-
"@plone/types": "2.0.0-alpha.
|
|
48
|
+
"@plone/types": "2.0.0-alpha.8"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@dnd-kit/core": "6.0.8",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"embla-carousel-autoplay": "^8.0.0",
|
|
55
55
|
"embla-carousel-react": "^8.0.0",
|
|
56
56
|
"react-animate-height": "^3.2.3",
|
|
57
|
-
"react-aria-components": "^1.
|
|
57
|
+
"react-aria-components": "^1.12.1",
|
|
58
58
|
"react-colorful": "^5.6.1",
|
|
59
59
|
"uuid": "^11.0.0",
|
|
60
60
|
"@plone/components": "^4.0.0-alpha.1"
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
buildStyleClassNamesExtenders,
|
|
36
36
|
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
37
37
|
import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
|
|
38
|
+
import ErrorBoundary from './ErrorBoundary';
|
|
38
39
|
|
|
39
40
|
const messages = defineMessages({
|
|
40
41
|
unknownBlock: {
|
|
@@ -288,12 +289,19 @@ export class Edit extends Component {
|
|
|
288
289
|
as={'div'}
|
|
289
290
|
className="block-inner-container"
|
|
290
291
|
>
|
|
291
|
-
<
|
|
292
|
-
{
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
<ErrorBoundary
|
|
293
|
+
name={`blockId-${this.props.id}-type-${type}`}
|
|
294
|
+
block={this.props.block}
|
|
295
|
+
type={type}
|
|
296
|
+
isEdit
|
|
297
|
+
>
|
|
298
|
+
<Block
|
|
299
|
+
{...this.props}
|
|
300
|
+
blockNode={this.blockNode}
|
|
301
|
+
data={this.props.data}
|
|
302
|
+
className={cx({ contained: parentIsContainer })}
|
|
303
|
+
/>
|
|
304
|
+
</ErrorBoundary>
|
|
297
305
|
</MaybeWrap>
|
|
298
306
|
|
|
299
307
|
{blocksConfig?.[type]?.blockModel === 3 && (
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
6
|
+
import configureStore from 'redux-mock-store';
|
|
7
|
+
|
|
8
|
+
describe('Error boundary', () => {
|
|
9
|
+
const consoleErrorSpy = vi
|
|
10
|
+
.spyOn(console, 'error')
|
|
11
|
+
.mockImplementation(() => undefined);
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
consoleErrorSpy.mockClear();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
consoleErrorSpy.mockRestore();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const mockStore = configureStore();
|
|
22
|
+
|
|
23
|
+
const store = mockStore({
|
|
24
|
+
intl: {
|
|
25
|
+
locale: 'en',
|
|
26
|
+
messages: {},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders fallback UI when a child throws', () => {
|
|
31
|
+
const ThrowError: FC = () => {
|
|
32
|
+
throw new Error('Test');
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const { container } = render(
|
|
36
|
+
<Provider store={store}>
|
|
37
|
+
<ErrorBoundary
|
|
38
|
+
name="test"
|
|
39
|
+
block="123"
|
|
40
|
+
type="slate"
|
|
41
|
+
blocks={null}
|
|
42
|
+
blocksLayout={null}
|
|
43
|
+
title={null}
|
|
44
|
+
>
|
|
45
|
+
<ThrowError />
|
|
46
|
+
</ErrorBoundary>
|
|
47
|
+
</Provider>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(screen.getByText('Block error:')).toBeInTheDocument();
|
|
51
|
+
expect(
|
|
52
|
+
container.querySelector('.block-error-boundary .title'),
|
|
53
|
+
).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ErrorInfo, ReactNode } from 'react';
|
|
3
|
+
import { connect } from 'react-redux';
|
|
4
|
+
import type { BlocksData } from '@plone/types';
|
|
5
|
+
import ErrorBoundaryMessage from './ErrorBoundaryMessage';
|
|
6
|
+
|
|
7
|
+
type OwnProps = {
|
|
8
|
+
name?: string;
|
|
9
|
+
block?: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
isEdit?: boolean;
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type StateProps = {
|
|
16
|
+
blocks: BlocksData['blocks'];
|
|
17
|
+
blocksLayout: BlocksData['blocks_layout'];
|
|
18
|
+
title: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ErrorBoundaryState = {
|
|
22
|
+
hasError: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ErrorBoundaryProps = OwnProps & StateProps;
|
|
26
|
+
|
|
27
|
+
export class ErrorBoundary extends React.Component<
|
|
28
|
+
ErrorBoundaryProps,
|
|
29
|
+
ErrorBoundaryState
|
|
30
|
+
> {
|
|
31
|
+
state: ErrorBoundaryState = { hasError: false };
|
|
32
|
+
|
|
33
|
+
static getDerivedStateFromError(_error: Error): ErrorBoundaryState {
|
|
34
|
+
// Update state so the next render will show the fallback UI.
|
|
35
|
+
return { hasError: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
componentDidUpdate(prevProps: ErrorBoundaryProps) {
|
|
39
|
+
const titleChanged = prevProps.title !== this.props.title;
|
|
40
|
+
const blocksChanged = prevProps.blocks !== this.props.blocks;
|
|
41
|
+
const blocksLayoutChanged =
|
|
42
|
+
prevProps.blocksLayout !== this.props.blocksLayout;
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
(blocksChanged || blocksLayoutChanged || titleChanged) &&
|
|
46
|
+
this.state.hasError
|
|
47
|
+
) {
|
|
48
|
+
this.setState({ hasError: false });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
53
|
+
// eslint-disable-next-line
|
|
54
|
+
console.error(error, errorInfo);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render(): ReactNode {
|
|
58
|
+
if (this.state.hasError) {
|
|
59
|
+
return (
|
|
60
|
+
<ErrorBoundaryMessage
|
|
61
|
+
name={this.props.name}
|
|
62
|
+
block={this.props.block}
|
|
63
|
+
type={this.props.type}
|
|
64
|
+
isEdit={this.props.isEdit}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this.props.children;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type ReduxState = {
|
|
74
|
+
form?: {
|
|
75
|
+
global?: {
|
|
76
|
+
blocks?: BlocksData['blocks'];
|
|
77
|
+
blocks_layout?: BlocksData['blocks_layout'];
|
|
78
|
+
title: string | null;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const mapStateToProps = (state: ReduxState): StateProps => ({
|
|
84
|
+
blocks: state.form?.global?.blocks ?? null,
|
|
85
|
+
blocksLayout: state.form?.global?.blocks_layout ?? null,
|
|
86
|
+
// Title is used for demonstration purposes
|
|
87
|
+
// If we want to use it in metadata sources, we should connect it to the full state
|
|
88
|
+
// which I am reluctant to do because nowadays the form state can be quite large
|
|
89
|
+
title: state.form?.global?.title ?? null,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export default connect(mapStateToProps)(ErrorBoundary);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|
2
|
+
|
|
3
|
+
type ErrorBoundaryMessageProps = {
|
|
4
|
+
name?: string;
|
|
5
|
+
block?: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
isEdit?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const messages = defineMessages({
|
|
11
|
+
title: {
|
|
12
|
+
id: 'blockErrorBoundaryTitle',
|
|
13
|
+
defaultMessage: 'Block error:',
|
|
14
|
+
},
|
|
15
|
+
description: {
|
|
16
|
+
id: 'blockErrorBoundaryDescription',
|
|
17
|
+
defaultMessage:
|
|
18
|
+
'The {type} block with the id {block} has encountered an error.{lineBreak}You can try to undo your changes (via the undo toolbar or pressing {shortcut}), or try to delete the block and recreate it again.',
|
|
19
|
+
},
|
|
20
|
+
viewDescription: {
|
|
21
|
+
id: 'blockErrorBoundaryViewDescription',
|
|
22
|
+
defaultMessage:
|
|
23
|
+
'The {type} block with the id {block} errored and cannot be displayed.{lineBreak}Please contact the site administrator for further assistance.',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const ErrorBoundaryMessage = (props: ErrorBoundaryMessageProps) => {
|
|
28
|
+
const intl = useIntl();
|
|
29
|
+
|
|
30
|
+
if (props.isEdit) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="block-error-boundary">
|
|
33
|
+
<div className="title">{intl.formatMessage(messages.title)}</div>
|
|
34
|
+
<p>
|
|
35
|
+
<FormattedMessage
|
|
36
|
+
{...messages.description}
|
|
37
|
+
values={{
|
|
38
|
+
type: <code>{props.type}</code>,
|
|
39
|
+
block: <code>{props.block}</code>,
|
|
40
|
+
lineBreak: <br />,
|
|
41
|
+
shortcut: <code>ctrl/cmd + Z</code>,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
return (
|
|
49
|
+
<div className="block-error-boundary">
|
|
50
|
+
<div className="title">{intl.formatMessage(messages.title)}</div>
|
|
51
|
+
<p>
|
|
52
|
+
<FormattedMessage
|
|
53
|
+
{...messages.viewDescription}
|
|
54
|
+
values={{
|
|
55
|
+
type: <code>{props.type}</code>,
|
|
56
|
+
block: <code>{props.block}</code>,
|
|
57
|
+
lineBreak: <br />,
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default ErrorBoundaryMessage;
|
|
@@ -12,6 +12,7 @@ import config from '@plone/volto/registry';
|
|
|
12
12
|
import ViewDefaultBlock from '@plone/volto/components/manage/Blocks/Block/DefaultView';
|
|
13
13
|
import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
|
|
14
14
|
import RenderEmptyBlock from '@plone/volto/components/theme/View/RenderEmptyBlock';
|
|
15
|
+
import ErrorBoundary from '../Blocks/Block/ErrorBoundary';
|
|
15
16
|
|
|
16
17
|
import StyleWrapperV3 from './StyleWrapperV3';
|
|
17
18
|
import RenderBlocksV2 from './RenderBlocksV2';
|
|
@@ -69,48 +70,55 @@ const RenderBlocks = (props) => {
|
|
|
69
70
|
|
|
70
71
|
if (Block) {
|
|
71
72
|
return (
|
|
72
|
-
<
|
|
73
|
-
key={block}
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
<ErrorBoundary
|
|
74
|
+
key={`error-boundary-block-${block}`}
|
|
75
|
+
name={`blockId-${block}-type-${content[blocksFieldname]?.[block]?.['@type']}`}
|
|
76
|
+
block={block}
|
|
77
|
+
type={content[blocksFieldname]?.[block]?.['@type']}
|
|
76
78
|
>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
id={block}
|
|
87
|
-
metadata={metadata}
|
|
88
|
-
properties={content}
|
|
79
|
+
<MaybeWrap
|
|
80
|
+
key={block}
|
|
81
|
+
condition={blockWrapperTag}
|
|
82
|
+
as={blockWrapperTag}
|
|
83
|
+
>
|
|
84
|
+
{currentBlockModel === 3 ? (
|
|
85
|
+
<StyleWrapperV3
|
|
86
|
+
block={block}
|
|
87
|
+
content={content}
|
|
89
88
|
data={blockData}
|
|
90
|
-
path={getBaseUrl(location?.pathname || '')}
|
|
91
89
|
blocksConfig={blocksConfig}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
isContainer={isContainer}
|
|
91
|
+
>
|
|
92
|
+
<Block
|
|
93
|
+
id={block}
|
|
94
|
+
metadata={metadata}
|
|
95
|
+
properties={content}
|
|
96
|
+
data={blockData}
|
|
97
|
+
path={getBaseUrl(location?.pathname || '')}
|
|
98
|
+
blocksConfig={blocksConfig}
|
|
99
|
+
/>
|
|
100
|
+
</StyleWrapperV3>
|
|
101
|
+
) : (
|
|
102
|
+
<StyleWrapper
|
|
103
|
+
key={block}
|
|
104
|
+
{...props}
|
|
104
105
|
id={block}
|
|
105
|
-
|
|
106
|
-
properties={content}
|
|
106
|
+
block={block}
|
|
107
107
|
data={blockData}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
isContainer={isContainer}
|
|
109
|
+
>
|
|
110
|
+
<Block
|
|
111
|
+
id={block}
|
|
112
|
+
metadata={metadata}
|
|
113
|
+
properties={content}
|
|
114
|
+
data={blockData}
|
|
115
|
+
path={getBaseUrl(location?.pathname || '')}
|
|
116
|
+
blocksConfig={blocksConfig}
|
|
117
|
+
/>
|
|
118
|
+
</StyleWrapper>
|
|
119
|
+
)}
|
|
120
|
+
</MaybeWrap>
|
|
121
|
+
</ErrorBoundary>
|
|
114
122
|
);
|
|
115
123
|
}
|
|
116
124
|
|
|
@@ -15,6 +15,7 @@ import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
|
|
|
15
15
|
import RenderEmptyBlock from '@plone/volto/components/theme/View/RenderEmptyBlock';
|
|
16
16
|
import cx from 'classnames';
|
|
17
17
|
import { groupByBGColor } from '../../helpers/grouping';
|
|
18
|
+
import ErrorBoundary from '../Blocks/Block/ErrorBoundary';
|
|
18
19
|
|
|
19
20
|
const messages = defineMessages({
|
|
20
21
|
unknownBlock: {
|
|
@@ -92,29 +93,36 @@ const RenderBlocks = (props) => {
|
|
|
92
93
|
|
|
93
94
|
if (Block) {
|
|
94
95
|
return (
|
|
95
|
-
<
|
|
96
|
-
key={block}
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
<ErrorBoundary
|
|
97
|
+
key={`error-boundary-block-${block}`}
|
|
98
|
+
name={`blockId-${block}-type-${content[blocksFieldname]?.[block]?.['@type']}`}
|
|
99
|
+
block={block}
|
|
100
|
+
type={content[blocksFieldname]?.[block]?.['@type']}
|
|
99
101
|
>
|
|
100
|
-
<
|
|
102
|
+
<MaybeWrap
|
|
101
103
|
key={block}
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
block={block}
|
|
105
|
-
data={blockData}
|
|
106
|
-
isContainer={isContainer}
|
|
104
|
+
condition={blockWrapperTag}
|
|
105
|
+
as={blockWrapperTag}
|
|
107
106
|
>
|
|
108
|
-
<
|
|
107
|
+
<StyleWrapper
|
|
108
|
+
key={block}
|
|
109
|
+
{...props}
|
|
109
110
|
id={block}
|
|
110
|
-
|
|
111
|
-
properties={content}
|
|
111
|
+
block={block}
|
|
112
112
|
data={blockData}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
isContainer={isContainer}
|
|
114
|
+
>
|
|
115
|
+
<Block
|
|
116
|
+
id={block}
|
|
117
|
+
metadata={metadata}
|
|
118
|
+
properties={content}
|
|
119
|
+
data={blockData}
|
|
120
|
+
path={getBaseUrl(location?.pathname || '')}
|
|
121
|
+
blocksConfig={blocksConfig}
|
|
122
|
+
/>
|
|
123
|
+
</StyleWrapper>
|
|
124
|
+
</MaybeWrap>
|
|
125
|
+
</ErrorBoundary>
|
|
118
126
|
);
|
|
119
127
|
}
|
|
120
128
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
3
3
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
4
|
-
import {
|
|
4
|
+
import { RadioGroup } from '@plone/components';
|
|
5
|
+
import { Radio } from 'react-aria-components';
|
|
5
6
|
import isEqual from 'lodash/isEqual';
|
|
6
7
|
import type { StyleDefinition } from '@plone/types';
|
|
7
8
|
|
|
@@ -90,6 +91,25 @@ const ButtonsWidget = (props: ButtonsWidgetProps) => {
|
|
|
90
91
|
[actions],
|
|
91
92
|
);
|
|
92
93
|
|
|
94
|
+
const selectedActionName = React.useMemo(
|
|
95
|
+
() =>
|
|
96
|
+
normalizedActions.find((action) => isEqual(value, action.value))?.name,
|
|
97
|
+
[normalizedActions, value],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const handleChange = React.useCallback(
|
|
101
|
+
(selectedName: string) => {
|
|
102
|
+
const selectedAction = normalizedActions.find(
|
|
103
|
+
({ name }) => name === selectedName,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (selectedAction) {
|
|
107
|
+
onChange(id, selectedAction.value);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
[id, normalizedActions, onChange],
|
|
111
|
+
);
|
|
112
|
+
|
|
93
113
|
React.useEffect(() => {
|
|
94
114
|
if (!value && defaultValue) {
|
|
95
115
|
const nextValue =
|
|
@@ -104,7 +124,14 @@ const ButtonsWidget = (props: ButtonsWidgetProps) => {
|
|
|
104
124
|
|
|
105
125
|
return (
|
|
106
126
|
<FormFieldWrapper {...props} className="widget">
|
|
107
|
-
<
|
|
127
|
+
<RadioGroup
|
|
128
|
+
aria-label={props.title || props.label || id}
|
|
129
|
+
orientation="horizontal"
|
|
130
|
+
value={selectedActionName}
|
|
131
|
+
onChange={handleChange}
|
|
132
|
+
isDisabled={disabled || isDisabled}
|
|
133
|
+
className="buttons buttons-widget"
|
|
134
|
+
>
|
|
108
135
|
{normalizedActions.map((action) => {
|
|
109
136
|
const actionInfo = actionsInfoMap?.[action.name];
|
|
110
137
|
const [iconOrText, ariaLabel] = actionInfo ?? [
|
|
@@ -113,16 +140,11 @@ const ButtonsWidget = (props: ButtonsWidgetProps) => {
|
|
|
113
140
|
];
|
|
114
141
|
|
|
115
142
|
return (
|
|
116
|
-
<
|
|
143
|
+
<Radio
|
|
117
144
|
key={action.name}
|
|
118
145
|
aria-label={ariaLabel}
|
|
119
|
-
|
|
120
|
-
className=
|
|
121
|
-
isEqual(value, action.value)
|
|
122
|
-
? 'react-aria-Button active'
|
|
123
|
-
: 'react-aria-Button'
|
|
124
|
-
}
|
|
125
|
-
isDisabled={disabled || isDisabled}
|
|
146
|
+
value={action.name}
|
|
147
|
+
className="buttons-widget-option"
|
|
126
148
|
>
|
|
127
149
|
{typeof iconOrText === 'string' ? (
|
|
128
150
|
<div className="image-sizes-text">{iconOrText}</div>
|
|
@@ -131,12 +153,13 @@ const ButtonsWidget = (props: ButtonsWidgetProps) => {
|
|
|
131
153
|
name={iconOrText}
|
|
132
154
|
title={ariaLabel || action.name}
|
|
133
155
|
size="24px"
|
|
156
|
+
ariaHidden={true}
|
|
134
157
|
/>
|
|
135
158
|
)}
|
|
136
|
-
</
|
|
159
|
+
</Radio>
|
|
137
160
|
);
|
|
138
161
|
})}
|
|
139
|
-
</
|
|
162
|
+
</RadioGroup>
|
|
140
163
|
</FormFieldWrapper>
|
|
141
164
|
);
|
|
142
165
|
};
|
package/src/theme/_widgets.scss
CHANGED
|
@@ -6,21 +6,22 @@
|
|
|
6
6
|
#sidebar-properties,
|
|
7
7
|
#sidebar-metadata {
|
|
8
8
|
.field.widget {
|
|
9
|
-
.buttons {
|
|
9
|
+
.buttons-widget {
|
|
10
10
|
display: flex;
|
|
11
11
|
align-items: center;
|
|
12
|
+
gap: 4px;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
.buttons-widget-option {
|
|
14
15
|
padding: 5px;
|
|
15
16
|
border-radius: 3px;
|
|
16
|
-
margin-right: 3px;
|
|
17
17
|
aspect-ratio: 1/1;
|
|
18
18
|
font-size: 1em;
|
|
19
19
|
line-height: initial;
|
|
20
|
+
|
|
20
21
|
&[data-hovered='true'] {
|
|
21
22
|
box-shadow: inset 0 0 0 1px var(--border-color-pressed, #2996da);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
&[data-selected='true'] {
|
|
24
25
|
box-shadow: inset 0 0 0 1px var(--border-color-pressed, #2996da);
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -134,12 +135,10 @@ span.color-contrast-label {
|
|
|
134
135
|
background-color: var(--theme-color);
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
.buttons-widget {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
color: var(--text-color-disabled);
|
|
142
|
-
}
|
|
138
|
+
.buttons-widget-option {
|
|
139
|
+
&[data-disabled] {
|
|
140
|
+
border-color: var(--border-color-disabled);
|
|
141
|
+
color: var(--text-color-disabled);
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
|
package/src/theme/main.scss
CHANGED
|
@@ -36,10 +36,12 @@
|
|
|
36
36
|
@import 'blocks/eventMetadata';
|
|
37
37
|
@import 'blocks/rss';
|
|
38
38
|
@import 'blocks/eventSearch';
|
|
39
|
+
@import 'blocks/error-boundary';
|
|
39
40
|
@import 'sticky-menu';
|
|
40
41
|
@import 'card';
|
|
41
42
|
@import 'insets';
|
|
42
43
|
@import 'person';
|
|
44
|
+
@import 'notfound';
|
|
43
45
|
|
|
44
46
|
@import 'temp';
|
|
45
47
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.page-not-found {
|
|
2
|
+
.content-area {
|
|
3
|
+
@include default-container-width();
|
|
4
|
+
@include adjustMarginsToContainer($default-container-width);
|
|
5
|
+
.view-wrapper {
|
|
6
|
+
padding-top: $spacing-xlarge;
|
|
7
|
+
padding-bottom: $spacing-xlarge;
|
|
8
|
+
}
|
|
9
|
+
h1 {
|
|
10
|
+
margin-bottom: $spacing-large;
|
|
11
|
+
font-size: 48px;
|
|
12
|
+
font-weight: 700;
|
|
13
|
+
line-height: 56px;
|
|
14
|
+
}
|
|
15
|
+
p,
|
|
16
|
+
a {
|
|
17
|
+
font-size: 24px;
|
|
18
|
+
font-weight: 300;
|
|
19
|
+
line-height: 33px;
|
|
20
|
+
}
|
|
21
|
+
a {
|
|
22
|
+
color: $black;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/theme/person.scss
CHANGED
|
@@ -325,24 +325,34 @@ body.person-squared-images,
|
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
|
|
329
|
+
// person with Four columns (edit mode and view mode)
|
|
330
|
+
.block.gridBlock .four.grid-items .person-teaser .card-summary,
|
|
331
|
+
.ui.stackable.stretched.four.column.grid .card-summary {
|
|
332
|
+
.summary-extra-info {
|
|
333
|
+
&.email {
|
|
334
|
+
min-width: 0;
|
|
335
|
+
|
|
336
|
+
a {
|
|
337
|
+
display: block;
|
|
338
|
+
overflow: hidden;
|
|
339
|
+
text-overflow: ellipsis;
|
|
340
|
+
white-space: nowrap;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Additional styles for view mode only
|
|
329
347
|
.ui.stackable.stretched.four.column.grid {
|
|
330
348
|
.card-summary {
|
|
331
349
|
display: grid;
|
|
350
|
+
|
|
332
351
|
.summary-extra-info {
|
|
333
352
|
gap: 5px;
|
|
334
|
-
&.email {
|
|
335
|
-
min-width: 0;
|
|
336
|
-
|
|
337
|
-
a {
|
|
338
|
-
display: block;
|
|
339
|
-
overflow: hidden;
|
|
340
|
-
text-overflow: ellipsis;
|
|
341
|
-
white-space: nowrap;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
353
|
}
|
|
345
354
|
}
|
|
355
|
+
|
|
346
356
|
.block.listing,
|
|
347
357
|
.block.search {
|
|
348
358
|
.listing-item.person-listing .card-summary {
|