@plone/volto 19.0.0-alpha.11 → 19.0.0-alpha.13
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 +27 -0
- package/locales/ca/LC_MESSAGES/volto.po +10 -0
- package/locales/de/LC_MESSAGES/volto.po +10 -0
- package/locales/en/LC_MESSAGES/volto.po +10 -0
- package/locales/es/LC_MESSAGES/volto.po +10 -0
- package/locales/eu/LC_MESSAGES/volto.po +10 -0
- package/locales/fi/LC_MESSAGES/volto.po +10 -0
- package/locales/fr/LC_MESSAGES/volto.po +10 -0
- package/locales/hi/LC_MESSAGES/volto.po +10 -0
- package/locales/it/LC_MESSAGES/volto.po +10 -0
- package/locales/ja/LC_MESSAGES/volto.po +10 -0
- package/locales/nl/LC_MESSAGES/volto.po +10 -0
- package/locales/pt/LC_MESSAGES/volto.po +10 -0
- package/locales/pt_BR/LC_MESSAGES/volto.po +10 -0
- package/locales/ro/LC_MESSAGES/volto.po +10 -0
- package/locales/ru/LC_MESSAGES/volto.po +10 -0
- package/locales/volto.pot +11 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +10 -0
- package/package.json +5 -4
- package/src/components/manage/Controlpanels/Relations/Relations.jsx +1 -1
- package/src/components/manage/Widgets/AlignWidget.stories.jsx +9 -0
- package/src/components/manage/Widgets/AlignWidget.test.tsx +95 -0
- package/src/components/manage/Widgets/{AlignWidget.jsx → AlignWidget.tsx} +23 -7
- package/src/components/manage/Widgets/BlockAlignment.stories.tsx +104 -0
- package/src/components/manage/Widgets/BlockAlignment.test.tsx +104 -0
- package/src/components/manage/Widgets/BlockAlignment.tsx +88 -0
- package/src/components/manage/Widgets/BlockWidth.stories.tsx +69 -0
- package/src/components/manage/Widgets/BlockWidth.test.tsx +62 -0
- package/src/components/manage/Widgets/BlockWidth.tsx +101 -0
- package/src/components/manage/Widgets/ButtonsWidget.stories.jsx +61 -0
- package/src/components/manage/Widgets/ButtonsWidget.test.tsx +138 -0
- package/src/components/manage/Widgets/ButtonsWidget.tsx +176 -0
- package/src/components/manage/Widgets/Size.stories.tsx +69 -0
- package/src/components/manage/Widgets/Size.test.tsx +59 -0
- package/src/components/manage/Widgets/Size.tsx +78 -0
- package/src/components/manage/Widgets/index.tsx +21 -0
- package/src/components/theme/App/App.jsx +2 -0
- package/src/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.tsx +7 -0
- package/src/config/Widgets.jsx +7 -0
- package/src/config/slots.js +19 -0
- package/theme/themes/pastanaga/extras/widgets.less +45 -0
- package/types/components/manage/Widgets/AlignWidget.d.ts +8 -10
- package/types/components/manage/Widgets/AlignWidget.stories.d.ts +1 -0
- package/types/components/manage/Widgets/BlockAlignment.d.ts +7 -0
- package/types/components/manage/Widgets/BlockAlignment.stories.d.ts +8 -0
- package/types/components/manage/Widgets/BlockWidth.d.ts +7 -0
- package/types/components/manage/Widgets/BlockWidth.stories.d.ts +6 -0
- package/types/components/manage/Widgets/ButtonsWidget.d.ts +48 -1
- package/types/components/manage/Widgets/ButtonsWidget.stories.d.ts +3 -0
- package/types/components/manage/Widgets/Size.d.ts +7 -0
- package/types/components/manage/Widgets/Size.stories.d.ts +6 -0
- package/types/components/manage/Widgets/index.d.ts +7 -2
- package/types/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.d.ts +3 -0
- package/types/config/Widgets.d.ts +6 -0
- package/types/config/slots.d.ts +7 -0
- package/src/components/manage/Widgets/AlignWidget.test.jsx +0 -59
- package/src/components/manage/Widgets/ButtonsWidget.jsx +0 -41
- package/src/components/manage/Widgets/ButtonsWidget.test.jsx +0 -70
|
@@ -605,6 +605,7 @@ msgstr "单元格"
|
|
|
605
605
|
#: components/manage/Blocks/Maps/Edit
|
|
606
606
|
#: components/manage/Sidebar/AlignBlock
|
|
607
607
|
#: components/manage/Widgets/AlignWidget
|
|
608
|
+
#: components/manage/Widgets/BlockAlignment
|
|
608
609
|
msgid "Center"
|
|
609
610
|
msgstr "中间"
|
|
610
611
|
|
|
@@ -1014,6 +1015,7 @@ msgstr "日期(最新在前)"
|
|
|
1014
1015
|
#: components/manage/Controlpanels/UndoControlpanel
|
|
1015
1016
|
#: components/manage/Preferences/ChangePassword
|
|
1016
1017
|
#: components/manage/Preferences/PersonalPreferences
|
|
1018
|
+
#: components/manage/Widgets/BlockWidth
|
|
1017
1019
|
#: components/manage/Widgets/SchemaWidget
|
|
1018
1020
|
#: components/manage/Widgets/SelectWidget
|
|
1019
1021
|
#: components/theme/Comments/CommentEditModal
|
|
@@ -1667,6 +1669,7 @@ msgstr ""
|
|
|
1667
1669
|
#: components/manage/Blocks/Maps/Edit
|
|
1668
1670
|
#: components/manage/Sidebar/AlignBlock
|
|
1669
1671
|
#: components/manage/Widgets/AlignWidget
|
|
1672
|
+
#: components/manage/Widgets/BlockWidth
|
|
1670
1673
|
msgid "Full"
|
|
1671
1674
|
msgstr ""
|
|
1672
1675
|
|
|
@@ -2052,6 +2055,7 @@ msgstr ""
|
|
|
2052
2055
|
|
|
2053
2056
|
#. Default: "Large"
|
|
2054
2057
|
#: components/manage/Widgets/ImageSizeWidget
|
|
2058
|
+
#: components/manage/Widgets/Size
|
|
2055
2059
|
msgid "Large"
|
|
2056
2060
|
msgstr "大"
|
|
2057
2061
|
|
|
@@ -2082,6 +2086,7 @@ msgstr "最新版本"
|
|
|
2082
2086
|
|
|
2083
2087
|
#. Default: "Layout"
|
|
2084
2088
|
#: components/manage/Controlpanels/ContentTypesActions
|
|
2089
|
+
#: components/manage/Widgets/BlockWidth
|
|
2085
2090
|
msgid "Layout"
|
|
2086
2091
|
msgstr "布局"
|
|
2087
2092
|
|
|
@@ -2094,6 +2099,7 @@ msgstr "首图"
|
|
|
2094
2099
|
#: components/manage/Blocks/Maps/Edit
|
|
2095
2100
|
#: components/manage/Sidebar/AlignBlock
|
|
2096
2101
|
#: components/manage/Widgets/AlignWidget
|
|
2102
|
+
#: components/manage/Widgets/BlockAlignment
|
|
2097
2103
|
msgid "Left"
|
|
2098
2104
|
msgstr ""
|
|
2099
2105
|
|
|
@@ -2292,6 +2298,7 @@ msgstr "最大值为{len}。"
|
|
|
2292
2298
|
|
|
2293
2299
|
#. Default: "Medium"
|
|
2294
2300
|
#: components/manage/Widgets/ImageSizeWidget
|
|
2301
|
+
#: components/manage/Widgets/Size
|
|
2295
2302
|
msgid "Medium"
|
|
2296
2303
|
msgstr "中等"
|
|
2297
2304
|
|
|
@@ -2404,6 +2411,7 @@ msgstr "名字"
|
|
|
2404
2411
|
|
|
2405
2412
|
#. Default: "Narrow"
|
|
2406
2413
|
#: components/manage/Widgets/AlignWidget
|
|
2414
|
+
#: components/manage/Widgets/BlockWidth
|
|
2407
2415
|
msgid "Narrow"
|
|
2408
2416
|
msgstr ""
|
|
2409
2417
|
|
|
@@ -3120,6 +3128,7 @@ msgstr "富文本"
|
|
|
3120
3128
|
#: components/manage/Blocks/Maps/Edit
|
|
3121
3129
|
#: components/manage/Sidebar/AlignBlock
|
|
3122
3130
|
#: components/manage/Widgets/AlignWidget
|
|
3131
|
+
#: components/manage/Widgets/BlockAlignment
|
|
3123
3132
|
msgid "Right"
|
|
3124
3133
|
msgstr ""
|
|
3125
3134
|
|
|
@@ -3516,6 +3525,7 @@ msgstr "大小:{size}"
|
|
|
3516
3525
|
|
|
3517
3526
|
#. Default: "Small"
|
|
3518
3527
|
#: components/manage/Widgets/ImageSizeWidget
|
|
3528
|
+
#: components/manage/Widgets/Size
|
|
3519
3529
|
msgid "Small"
|
|
3520
3530
|
msgstr "小"
|
|
3521
3531
|
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "19.0.0-alpha.
|
|
12
|
+
"version": "19.0.0-alpha.13",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git@github.com:plone/volto.git"
|
|
@@ -240,9 +240,10 @@
|
|
|
240
240
|
"url": "^0.11.3",
|
|
241
241
|
"use-deep-compare-effect": "1.8.1",
|
|
242
242
|
"uuid": "^8.3.2",
|
|
243
|
+
"@plone/components": "4.0.0-alpha.3",
|
|
243
244
|
"@plone/registry": "3.0.0-alpha.8",
|
|
244
|
-
"@plone/
|
|
245
|
-
"@plone/
|
|
245
|
+
"@plone/volto-slate": "19.0.0-alpha.7",
|
|
246
|
+
"@plone/scripts": "4.0.0-alpha.3"
|
|
246
247
|
},
|
|
247
248
|
"devDependencies": {
|
|
248
249
|
"@babel/core": "^7.0.0",
|
|
@@ -366,7 +367,7 @@
|
|
|
366
367
|
"webpack-bundle-analyzer": "4.10.1",
|
|
367
368
|
"webpack-dev-server": "4.11.1",
|
|
368
369
|
"webpack-node-externals": "3.0.0",
|
|
369
|
-
"@plone/types": "2.0.0-alpha.
|
|
370
|
+
"@plone/types": "2.0.0-alpha.10",
|
|
370
371
|
"@plone/volto-coresandbox": "1.0.0"
|
|
371
372
|
},
|
|
372
373
|
"volta": {
|
|
@@ -7,6 +7,15 @@ export const Align = WidgetStory.bind({
|
|
|
7
7
|
widget: AlignWidget,
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
export const AlignDefaultLeft = WidgetStory.bind({
|
|
11
|
+
props: {
|
|
12
|
+
id: 'align-default-left',
|
|
13
|
+
title: 'Align (default left)',
|
|
14
|
+
default: 'left',
|
|
15
|
+
},
|
|
16
|
+
widget: AlignWidget,
|
|
17
|
+
});
|
|
18
|
+
|
|
10
19
|
export default {
|
|
11
20
|
title: 'Edit Widgets/Align',
|
|
12
21
|
component: AlignWidget,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import configureStore from 'redux-mock-store';
|
|
5
|
+
import { Provider } from 'react-intl-redux';
|
|
6
|
+
import imageFullSVG from '@plone/volto/icons/image-full.svg';
|
|
7
|
+
|
|
8
|
+
import AlignWidget from './AlignWidget';
|
|
9
|
+
import type { ButtonsWidgetProps } from './ButtonsWidget';
|
|
10
|
+
|
|
11
|
+
const mockStore = configureStore();
|
|
12
|
+
|
|
13
|
+
const renderAlignWidget = (
|
|
14
|
+
props: Partial<ButtonsWidgetProps> = {},
|
|
15
|
+
): {
|
|
16
|
+
onChange: ButtonsWidgetProps['onChange'];
|
|
17
|
+
asFragment: () => DocumentFragment;
|
|
18
|
+
getByRole: ReturnType<typeof render>['getByRole'];
|
|
19
|
+
} => {
|
|
20
|
+
const { onChange: providedOnChange, ...restProps } = props;
|
|
21
|
+
const onChange = providedOnChange ?? vi.fn();
|
|
22
|
+
const store = mockStore({
|
|
23
|
+
intl: {
|
|
24
|
+
locale: 'en',
|
|
25
|
+
messages: {},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const widgetProps: ButtonsWidgetProps = {
|
|
30
|
+
id: 'align',
|
|
31
|
+
title: 'Alignment',
|
|
32
|
+
fieldSet: 'default',
|
|
33
|
+
onChange,
|
|
34
|
+
value: undefined,
|
|
35
|
+
default: undefined,
|
|
36
|
+
disabled: false,
|
|
37
|
+
isDisabled: false,
|
|
38
|
+
...restProps,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const rendered = render(
|
|
42
|
+
<Provider store={store}>
|
|
43
|
+
<AlignWidget {...widgetProps} />
|
|
44
|
+
</Provider>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
onChange,
|
|
49
|
+
asFragment: rendered.asFragment,
|
|
50
|
+
getByRole: rendered.getByRole,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe('AlignWidget', () => {
|
|
55
|
+
it('renders with default actions', () => {
|
|
56
|
+
const { asFragment } = renderAlignWidget();
|
|
57
|
+
expect(asFragment()).toMatchSnapshot();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('renders with custom actions', () => {
|
|
61
|
+
const { asFragment } = renderAlignWidget({
|
|
62
|
+
actions: ['additional'],
|
|
63
|
+
actionsInfoMap: {
|
|
64
|
+
additional: [imageFullSVG, 'Additional action title'],
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(asFragment()).toMatchSnapshot();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('selects the defaultAction when value is missing', () => {
|
|
72
|
+
const { getByRole } = renderAlignWidget({
|
|
73
|
+
defaultAction: 'left',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(getByRole('radio', { name: 'Left' })).toBeChecked();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('selects the default when value is missing', () => {
|
|
80
|
+
const { getByRole } = renderAlignWidget({
|
|
81
|
+
default: 'left',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(getByRole('radio', { name: 'Left' })).toBeChecked();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not override provided value with default', () => {
|
|
88
|
+
const { onChange } = renderAlignWidget({
|
|
89
|
+
default: 'left',
|
|
90
|
+
value: 'center',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(onChange).not.toHaveBeenCalledWith('align', 'left');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
-
import ButtonsWidget
|
|
3
|
+
import ButtonsWidget, {
|
|
4
|
+
type ActionInfo,
|
|
5
|
+
type ButtonsWidgetProps,
|
|
6
|
+
} from './ButtonsWidget';
|
|
4
7
|
import imageLeftSVG from '@plone/volto/icons/image-left.svg';
|
|
5
8
|
import imageRightSVG from '@plone/volto/icons/image-right.svg';
|
|
6
9
|
import imageFitSVG from '@plone/volto/icons/image-fit.svg';
|
|
7
10
|
import imageNarrowSVG from '@plone/volto/icons/image-narrow.svg';
|
|
8
11
|
import imageWideSVG from '@plone/volto/icons/image-wide.svg';
|
|
9
12
|
import imageFullSVG from '@plone/volto/icons/image-full.svg';
|
|
13
|
+
import type { IntlShape } from 'react-intl';
|
|
10
14
|
|
|
11
15
|
const messages = defineMessages({
|
|
12
16
|
left: {
|
|
@@ -35,20 +39,32 @@ const messages = defineMessages({
|
|
|
35
39
|
},
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
export const defaultActionsInfo = ({
|
|
42
|
+
export const defaultActionsInfo = ({
|
|
43
|
+
intl,
|
|
44
|
+
}: {
|
|
45
|
+
intl: IntlShape;
|
|
46
|
+
}): Record<string, ActionInfo> => ({
|
|
39
47
|
left: [imageLeftSVG, intl.formatMessage(messages.left)],
|
|
40
|
-
right: [imageRightSVG, intl.formatMessage(messages.right)],
|
|
41
48
|
center: [imageFitSVG, intl.formatMessage(messages.center)],
|
|
49
|
+
right: [imageRightSVG, intl.formatMessage(messages.right)],
|
|
42
50
|
narrow: [imageNarrowSVG, intl.formatMessage(messages.narrow)],
|
|
43
51
|
wide: [imageWideSVG, intl.formatMessage(messages.wide)],
|
|
44
52
|
full: [imageFullSVG, intl.formatMessage(messages.full)],
|
|
45
53
|
});
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
type AlignWidgetProps = ButtonsWidgetProps & {
|
|
56
|
+
defaultAction?: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const AlignWidget = (props: AlignWidgetProps) => {
|
|
48
60
|
const intl = useIntl();
|
|
49
61
|
|
|
50
|
-
const {
|
|
51
|
-
|
|
62
|
+
const {
|
|
63
|
+
actions = ['left', 'center', 'right', 'full'],
|
|
64
|
+
actionsInfoMap,
|
|
65
|
+
default: defaultValue,
|
|
66
|
+
defaultAction,
|
|
67
|
+
} = props;
|
|
52
68
|
|
|
53
69
|
const actionsInfo = {
|
|
54
70
|
...defaultActionsInfo({ intl }),
|
|
@@ -60,7 +76,7 @@ const AlignWidget = (props) => {
|
|
|
60
76
|
{...props}
|
|
61
77
|
actions={actions}
|
|
62
78
|
actionsInfoMap={actionsInfo}
|
|
63
|
-
|
|
79
|
+
default={defaultValue ?? defaultAction ?? 'center'}
|
|
64
80
|
/>
|
|
65
81
|
);
|
|
66
82
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import BlockAlignment from './BlockAlignment';
|
|
5
|
+
import type { ButtonsWidgetProps } from './ButtonsWidget';
|
|
6
|
+
import {
|
|
7
|
+
RealStoreWrapper as Wrapper,
|
|
8
|
+
FormUndoWrapper,
|
|
9
|
+
} from '@plone/volto/storybook';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof BlockAlignment> = {
|
|
12
|
+
title: 'Edit Widgets/BlockAlignment',
|
|
13
|
+
component: BlockAlignment,
|
|
14
|
+
decorators: [
|
|
15
|
+
(Story) => (
|
|
16
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
17
|
+
<h4>Block alignment widget</h4>
|
|
18
|
+
<Story />
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof BlockAlignment>;
|
|
27
|
+
|
|
28
|
+
type TemplateParameters = {
|
|
29
|
+
initialValue?: ButtonsWidgetProps['value'];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const Template: StoryFn<typeof BlockAlignment> = (args, context) => {
|
|
33
|
+
const { initialValue } =
|
|
34
|
+
(context.parameters as TemplateParameters | undefined) ?? {};
|
|
35
|
+
const { value: _ignoredValue, onChange: argsOnChange, ...restArgs } = args;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
39
|
+
<FormUndoWrapper
|
|
40
|
+
initialState={{ value: initialValue ?? _ignoredValue }}
|
|
41
|
+
showControls
|
|
42
|
+
>
|
|
43
|
+
{({ state, onChange }) => (
|
|
44
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
45
|
+
<BlockAlignment
|
|
46
|
+
{...(restArgs as ButtonsWidgetProps)}
|
|
47
|
+
value={state.value}
|
|
48
|
+
onChange={(id, nextValue) => {
|
|
49
|
+
argsOnChange?.(id, nextValue);
|
|
50
|
+
onChange({ value: nextValue });
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
<pre>Value: {JSON.stringify(state.value, null, 2)}</pre>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</FormUndoWrapper>
|
|
57
|
+
</Wrapper>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const DefaultAlignment: Story = {
|
|
62
|
+
render: Template,
|
|
63
|
+
args: {
|
|
64
|
+
id: 'alignment',
|
|
65
|
+
title: 'Block alignment',
|
|
66
|
+
block: 'block',
|
|
67
|
+
fieldSet: 'default',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const CustomActions: Story = {
|
|
72
|
+
render: Template,
|
|
73
|
+
args: {
|
|
74
|
+
...(DefaultAlignment.args ?? {}),
|
|
75
|
+
actions: [
|
|
76
|
+
{
|
|
77
|
+
name: 'wide',
|
|
78
|
+
label: 'Wide',
|
|
79
|
+
style: {
|
|
80
|
+
'--block-alignment': 'var(--align-wide)',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'full',
|
|
85
|
+
label: 'Full',
|
|
86
|
+
style: {
|
|
87
|
+
'--block-alignment': 'var(--align-full)',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
actionsInfoMap: {
|
|
92
|
+
wide: ['W', 'Wide alignment'],
|
|
93
|
+
full: ['F', 'Full width alignment'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const FilteredActions: Story = {
|
|
99
|
+
render: Template,
|
|
100
|
+
args: {
|
|
101
|
+
...(DefaultAlignment.args ?? {}),
|
|
102
|
+
filterActions: ['center'],
|
|
103
|
+
},
|
|
104
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, it, expect, vi } 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
|
+
|
|
7
|
+
import BlockAlignment from './BlockAlignment';
|
|
8
|
+
import type { ButtonsWidgetProps } from './ButtonsWidget';
|
|
9
|
+
|
|
10
|
+
const mockStore = configureStore();
|
|
11
|
+
|
|
12
|
+
const renderWidget = (props: Partial<ButtonsWidgetProps> = {}) => {
|
|
13
|
+
const { onChange: providedOnChange, ...restProps } = props;
|
|
14
|
+
const onChange = providedOnChange ?? vi.fn();
|
|
15
|
+
const widgetProps: ButtonsWidgetProps = {
|
|
16
|
+
id: 'alignment',
|
|
17
|
+
title: 'Block alignment',
|
|
18
|
+
fieldSet: 'default',
|
|
19
|
+
onChange,
|
|
20
|
+
value: undefined,
|
|
21
|
+
default: undefined,
|
|
22
|
+
disabled: false,
|
|
23
|
+
isDisabled: false,
|
|
24
|
+
...restProps,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
onChange,
|
|
29
|
+
...render(
|
|
30
|
+
<Provider
|
|
31
|
+
store={mockStore({
|
|
32
|
+
intl: {
|
|
33
|
+
locale: 'en',
|
|
34
|
+
messages: {},
|
|
35
|
+
},
|
|
36
|
+
})}
|
|
37
|
+
>
|
|
38
|
+
<BlockAlignment {...widgetProps} />
|
|
39
|
+
</Provider>,
|
|
40
|
+
),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe('BlockAlignment', () => {
|
|
45
|
+
it('renders default alignment buttons', () => {
|
|
46
|
+
renderWidget();
|
|
47
|
+
|
|
48
|
+
expect(screen.getByRole('radio', { name: 'Left' })).toBeInTheDocument();
|
|
49
|
+
expect(screen.getByRole('radio', { name: 'Center' })).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByRole('radio', { name: 'Right' })).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('filters actions when filterActions is provided', () => {
|
|
54
|
+
renderWidget({ filterActions: ['center'] });
|
|
55
|
+
|
|
56
|
+
expect(screen.getByRole('radio', { name: 'Center' })).toBeInTheDocument();
|
|
57
|
+
expect(
|
|
58
|
+
screen.queryByRole('button', { name: 'Left' }),
|
|
59
|
+
).not.toBeInTheDocument();
|
|
60
|
+
expect(
|
|
61
|
+
screen.queryByRole('button', { name: 'Right' }),
|
|
62
|
+
).not.toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('merges custom actions info map entries', () => {
|
|
66
|
+
renderWidget({
|
|
67
|
+
actions: ['left', 'custom'],
|
|
68
|
+
actionsInfoMap: {
|
|
69
|
+
custom: ['Custom', 'Custom alignment'],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(screen.getByRole('radio', { name: 'Left' })).toBeInTheDocument();
|
|
74
|
+
expect(
|
|
75
|
+
screen.getByRole('radio', { name: 'Custom alignment' }),
|
|
76
|
+
).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('renders when style definitions are provided as actions', () => {
|
|
80
|
+
renderWidget({
|
|
81
|
+
actions: [
|
|
82
|
+
{
|
|
83
|
+
name: 'wide',
|
|
84
|
+
label: 'Wide',
|
|
85
|
+
style: {
|
|
86
|
+
'--block-alignment': 'var(--align-wide)',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(screen.getByRole('radio', { name: 'wide' })).toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('invokes onChange with styles when default actions are used', () => {
|
|
96
|
+
const { onChange } = renderWidget();
|
|
97
|
+
|
|
98
|
+
fireEvent.click(screen.getByRole('radio', { name: 'Center' }));
|
|
99
|
+
|
|
100
|
+
expect(onChange).toHaveBeenCalledWith('alignment', {
|
|
101
|
+
'--block-alignment': 'var(--align-center)',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import ButtonsWidget, {
|
|
4
|
+
type ActionInfo,
|
|
5
|
+
type ButtonsWidgetProps,
|
|
6
|
+
} from './ButtonsWidget';
|
|
7
|
+
import imageFitSVG from '@plone/volto/icons/image-fit.svg';
|
|
8
|
+
import imageLeftSVG from '@plone/volto/icons/image-left.svg';
|
|
9
|
+
import imageRightSVG from '@plone/volto/icons/image-right.svg';
|
|
10
|
+
import type { IntlShape } from 'react-intl';
|
|
11
|
+
import type { StyleDefinition } from '@plone/types';
|
|
12
|
+
|
|
13
|
+
const messages = defineMessages({
|
|
14
|
+
left: {
|
|
15
|
+
id: 'Left',
|
|
16
|
+
defaultMessage: 'Left',
|
|
17
|
+
},
|
|
18
|
+
right: {
|
|
19
|
+
id: 'Right',
|
|
20
|
+
defaultMessage: 'Right',
|
|
21
|
+
},
|
|
22
|
+
center: {
|
|
23
|
+
id: 'Center',
|
|
24
|
+
defaultMessage: 'Center',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const defaultActionsInfo = ({
|
|
29
|
+
intl,
|
|
30
|
+
}: {
|
|
31
|
+
intl: IntlShape;
|
|
32
|
+
}): Record<string, ActionInfo> => ({
|
|
33
|
+
left: [imageLeftSVG, intl.formatMessage(messages.left)],
|
|
34
|
+
right: [imageRightSVG, intl.formatMessage(messages.right)],
|
|
35
|
+
center: [imageFitSVG, intl.formatMessage(messages.center)],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const DEFAULT_ACTIONS: StyleDefinition[] = [
|
|
39
|
+
{
|
|
40
|
+
style: {
|
|
41
|
+
'--block-alignment': 'var(--align-left)',
|
|
42
|
+
},
|
|
43
|
+
name: 'left',
|
|
44
|
+
label: 'Left',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
style: {
|
|
48
|
+
'--block-alignment': 'var(--align-center)',
|
|
49
|
+
},
|
|
50
|
+
name: 'center',
|
|
51
|
+
label: 'Center',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
style: {
|
|
55
|
+
'--block-alignment': 'var(--align-right)',
|
|
56
|
+
},
|
|
57
|
+
name: 'right',
|
|
58
|
+
label: 'Right',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const BlockAlignmentWidget = (props: ButtonsWidgetProps) => {
|
|
63
|
+
const intl = useIntl();
|
|
64
|
+
|
|
65
|
+
const { actions = DEFAULT_ACTIONS, actionsInfoMap, filterActions } = props;
|
|
66
|
+
const filteredActions =
|
|
67
|
+
filterActions && filterActions.length > 0
|
|
68
|
+
? actions.filter((action) => {
|
|
69
|
+
const actionName = typeof action === 'string' ? action : action.name;
|
|
70
|
+
return filterActions.includes(actionName);
|
|
71
|
+
})
|
|
72
|
+
: actions;
|
|
73
|
+
|
|
74
|
+
const actionsInfo = {
|
|
75
|
+
...defaultActionsInfo({ intl }),
|
|
76
|
+
...actionsInfoMap,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<ButtonsWidget
|
|
81
|
+
{...props}
|
|
82
|
+
actions={filteredActions}
|
|
83
|
+
actionsInfoMap={actionsInfo}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default BlockAlignmentWidget;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import BlockWidth from './BlockWidth';
|
|
5
|
+
import type { ButtonsWidgetProps } from './ButtonsWidget';
|
|
6
|
+
import {
|
|
7
|
+
RealStoreWrapper as Wrapper,
|
|
8
|
+
FormUndoWrapper,
|
|
9
|
+
} from '@plone/volto/storybook';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof BlockWidth> = {
|
|
12
|
+
title: 'Edit Widgets/BlockWidth',
|
|
13
|
+
component: BlockWidth,
|
|
14
|
+
decorators: [
|
|
15
|
+
(Story) => (
|
|
16
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
17
|
+
<h4>Block width widget</h4>
|
|
18
|
+
<Story />
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof BlockWidth>;
|
|
27
|
+
|
|
28
|
+
type TemplateParameters = {
|
|
29
|
+
initialValue?: ButtonsWidgetProps['value'];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const Template: StoryFn<typeof BlockWidth> = (args, context) => {
|
|
33
|
+
const { initialValue } =
|
|
34
|
+
(context.parameters as TemplateParameters | undefined) ?? {};
|
|
35
|
+
const { value: _ignoredValue, onChange: argsOnChange, ...restArgs } = args;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
39
|
+
<FormUndoWrapper
|
|
40
|
+
initialState={{ value: initialValue ?? _ignoredValue }}
|
|
41
|
+
showControls
|
|
42
|
+
>
|
|
43
|
+
{({ state, onChange }) => (
|
|
44
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
45
|
+
<BlockWidth
|
|
46
|
+
{...(restArgs as ButtonsWidgetProps)}
|
|
47
|
+
value={state.value}
|
|
48
|
+
onChange={(id, nextValue) => {
|
|
49
|
+
argsOnChange?.(id, nextValue);
|
|
50
|
+
onChange({ value: nextValue });
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
<pre>Value: {JSON.stringify(state.value, null, 2)}</pre>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</FormUndoWrapper>
|
|
57
|
+
</Wrapper>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const DefaultWidth: Story = {
|
|
62
|
+
render: Template,
|
|
63
|
+
args: {
|
|
64
|
+
id: 'blockWidth',
|
|
65
|
+
title: 'Block width',
|
|
66
|
+
block: 'block',
|
|
67
|
+
fieldSet: 'default',
|
|
68
|
+
},
|
|
69
|
+
};
|