@strapi/admin 4.14.0-beta.0 → 4.14.1

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 (104) hide show
  1. package/admin/src/components/RBACProvider/index.js +1 -1
  2. package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +87 -0
  3. package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +463 -0
  4. package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +451 -0
  5. package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +102 -0
  6. package/admin/src/content-manager/components/BlocksEditor/index.js +126 -0
  7. package/admin/src/content-manager/components/InputUID/index.js +92 -109
  8. package/admin/src/content-manager/components/Inputs/index.js +3 -1
  9. package/admin/src/content-manager/components/Inputs/utils/getInputType.js +2 -0
  10. package/admin/src/content-manager/components/RelationInput/RelationInput.js +59 -43
  11. package/admin/src/content-manager/hooks/useRelation/useRelation.js +84 -86
  12. package/admin/src/content-manager/pages/ListView/index.js +1 -0
  13. package/admin/src/content-manager/utils/schema.js +22 -0
  14. package/admin/src/index.js +7 -1
  15. package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +1 -1
  16. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +0 -1
  17. package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +1 -1
  18. package/admin/src/translations/en.json +17 -0
  19. package/build/{1049.bf5230e6.chunk.js → 1049.acb0e730.chunk.js} +1 -1
  20. package/build/{1227.4f48119b.chunk.js → 1227.969e24e6.chunk.js} +1 -1
  21. package/build/{1386.8c070d59.chunk.js → 1386.db9a2795.chunk.js} +1 -1
  22. package/build/{2225.73c9a224.chunk.js → 2225.78fb9b89.chunk.js} +2 -2
  23. package/build/{2379.27c6ab54.chunk.js → 2379.906334f0.chunk.js} +1 -1
  24. package/build/{2395.120256d6.chunk.js → 2395.f6ac2863.chunk.js} +1 -1
  25. package/build/2614.3e088d3e.chunk.js +35 -0
  26. package/build/{2801.fdd4d8a2.chunk.js → 2801.2afb4757.chunk.js} +1 -1
  27. package/build/{4174.924ebd4c.chunk.js → 4174.3e13fb26.chunk.js} +1 -1
  28. package/build/4546.1203ac95.chunk.js +1 -0
  29. package/build/{502.d26ea06b.chunk.js → 502.9918bff7.chunk.js} +1 -1
  30. package/build/{6266.c652bdb1.chunk.js → 6266.e8990811.chunk.js} +5 -5
  31. package/build/6812.00ef5b0d.chunk.js +26 -0
  32. package/build/{7464.d3d1414f.chunk.js → 7464.0280cf59.chunk.js} +1 -1
  33. package/build/{7897.cf22d5fe.chunk.js → 7897.4a39de37.chunk.js} +1 -1
  34. package/build/{8276.b55f4600.chunk.js → 8276.951e198e.chunk.js} +2 -2
  35. package/build/9832.65ed5a44.chunk.js +181 -0
  36. package/build/{Admin-authenticatedApp.a687d9c6.chunk.js → Admin-authenticatedApp.c0c1c027.chunk.js} +2 -2
  37. package/build/{Admin_InternalErrorPage.18ba7fd6.chunk.js → Admin_InternalErrorPage.b3163562.chunk.js} +1 -1
  38. package/build/{Admin_homePage.86c8d4c9.chunk.js → Admin_homePage.6cb51f18.chunk.js} +1 -1
  39. package/build/{Admin_marketplace.e75c9cf4.chunk.js → Admin_marketplace.3eb5e132.chunk.js} +4 -4
  40. package/build/{Admin_pluginsPage.d1afbf28.chunk.js → Admin_pluginsPage.b9fa2947.chunk.js} +1 -1
  41. package/build/{Admin_profilePage.079903a3.chunk.js → Admin_profilePage.a4d41380.chunk.js} +2 -2
  42. package/build/{Admin_settingsPage.60a3e46e.chunk.js → Admin_settingsPage.6dc2af9f.chunk.js} +1 -1
  43. package/build/{Upload_ConfigureTheView.949535b8.chunk.js → Upload_ConfigureTheView.cc7ca628.chunk.js} +1 -1
  44. package/build/{admin-app.4654dc77.chunk.js → admin-app.98cdf43a.chunk.js} +5 -5
  45. package/build/{admin-edit-roles-page.6597d934.chunk.js → admin-edit-roles-page.418bb1c5.chunk.js} +3 -3
  46. package/build/{admin-edit-users.3014605e.chunk.js → admin-edit-users.9b42cc9e.chunk.js} +2 -2
  47. package/build/{admin-roles-list.ab6fcfb7.chunk.js → admin-roles-list.cf964578.chunk.js} +1 -1
  48. package/build/{admin-users.81bf5f4d.chunk.js → admin-users.8385dd73.chunk.js} +2 -2
  49. package/build/{api-tokens-create-page.f5a85725.chunk.js → api-tokens-create-page.2f25ddf6.chunk.js} +1 -1
  50. package/build/{api-tokens-edit-page.06a32cef.chunk.js → api-tokens-edit-page.45faac16.chunk.js} +1 -1
  51. package/build/{api-tokens-list-page.bb27d544.chunk.js → api-tokens-list-page.5baabf1a.chunk.js} +2 -2
  52. package/build/{audit-logs-settings-page.4eb6cdf8.chunk.js → audit-logs-settings-page.91489670.chunk.js} +1 -1
  53. package/build/content-manager.0d2b4a60.chunk.js +1199 -0
  54. package/build/{content-type-builder-list-view.b75fb938.chunk.js → content-type-builder-list-view.aa8a5d1a.chunk.js} +2 -2
  55. package/build/content-type-builder-translation-en-json.b9e5cacd.chunk.js +1 -0
  56. package/build/content-type-builder.885f2cad.chunk.js +146 -0
  57. package/build/{email-settings-page.07d417d5.chunk.js → email-settings-page.6bd7b280.chunk.js} +1 -1
  58. package/build/{en-json.e12fd5fc.chunk.js → en-json.a3973ff5.chunk.js} +1 -1
  59. package/build/{i18n-settings-page.7107e28a.chunk.js → i18n-settings-page.6c0157e7.chunk.js} +1 -1
  60. package/build/index.html +1 -1
  61. package/build/main.105dcf23.js +2665 -0
  62. package/build/{review-workflows-settings-create-view.5d8806b2.chunk.js → review-workflows-settings-create-view.ae369a88.chunk.js} +1 -1
  63. package/build/{review-workflows-settings-edit-view.634903ed.chunk.js → review-workflows-settings-edit-view.9a61c69f.chunk.js} +1 -1
  64. package/build/{review-workflows-settings-list-view.d138c3b5.chunk.js → review-workflows-settings-list-view.067e0c35.chunk.js} +2 -2
  65. package/build/{runtime~main.9589b498.js → runtime~main.6c489074.js} +2 -2
  66. package/build/{sso-settings-page.caa35f7b.chunk.js → sso-settings-page.a29e6c38.chunk.js} +1 -1
  67. package/build/{transfer-tokens-create-page.6e7049ff.chunk.js → transfer-tokens-create-page.6e1b8cee.chunk.js} +1 -1
  68. package/build/{transfer-tokens-edit-page.449b4502.chunk.js → transfer-tokens-edit-page.10bb22e2.chunk.js} +1 -1
  69. package/build/{transfer-tokens-list-page.34caf827.chunk.js → transfer-tokens-list-page.0306652c.chunk.js} +2 -2
  70. package/build/{upload-settings.fede24b9.chunk.js → upload-settings.0af6edc5.chunk.js} +1 -1
  71. package/build/{upload.a96e2452.chunk.js → upload.19e14c8e.chunk.js} +1 -1
  72. package/build/{users-advanced-settings-page.ac7968e7.chunk.js → users-advanced-settings-page.ed69812f.chunk.js} +1 -1
  73. package/build/{users-email-settings-page.125a89e2.chunk.js → users-email-settings-page.131a00fb.chunk.js} +1 -1
  74. package/build/{users-providers-settings-page.ce34951c.chunk.js → users-providers-settings-page.b3dca41d.chunk.js} +1 -1
  75. package/build/{users-roles-settings-page.d415835a.chunk.js → users-roles-settings-page.afab5a0d.chunk.js} +4 -4
  76. package/build/{webhook-edit-page.7498417e.chunk.js → webhook-edit-page.4c037da4.chunk.js} +2 -2
  77. package/build/{webhook-list-page.1b085c7f.chunk.js → webhook-list-page.56c82f4a.chunk.js} +1 -1
  78. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
  79. package/ee/server/bootstrap.js +1 -1
  80. package/ee/server/content-types/workflow-stage/index.js +1 -1
  81. package/ee/server/controllers/admin.js +1 -1
  82. package/ee/server/controllers/user.js +1 -1
  83. package/ee/server/destroy.js +1 -1
  84. package/ee/server/register.js +1 -1
  85. package/ee/server/routes/utils.js +1 -1
  86. package/ee/server/services/audit-logs.js +1 -1
  87. package/ee/server/services/passport/sso.js +1 -1
  88. package/ee/server/services/passport.js +1 -1
  89. package/ee/server/services/seat-enforcement.js +1 -1
  90. package/ee/server/utils/sso-lock.js +1 -1
  91. package/ee/server/validation/role.js +1 -1
  92. package/ee/server/validation/user.js +1 -1
  93. package/package.json +20 -12
  94. package/server/controllers/admin.js +1 -1
  95. package/server/domain/permission/index.js +8 -1
  96. package/server/validation/permission.js +1 -1
  97. package/utils/plugins.js +7 -1
  98. package/build/4546.9710f321.chunk.js +0 -1
  99. package/build/6272.4017459a.chunk.js +0 -160
  100. package/build/6812.31979984.chunk.js +0 -26
  101. package/build/content-manager.9187db78.chunk.js +0 -1097
  102. package/build/content-type-builder-translation-en-json.ed29ff4d.chunk.js +0 -1
  103. package/build/content-type-builder.5ff93edd.chunk.js +0 -170
  104. package/build/main.da000219.js +0 -2860
@@ -31,7 +31,7 @@ const RBACProvider = ({ children, permissions, refetchPermissions }) => {
31
31
  };
32
32
 
33
33
  RBACProvider.propTypes = {
34
- children: PropTypes.element.isRequired,
34
+ children: PropTypes.node.isRequired,
35
35
  permissions: PropTypes.array.isRequired,
36
36
  refetchPermissions: PropTypes.func.isRequired,
37
37
  };
@@ -0,0 +1,87 @@
1
+ import * as React from 'react';
2
+
3
+ import PropTypes from 'prop-types';
4
+ import { Editable, useSlate } from 'slate-react';
5
+ import { useTheme } from 'styled-components';
6
+
7
+ import { useBlocksStore } from '../hooks/useBlocksStore';
8
+ import { useModifiersStore } from '../hooks/useModifiersStore';
9
+
10
+ const getEditorStyle = (theme) => ({
11
+ // The outline style is set on the wrapper with :focus-within
12
+ outline: 'none',
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ gap: theme.spaces[2],
16
+ });
17
+
18
+ const baseRenderLeaf = (props, modifiers) => {
19
+ // Recursively wrap the children for each active modifier
20
+ const wrappedChildren = Object.entries(modifiers).reduce((currentChildren, modifierEntry) => {
21
+ const [name, modifier] = modifierEntry;
22
+
23
+ if (props.leaf[name]) {
24
+ return modifier.renderLeaf(currentChildren);
25
+ }
26
+
27
+ return currentChildren;
28
+ }, props.children);
29
+
30
+ return <span {...props.attributes}>{wrappedChildren}</span>;
31
+ };
32
+
33
+ const baseRenderElement = (props, blocks) => {
34
+ const blockMatch = Object.values(blocks).find((block) => block.matchNode(props.element));
35
+ const block = blockMatch || blocks.paragraph;
36
+
37
+ return block.renderElement(props);
38
+ };
39
+
40
+ const BlocksInput = ({ readOnly }) => {
41
+ const theme = useTheme();
42
+ const editor = useSlate();
43
+
44
+ // Create renderLeaf function based on the modifiers store
45
+ const modifiers = useModifiersStore();
46
+ const renderLeaf = React.useCallback((props) => baseRenderLeaf(props, modifiers), [modifiers]);
47
+
48
+ // Create renderElement function base on the blocks store
49
+ const blocks = useBlocksStore();
50
+ const renderElement = React.useCallback((props) => baseRenderElement(props, blocks), [blocks]);
51
+
52
+ const handleEnter = () => {
53
+ const selectedNode = editor.children[editor.selection.anchor.path[0]];
54
+ const selectedBlock = Object.values(blocks).find((block) => block.matchNode(selectedNode));
55
+
56
+ // Check if there's an enter handler for the selected block
57
+ if (selectedBlock.handleEnterKey) {
58
+ selectedBlock.handleEnterKey(editor);
59
+ } else {
60
+ // If not, insert a new paragraph
61
+ blocks.paragraph.handleEnterKey(editor);
62
+ }
63
+ };
64
+
65
+ const handleKeyDown = (event) => {
66
+ if (event.key === 'Enter') {
67
+ event.preventDefault();
68
+ handleEnter();
69
+ }
70
+ };
71
+
72
+ return (
73
+ <Editable
74
+ readOnly={readOnly}
75
+ style={getEditorStyle(theme)}
76
+ renderElement={renderElement}
77
+ renderLeaf={renderLeaf}
78
+ onKeyDown={handleKeyDown}
79
+ />
80
+ );
81
+ };
82
+
83
+ BlocksInput.propTypes = {
84
+ readOnly: PropTypes.bool.isRequired,
85
+ };
86
+
87
+ export default BlocksInput;
@@ -0,0 +1,463 @@
1
+ import * as React from 'react';
2
+
3
+ import * as Toolbar from '@radix-ui/react-toolbar';
4
+ import { Flex, Icon, Tooltip, Select, Option, Box, Typography } from '@strapi/design-system';
5
+ import { pxToRem, prefixFileUrlWithBackendUrl, useLibrary } from '@strapi/helper-plugin';
6
+ import { BulletList, NumberList } from '@strapi/icons';
7
+ import PropTypes from 'prop-types';
8
+ import { useIntl } from 'react-intl';
9
+ import { Editor, Transforms, Element as SlateElement } from 'slate';
10
+ import { useSlate } from 'slate-react';
11
+ import styled from 'styled-components';
12
+
13
+ import { useBlocksStore } from '../hooks/useBlocksStore';
14
+ import { useModifiersStore } from '../hooks/useModifiersStore';
15
+
16
+ const Separator = styled(Toolbar.Separator)`
17
+ background: ${({ theme }) => theme.colors.neutral150};
18
+ width: 1px;
19
+ height: ${pxToRem(24)};
20
+ `;
21
+
22
+ const FlexButton = styled(Flex).attrs({ as: 'button' })`
23
+ &:hover {
24
+ background: ${({ theme }) => theme.colors.primary100};
25
+ }
26
+ `;
27
+
28
+ const ToolbarButton = ({ icon, name, label, isActive, handleClick }) => {
29
+ const { formatMessage } = useIntl();
30
+ const labelMessage = formatMessage(label);
31
+
32
+ return (
33
+ <Tooltip description={labelMessage}>
34
+ <Toolbar.ToggleItem value={name} data-state={isActive ? 'on' : 'off'} asChild>
35
+ <FlexButton
36
+ background={isActive ? 'primary100' : ''}
37
+ alignItems="center"
38
+ justifyContent="center"
39
+ width={7}
40
+ height={7}
41
+ hasRadius
42
+ onMouseDown={(e) => {
43
+ e.preventDefault();
44
+ handleClick();
45
+ }}
46
+ aria-label={labelMessage}
47
+ >
48
+ <Icon width={3} height={3} as={icon} color={isActive ? 'primary600' : 'neutral600'} />
49
+ </FlexButton>
50
+ </Toolbar.ToggleItem>
51
+ </Tooltip>
52
+ );
53
+ };
54
+
55
+ ToolbarButton.propTypes = {
56
+ icon: PropTypes.elementType.isRequired,
57
+ name: PropTypes.string.isRequired,
58
+ label: PropTypes.shape({
59
+ id: PropTypes.string.isRequired,
60
+ defaultMessage: PropTypes.string.isRequired,
61
+ }).isRequired,
62
+ isActive: PropTypes.bool.isRequired,
63
+ handleClick: PropTypes.func.isRequired,
64
+ };
65
+
66
+ const ModifierButton = ({ icon, name, label }) => {
67
+ const editor = useSlate();
68
+
69
+ const isModifierActive = () => {
70
+ const modifiers = Editor.marks(editor);
71
+
72
+ if (!modifiers) return false;
73
+
74
+ return Boolean(modifiers[name]);
75
+ };
76
+
77
+ const isActive = isModifierActive();
78
+
79
+ const toggleModifier = () => {
80
+ if (isActive) {
81
+ Editor.removeMark(editor, name);
82
+ } else {
83
+ Editor.addMark(editor, name, true);
84
+ }
85
+ };
86
+
87
+ return (
88
+ <ToolbarButton
89
+ icon={icon}
90
+ name={name}
91
+ label={label}
92
+ isActive={isActive}
93
+ handleClick={toggleModifier}
94
+ />
95
+ );
96
+ };
97
+
98
+ ModifierButton.propTypes = {
99
+ icon: PropTypes.elementType.isRequired,
100
+ name: PropTypes.string.isRequired,
101
+ label: PropTypes.shape({
102
+ id: PropTypes.string.isRequired,
103
+ defaultMessage: PropTypes.string.isRequired,
104
+ }).isRequired,
105
+ };
106
+
107
+ const isBlockActive = (editor, matchNode) => {
108
+ const { selection } = editor;
109
+
110
+ if (!selection) return false;
111
+
112
+ const match = Array.from(
113
+ Editor.nodes(editor, {
114
+ at: Editor.unhangRange(editor, selection),
115
+ match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && matchNode(n),
116
+ })
117
+ );
118
+
119
+ return match.length > 0;
120
+ };
121
+
122
+ const toggleBlock = (editor, value) => {
123
+ const { type, level } = value;
124
+
125
+ const newProperties = {
126
+ type,
127
+ level: level || null,
128
+ };
129
+
130
+ Transforms.setNodes(editor, newProperties);
131
+ };
132
+
133
+ const ALLOWED_MEDIA_TYPE = 'images';
134
+
135
+ const IMAGE_SCHEMA_FIELDS = [
136
+ 'name',
137
+ 'alternativeText',
138
+ 'url',
139
+ 'caption',
140
+ 'width',
141
+ 'height',
142
+ 'formats',
143
+ 'hash',
144
+ 'ext',
145
+ 'mime',
146
+ 'size',
147
+ 'previewUrl',
148
+ 'provider',
149
+ 'provider_metadata',
150
+ 'createdAt',
151
+ 'updatedAt',
152
+ ];
153
+
154
+ const pick = (object, imageSchemaFields) => {
155
+ return Object.keys(object).reduce((acc, key) => {
156
+ if (imageSchemaFields.includes(key)) {
157
+ acc[key] = object[key];
158
+ }
159
+
160
+ return acc;
161
+ }, {});
162
+ };
163
+
164
+ const ImageDialog = ({ handleClose }) => {
165
+ const editor = useSlate();
166
+ const { components } = useLibrary();
167
+ const MediaLibraryDialog = components['media-library'];
168
+
169
+ const insertImages = (images) => {
170
+ images.forEach((img) => {
171
+ const image = { type: 'image', image: img, children: [{ type: 'text', text: '' }] };
172
+ Transforms.insertNodes(editor, image);
173
+ });
174
+ };
175
+
176
+ const handleSelectAssets = (images) => {
177
+ const formattedImages = images.map((image) => {
178
+ // create an object with imageSchema defined and exclude unnecessary props coming from media library config
179
+ const expectedImage = pick(image, IMAGE_SCHEMA_FIELDS);
180
+
181
+ return {
182
+ ...expectedImage,
183
+ alternativeText: expectedImage.alternativeText || expectedImage.name,
184
+ url: prefixFileUrlWithBackendUrl(image.url),
185
+ };
186
+ });
187
+
188
+ insertImages(formattedImages);
189
+ handleClose();
190
+ };
191
+
192
+ return (
193
+ <MediaLibraryDialog
194
+ allowedTypes={[ALLOWED_MEDIA_TYPE]}
195
+ onClose={handleClose}
196
+ onSelectAssets={handleSelectAssets}
197
+ />
198
+ );
199
+ };
200
+
201
+ ImageDialog.propTypes = {
202
+ handleClose: PropTypes.func.isRequired,
203
+ };
204
+
205
+ const isLastBlockImageOrCode = (editor) => {
206
+ const { selection } = editor;
207
+
208
+ if (!selection) return false;
209
+
210
+ const [currentImageORCodeBlock] = Editor.nodes(editor, {
211
+ at: selection,
212
+ match: (n) => n.type === 'image' || n.type === 'code',
213
+ });
214
+
215
+ if (currentImageORCodeBlock) {
216
+ const [, currentNodePath] = currentImageORCodeBlock;
217
+
218
+ const isNodeAfter = Boolean(Editor.after(editor, currentNodePath));
219
+
220
+ return !isNodeAfter;
221
+ }
222
+
223
+ return false;
224
+ };
225
+
226
+ export const BlocksDropdown = () => {
227
+ const editor = useSlate();
228
+ const { formatMessage } = useIntl();
229
+ const [isMediaLibraryVisible, setIsMediaLibraryVisible] = React.useState(false);
230
+
231
+ const blocks = useBlocksStore();
232
+ const blockKeysToInclude = Object.entries(blocks).reduce((currentKeys, entry) => {
233
+ const [key, block] = entry;
234
+
235
+ return block.isInBlocksSelector ? [...currentKeys, key] : currentKeys;
236
+ }, []);
237
+
238
+ const [blockSelected, setBlockSelected] = React.useState(Object.keys(blocks)[0]);
239
+
240
+ /**
241
+ * @param {string} optionKey - key of the heading selected
242
+ */
243
+ const selectOption = (optionKey) => {
244
+ toggleBlock(editor, blocks[optionKey].value);
245
+
246
+ setBlockSelected(optionKey);
247
+
248
+ if (isLastBlockImageOrCode(editor)) {
249
+ // insert blank line to add new blocks below code or image blocks
250
+ Transforms.insertNodes(
251
+ editor,
252
+ {
253
+ type: 'paragraph',
254
+ children: [{ type: 'text', text: '' }],
255
+ },
256
+ { at: [editor.children.length] }
257
+ );
258
+ }
259
+
260
+ if (optionKey === 'image') {
261
+ setIsMediaLibraryVisible(true);
262
+ }
263
+ };
264
+
265
+ return (
266
+ <>
267
+ <Select
268
+ startIcon={<Icon as={blocks[blockSelected].icon} />}
269
+ onChange={selectOption}
270
+ placeholder={blocks[blockSelected].label}
271
+ value={blockSelected}
272
+ aria-label={formatMessage({
273
+ id: 'components.Blocks.blocks.selectBlock',
274
+ defaultMessage: 'Select a block',
275
+ })}
276
+ >
277
+ {blockKeysToInclude.map((key) => (
278
+ <BlockOption
279
+ key={key}
280
+ value={key}
281
+ label={blocks[key].label}
282
+ icon={blocks[key].icon}
283
+ matchNode={blocks[key].matchNode}
284
+ handleSelection={setBlockSelected}
285
+ blockSelected={blockSelected}
286
+ />
287
+ ))}
288
+ </Select>
289
+ {isMediaLibraryVisible && <ImageDialog handleClose={() => setIsMediaLibraryVisible(false)} />}
290
+ </>
291
+ );
292
+ };
293
+
294
+ const BlockOption = ({ value, icon, label, handleSelection, blockSelected, matchNode }) => {
295
+ const { formatMessage } = useIntl();
296
+ const editor = useSlate();
297
+
298
+ const isActive = isBlockActive(editor, matchNode);
299
+ const isSelected = value === blockSelected;
300
+
301
+ React.useEffect(() => {
302
+ if (isActive && !isSelected) {
303
+ handleSelection(value);
304
+ }
305
+ }, [handleSelection, isActive, isSelected, value]);
306
+
307
+ return (
308
+ <Option
309
+ startIcon={<Icon as={icon} color={isSelected ? 'primary600' : 'neutral600'} />}
310
+ value={value}
311
+ >
312
+ {formatMessage(label)}
313
+ </Option>
314
+ );
315
+ };
316
+
317
+ BlockOption.propTypes = {
318
+ icon: PropTypes.elementType.isRequired,
319
+ value: PropTypes.string.isRequired,
320
+ label: PropTypes.shape({
321
+ id: PropTypes.string.isRequired,
322
+ defaultMessage: PropTypes.string.isRequired,
323
+ }).isRequired,
324
+ matchNode: PropTypes.func.isRequired,
325
+ handleSelection: PropTypes.func.isRequired,
326
+ blockSelected: PropTypes.string.isRequired,
327
+ };
328
+
329
+ const ListButton = ({ icon, format, label }) => {
330
+ const editor = useSlate();
331
+
332
+ /**
333
+ *
334
+ * @param {import('slate').Node} node
335
+ * @returns boolean
336
+ */
337
+ const isListNode = (node) => {
338
+ return !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === 'list';
339
+ };
340
+
341
+ const isListActive = () => {
342
+ const { selection } = editor;
343
+
344
+ if (!selection) return false;
345
+
346
+ const [match] = Array.from(
347
+ Editor.nodes(editor, {
348
+ at: Editor.unhangRange(editor, selection),
349
+ match: (node) => isListNode(node) && node.format === format,
350
+ })
351
+ );
352
+
353
+ return Boolean(match);
354
+ };
355
+
356
+ const isActive = isListActive();
357
+
358
+ const toggleList = () => {
359
+ // Delete the parent list so that we're left with only the list items directly
360
+ Transforms.unwrapNodes(editor, {
361
+ match: (node) => isListNode(node) && ['ordered', 'unordered'].includes(node.format),
362
+ split: true,
363
+ });
364
+
365
+ // Change the type of the current selection
366
+ Transforms.setNodes(editor, {
367
+ type: isActive ? 'paragraph' : 'list-item',
368
+ });
369
+
370
+ // If the selection is now a list item, wrap it inside a list
371
+ if (!isActive) {
372
+ const block = { type: 'list', format, children: [] };
373
+ Transforms.wrapNodes(editor, block);
374
+ }
375
+ };
376
+
377
+ return (
378
+ <ToolbarButton
379
+ icon={icon}
380
+ name={format}
381
+ label={label}
382
+ isActive={isActive}
383
+ handleClick={toggleList}
384
+ />
385
+ );
386
+ };
387
+
388
+ ListButton.propTypes = {
389
+ icon: PropTypes.elementType.isRequired,
390
+ format: PropTypes.string.isRequired,
391
+ label: PropTypes.shape({
392
+ id: PropTypes.string.isRequired,
393
+ defaultMessage: PropTypes.string.isRequired,
394
+ }).isRequired,
395
+ };
396
+
397
+ // TODO: Remove after the RTE Blocks Alpha release
398
+ const AlphaTag = styled(Box)`
399
+ background-color: ${({ theme }) => theme.colors.warning100};
400
+ border: ${({ theme }) => `1px solid ${theme.colors.warning200}`};
401
+ border-radius: ${({ theme }) => theme.borderRadius};
402
+ font-size: ${({ theme }) => theme.fontSizes[0]};
403
+ padding: ${({ theme }) => `${2 / 16}rem ${theme.spaces[1]}`};
404
+ `;
405
+
406
+ const BlocksToolbar = () => {
407
+ const modifiers = useModifiersStore();
408
+
409
+ return (
410
+ <Toolbar.Root asChild>
411
+ {/* Remove after the RTE Blocks Alpha release (paddingRight and width) */}
412
+ <Flex gap={1} padding={2} paddingRight={4} width="100%">
413
+ <BlocksDropdown />
414
+ <Separator />
415
+ <Toolbar.ToggleGroup type="multiple" asChild>
416
+ <Flex gap={1}>
417
+ {Object.entries(modifiers).map(([name, modifier]) => (
418
+ <ToolbarButton
419
+ key={name}
420
+ name={name}
421
+ icon={modifier.icon}
422
+ label={modifier.label}
423
+ isActive={modifier.checkIsActive()}
424
+ handleClick={modifier.handleToggle}
425
+ />
426
+ ))}
427
+ </Flex>
428
+ </Toolbar.ToggleGroup>
429
+ <Separator />
430
+ <Toolbar.ToggleGroup type="single" asChild>
431
+ <Flex gap={1}>
432
+ <ListButton
433
+ label={{
434
+ id: 'components.Blocks.blocks.unorderedList',
435
+ defaultMessage: 'Bulleted list',
436
+ }}
437
+ format="unordered"
438
+ icon={BulletList}
439
+ />
440
+ <ListButton
441
+ label={{
442
+ id: 'components.Blocks.blocks.orderedList',
443
+ defaultMessage: 'Numbered list',
444
+ }}
445
+ format="ordered"
446
+ icon={NumberList}
447
+ />
448
+ </Flex>
449
+ </Toolbar.ToggleGroup>
450
+ {/* TODO: Remove after the RTE Blocks Alpha release */}
451
+ <Flex grow={1} justifyContent="flex-end">
452
+ <AlphaTag>
453
+ <Typography textColor="warning600" variant="sigma">
454
+ ALPHA
455
+ </Typography>
456
+ </AlphaTag>
457
+ </Flex>
458
+ </Flex>
459
+ </Toolbar.Root>
460
+ );
461
+ };
462
+
463
+ export { BlocksToolbar };