@plone/volto 19.0.0-alpha.11 → 19.0.0-alpha.12

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 (58) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +10 -0
  3. package/locales/de/LC_MESSAGES/volto.po +10 -0
  4. package/locales/en/LC_MESSAGES/volto.po +10 -0
  5. package/locales/es/LC_MESSAGES/volto.po +10 -0
  6. package/locales/eu/LC_MESSAGES/volto.po +10 -0
  7. package/locales/fi/LC_MESSAGES/volto.po +10 -0
  8. package/locales/fr/LC_MESSAGES/volto.po +10 -0
  9. package/locales/hi/LC_MESSAGES/volto.po +10 -0
  10. package/locales/it/LC_MESSAGES/volto.po +10 -0
  11. package/locales/ja/LC_MESSAGES/volto.po +10 -0
  12. package/locales/nl/LC_MESSAGES/volto.po +10 -0
  13. package/locales/pt/LC_MESSAGES/volto.po +10 -0
  14. package/locales/pt_BR/LC_MESSAGES/volto.po +10 -0
  15. package/locales/ro/LC_MESSAGES/volto.po +10 -0
  16. package/locales/ru/LC_MESSAGES/volto.po +10 -0
  17. package/locales/volto.pot +11 -1
  18. package/locales/zh_CN/LC_MESSAGES/volto.po +10 -0
  19. package/package.json +5 -4
  20. package/src/components/manage/Controlpanels/Relations/Relations.jsx +1 -1
  21. package/src/components/manage/Widgets/AlignWidget.stories.jsx +9 -0
  22. package/src/components/manage/Widgets/AlignWidget.test.tsx +95 -0
  23. package/src/components/manage/Widgets/{AlignWidget.jsx → AlignWidget.tsx} +23 -7
  24. package/src/components/manage/Widgets/BlockAlignment.stories.tsx +104 -0
  25. package/src/components/manage/Widgets/BlockAlignment.test.tsx +104 -0
  26. package/src/components/manage/Widgets/BlockAlignment.tsx +88 -0
  27. package/src/components/manage/Widgets/BlockWidth.stories.tsx +69 -0
  28. package/src/components/manage/Widgets/BlockWidth.test.tsx +62 -0
  29. package/src/components/manage/Widgets/BlockWidth.tsx +101 -0
  30. package/src/components/manage/Widgets/ButtonsWidget.stories.jsx +61 -0
  31. package/src/components/manage/Widgets/ButtonsWidget.test.tsx +138 -0
  32. package/src/components/manage/Widgets/ButtonsWidget.tsx +176 -0
  33. package/src/components/manage/Widgets/Size.stories.tsx +69 -0
  34. package/src/components/manage/Widgets/Size.test.tsx +59 -0
  35. package/src/components/manage/Widgets/Size.tsx +78 -0
  36. package/src/components/manage/Widgets/index.tsx +21 -0
  37. package/src/components/theme/App/App.jsx +2 -0
  38. package/src/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.tsx +7 -0
  39. package/src/config/Widgets.jsx +7 -0
  40. package/src/config/slots.js +19 -0
  41. package/theme/themes/pastanaga/extras/widgets.less +45 -0
  42. package/types/components/manage/Widgets/AlignWidget.d.ts +8 -10
  43. package/types/components/manage/Widgets/AlignWidget.stories.d.ts +1 -0
  44. package/types/components/manage/Widgets/BlockAlignment.d.ts +7 -0
  45. package/types/components/manage/Widgets/BlockAlignment.stories.d.ts +8 -0
  46. package/types/components/manage/Widgets/BlockWidth.d.ts +7 -0
  47. package/types/components/manage/Widgets/BlockWidth.stories.d.ts +6 -0
  48. package/types/components/manage/Widgets/ButtonsWidget.d.ts +48 -1
  49. package/types/components/manage/Widgets/ButtonsWidget.stories.d.ts +3 -0
  50. package/types/components/manage/Widgets/Size.d.ts +7 -0
  51. package/types/components/manage/Widgets/Size.stories.d.ts +6 -0
  52. package/types/components/manage/Widgets/index.d.ts +7 -2
  53. package/types/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.d.ts +3 -0
  54. package/types/config/Widgets.d.ts +6 -0
  55. package/types/config/slots.d.ts +7 -0
  56. package/src/components/manage/Widgets/AlignWidget.test.jsx +0 -59
  57. package/src/components/manage/Widgets/ButtonsWidget.jsx +0 -41
  58. 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.11",
12
+ "version": "19.0.0-alpha.12",
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.1",
243
244
  "@plone/registry": "3.0.0-alpha.8",
244
- "@plone/scripts": "4.0.0-alpha.3",
245
- "@plone/volto-slate": "19.0.0-alpha.6"
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.9",
370
+ "@plone/types": "2.0.0-alpha.10",
370
371
  "@plone/volto-coresandbox": "1.0.0"
371
372
  },
372
373
  "volta": {
@@ -47,7 +47,7 @@ const RelationsControlPanel = () => {
47
47
 
48
48
  return (
49
49
  <>
50
- <div className="ui container">
50
+ <div className="">
51
51
  <div className="relations-control-panel">
52
52
  <Helmet title={intl.formatMessage(messages.relations)} />
53
53
  {can_edit ? (
@@ -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 from './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 = ({ intl }) => ({
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
- const AlignWidget = (props) => {
55
+ type AlignWidgetProps = ButtonsWidgetProps & {
56
+ defaultAction?: string;
57
+ };
58
+
59
+ const AlignWidget = (props: AlignWidgetProps) => {
48
60
  const intl = useIntl();
49
61
 
50
- const { actions = ['left', 'right', 'center', 'full'], actionsInfoMap } =
51
- props;
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
- defaultAction={props.defaultAction || 'center'}
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
+ };