@squiz/formatted-text-editor 2.2.1 → 2.2.2
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 +6 -0
- package/demo/App.tsx +3 -0
- package/demo/index.scss +4 -0
- package/lib/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.js +79 -13
- package/lib/index.css +74 -1
- package/package.json +5 -4
- package/src/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.spec.tsx +38 -0
- package/src/EditorToolbar/Tools/ContentTools/ContentToolsDropdown.tsx +126 -27
- package/src/EditorToolbar/_toolbar.scss +6 -0
- package/src/index.scss +1 -0
package/CHANGELOG.md
CHANGED
package/demo/App.tsx
CHANGED
@@ -7,6 +7,7 @@ import Button from '../src/ui/Button/Button';
|
|
7
7
|
import TextFieldsOutlinedIcon from '@mui/icons-material/TextFieldsOutlined';
|
8
8
|
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
9
9
|
import { VerticalDivider } from '@remirror/react-components';
|
10
|
+
import { Dialog, useDialogStore } from '@squiz/sds';
|
10
11
|
const ComponentHandlers = () => (
|
11
12
|
<div style={{ display: 'flex', justifyContent: 'flex-end', maxHeight: '2rem' }}>
|
12
13
|
<Button icon={<TextFieldsOutlinedIcon />} onClick={(x) => x} />
|
@@ -24,6 +25,7 @@ function App() {
|
|
24
25
|
const [editable, setEditable] = useState(true);
|
25
26
|
const [enableTableTool, setEnableTableTool] = useState(true);
|
26
27
|
const [border, setBorder] = useState(true);
|
28
|
+
const { activeDialog } = useDialogStore();
|
27
29
|
|
28
30
|
const handleEditorChange: RemirrorEventListener<Extension> = (parameter) => {
|
29
31
|
try {
|
@@ -105,6 +107,7 @@ function App() {
|
|
105
107
|
<ReactDiffViewer oldValue={squizDoc} newValue={squizDoc} splitView={false} showDiffOnly={false} />
|
106
108
|
</div>
|
107
109
|
</div>
|
110
|
+
{activeDialog && <Dialog {...activeDialog} />}
|
108
111
|
</div>
|
109
112
|
);
|
110
113
|
}
|
package/demo/index.scss
CHANGED
@@ -8,28 +8,94 @@ const AiIcon_1 = require("../../../Icons/AiIcon");
|
|
8
8
|
const sds_1 = require("@squiz/sds");
|
9
9
|
const dxp_ai_client_react_1 = require("@squiz/dxp-ai-client-react");
|
10
10
|
const react_2 = require("@remirror/react");
|
11
|
+
const dxp_content_tools_modal_1 = require("@squiz/dxp-content-tools-modal");
|
12
|
+
const model_1 = require("@remirror/pm/model");
|
13
|
+
const prosemirror_model_1 = require("prosemirror-model");
|
11
14
|
const ContentToolsDropdown = () => {
|
12
|
-
const
|
13
|
-
const
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
key: item.id,
|
18
|
-
label: react_1.default.createElement("span", null, item.name),
|
19
|
-
},
|
20
|
-
],
|
21
|
-
key: item.id,
|
22
|
-
}));
|
15
|
+
const aiService = (0, dxp_ai_client_react_1.useAiService)();
|
16
|
+
const { contentTools } = aiService;
|
17
|
+
const { updateActiveDialog } = (0, sds_1.useDialogStore)();
|
18
|
+
const { isSelectionEmpty, getHTML } = (0, react_2.useHelpers)();
|
19
|
+
const { getState, view } = (0, react_2.useRemirrorContext)();
|
23
20
|
// No content tools to show, don't show dropdown at all
|
24
21
|
if (!contentTools || contentTools?.length === 0) {
|
25
22
|
return null;
|
26
23
|
}
|
24
|
+
const hasContent = getState().doc.textContent.length > 0;
|
25
|
+
const getSelectedRichText = () => {
|
26
|
+
const { from, to } = getState().selection;
|
27
|
+
const fragment = getState().doc.slice(from, to).content;
|
28
|
+
const div = document.createElement('div');
|
29
|
+
fragment.forEach((node) => {
|
30
|
+
div.appendChild(model_1.DOMSerializer.fromSchema(getState().schema).serializeNode(node));
|
31
|
+
});
|
32
|
+
return div.innerHTML;
|
33
|
+
};
|
34
|
+
const onInsertAfter = (content) => {
|
35
|
+
const schema = getState().schema;
|
36
|
+
const doc = getState().doc;
|
37
|
+
const tr = getState().tr;
|
38
|
+
// Parse the HTML string into a document fragment
|
39
|
+
const element = document.createElement('div');
|
40
|
+
element.innerHTML = content;
|
41
|
+
const newContent = prosemirror_model_1.DOMParser.fromSchema(schema).parse(element);
|
42
|
+
// Append the new content to the existing document
|
43
|
+
const newTransaction = tr.insert(doc.content.size, newContent.content);
|
44
|
+
view.dispatch(newTransaction);
|
45
|
+
};
|
46
|
+
const onReplace = (content) => {
|
47
|
+
const schema = getState().schema;
|
48
|
+
const { from, to, empty } = getState().selection;
|
49
|
+
const { tr } = getState();
|
50
|
+
// Parse the HTML string into a document fragment
|
51
|
+
const element = document.createElement('div');
|
52
|
+
element.innerHTML = content;
|
53
|
+
const newContent = prosemirror_model_1.DOMParser.fromSchema(schema).parse(element);
|
54
|
+
if (empty) {
|
55
|
+
// If there is no selection, replace the entire document content
|
56
|
+
const newTransaction = tr.replaceWith(0, getState().doc.content.size, newContent.content);
|
57
|
+
view.dispatch(newTransaction);
|
58
|
+
}
|
59
|
+
else {
|
60
|
+
// Replace the selected content with the new content
|
61
|
+
const newTransaction = tr.replaceWith(from, to, newContent.content);
|
62
|
+
view.dispatch(newTransaction);
|
63
|
+
}
|
64
|
+
};
|
65
|
+
const dropdownItems = contentTools?.map((item) => {
|
66
|
+
return {
|
67
|
+
items: [
|
68
|
+
{
|
69
|
+
action: () => {
|
70
|
+
// If we don't have a selection, use all text
|
71
|
+
const richContent = isSelectionEmpty() ? getHTML() : getSelectedRichText(); // NOTE: selected rich text may not work as expected
|
72
|
+
const dialogProps = {
|
73
|
+
dialogContent: {
|
74
|
+
renderContent: (dialogContentProps) => {
|
75
|
+
return (react_1.default.createElement(dxp_content_tools_modal_1.ModalContent, { aiServiceOverride: aiService, content: richContent, contentTool: item, dialogContentProps: dialogContentProps, onInsertAfter: onInsertAfter, onReplace: onReplace }));
|
76
|
+
},
|
77
|
+
},
|
78
|
+
dialogSize: sds_1.DIALOG_SIZE_XL,
|
79
|
+
heading: `Rewrite content to ... ${item.name}`,
|
80
|
+
icon: dxp_content_tools_modal_1.ICON_DXP_AI,
|
81
|
+
stateHandler: sds_1.useDialogStore,
|
82
|
+
};
|
83
|
+
updateActiveDialog(dialogProps);
|
84
|
+
},
|
85
|
+
key: item.id,
|
86
|
+
label: react_1.default.createElement("span", null, item.name),
|
87
|
+
},
|
88
|
+
],
|
89
|
+
key: item.id,
|
90
|
+
};
|
91
|
+
});
|
27
92
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
28
93
|
react_1.default.createElement(react_2.VerticalDivider, null),
|
29
|
-
react_1.default.createElement(sds_1.Dropdown, { title: "Content tools", "aria-label": "Content tools", buttonProps: {
|
94
|
+
hasContent && (react_1.default.createElement(sds_1.Dropdown, { title: "Content tools", "aria-label": "Content tools", buttonProps: {
|
30
95
|
format: sds_1.BUTTON_FORMAT_TRANSPARENT,
|
31
96
|
icon: AiIcon_1.ICON_AI,
|
32
97
|
theme: sds_1.BUTTON_THEME_DEFAULT,
|
33
|
-
}, className: "content-tools-dropdown", dropdownPosition: sds_1.DROPDOWN_POSITION_RIGHT, heading: 'Rewrite to...', sections: dropdownItems ?? [] }))
|
98
|
+
}, className: "content-tools-dropdown", dropdownPosition: sds_1.DROPDOWN_POSITION_RIGHT, heading: 'Rewrite to...', sections: dropdownItems ?? [], disabled: !hasContent })),
|
99
|
+
!hasContent && react_1.default.createElement("div", { className: "content-tools-dropdown--disabled" }, AiIcon_1.ICON_AI)));
|
34
100
|
};
|
35
101
|
exports.default = ContentToolsDropdown;
|
package/lib/index.css
CHANGED
@@ -2655,7 +2655,8 @@
|
|
2655
2655
|
.squiz-fte-scope .sds-text-field textarea {
|
2656
2656
|
resize: vertical;
|
2657
2657
|
}
|
2658
|
-
.squiz-fte-scope .sds-text-field .sds-field-error__icon
|
2658
|
+
.squiz-fte-scope .sds-text-field .sds-field-error__icon,
|
2659
|
+
.squiz-fte-scope .sds-text-field .sds-text-field__copy-icon {
|
2659
2660
|
align-items: center;
|
2660
2661
|
display: flex;
|
2661
2662
|
height: 100%;
|
@@ -2664,6 +2665,9 @@
|
|
2664
2665
|
right: 0;
|
2665
2666
|
top: 0;
|
2666
2667
|
}
|
2668
|
+
.squiz-fte-scope .sds-text-field:has(.sds-text-field__copy-icon) input {
|
2669
|
+
padding-right: 2rem;
|
2670
|
+
}
|
2667
2671
|
.squiz-fte-scope .sds-text-field.readonly input,
|
2668
2672
|
.squiz-fte-scope .sds-text-field.readonly textarea {
|
2669
2673
|
background: rgba(0, 0, 0, 0.04);
|
@@ -4179,6 +4183,63 @@
|
|
4179
4183
|
width: 400px;
|
4180
4184
|
}
|
4181
4185
|
|
4186
|
+
/* ../../node_modules/@squiz/dxp-content-tools-modal/lib/package.css */
|
4187
|
+
@keyframes skeleton-pulse {
|
4188
|
+
0% {
|
4189
|
+
opacity: 1;
|
4190
|
+
}
|
4191
|
+
50% {
|
4192
|
+
opacity: 0.45;
|
4193
|
+
}
|
4194
|
+
100% {
|
4195
|
+
opacity: 1;
|
4196
|
+
}
|
4197
|
+
}
|
4198
|
+
.squiz-fte-scope .dxp-ctm-section__header {
|
4199
|
+
align-items: center;
|
4200
|
+
display: flex;
|
4201
|
+
flex-direction: row;
|
4202
|
+
height: 36px;
|
4203
|
+
justify-content: space-between;
|
4204
|
+
margin-bottom: 0.5rem;
|
4205
|
+
}
|
4206
|
+
.squiz-fte-scope .dxp-ctm-section__header h3 {
|
4207
|
+
font-size: 0.96rem;
|
4208
|
+
}
|
4209
|
+
.squiz-fte-scope .dxp-ctm-section__spinner {
|
4210
|
+
display: flex;
|
4211
|
+
justify-content: center;
|
4212
|
+
padding-top: 1.5rem;
|
4213
|
+
}
|
4214
|
+
.squiz-fte-scope .dxp-ctm-content {
|
4215
|
+
align-content: stretch;
|
4216
|
+
align-items: stretch;
|
4217
|
+
display: flex;
|
4218
|
+
flex-flow: row nowrap;
|
4219
|
+
gap: 0.25rem;
|
4220
|
+
}
|
4221
|
+
.squiz-fte-scope .dxp-ctm-content > .dxp-ctm-section {
|
4222
|
+
display: flex;
|
4223
|
+
flex: 1;
|
4224
|
+
flex-direction: column;
|
4225
|
+
padding: 1rem;
|
4226
|
+
}
|
4227
|
+
.squiz-fte-scope .dxp-ctm-content > .dxp-ctm-section > .squiz-fte-scope {
|
4228
|
+
flex-grow: 1;
|
4229
|
+
}
|
4230
|
+
.squiz-fte-scope .dxp-ctm-content > .dxp-ctm-section > .squiz-fte-scope:not(.dxp-ctm-section__spinner) {
|
4231
|
+
background-color: #fff;
|
4232
|
+
}
|
4233
|
+
.squiz-fte-scope .dxp-ctm-section {
|
4234
|
+
background-color: #ededed;
|
4235
|
+
border-radius: 0.5rem;
|
4236
|
+
min-height: 400px;
|
4237
|
+
padding: 0.5rem;
|
4238
|
+
}
|
4239
|
+
.squiz-fte-scope .dxp-ctm-section__header h3 {
|
4240
|
+
margin: 0;
|
4241
|
+
}
|
4242
|
+
|
4182
4243
|
/* src/index.scss */
|
4183
4244
|
.squiz-fte-scope *,
|
4184
4245
|
.squiz-fte-scope ::before,
|
@@ -5376,6 +5437,12 @@
|
|
5376
5437
|
.squiz-fte-scope .editor-toolbar__tools .squiz-fte-btn ~ .squiz-fte-btn {
|
5377
5438
|
margin-left: 2px;
|
5378
5439
|
}
|
5440
|
+
.squiz-fte-scope .editor-toolbar .content-tools-dropdown--disabled,
|
5441
|
+
.squiz-fte-scope__floating-popover .content-tools-dropdown--disabled {
|
5442
|
+
cursor: not-allowed;
|
5443
|
+
filter: grayscale(100%);
|
5444
|
+
padding: 6px 8px;
|
5445
|
+
}
|
5379
5446
|
.squiz-fte-scope .header-toolbar {
|
5380
5447
|
transition-duration: 0.3s;
|
5381
5448
|
transition-property: max-height, opacity;
|
@@ -5746,4 +5813,10 @@
|
|
5746
5813
|
* @license
|
5747
5814
|
* Copyright Squiz Australia Pty Ltd. All Rights Reserved.
|
5748
5815
|
*)
|
5816
|
+
|
5817
|
+
@squiz/dxp-content-tools-modal/lib/package.css:
|
5818
|
+
(*!
|
5819
|
+
* @license
|
5820
|
+
* Copyright Squiz Australia Pty Ltd. All Rights Reserved.
|
5821
|
+
*)
|
5749
5822
|
*/
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "2.2.
|
3
|
+
"version": "2.2.2",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"private": false,
|
@@ -24,12 +24,13 @@
|
|
24
24
|
"@mui/icons-material": "5.15.18",
|
25
25
|
"@remirror/extension-react-tables": "^2.2.19",
|
26
26
|
"@remirror/react": "2.0.35",
|
27
|
+
"@squiz/dam-resource-browser-plugin": "^0.9.0-rc.0",
|
27
28
|
"@squiz/dx-json-schema-lib": "^1.72.0",
|
28
|
-
"@squiz/dxp-ai-client-react": "^0.
|
29
|
+
"@squiz/dxp-ai-client-react": "^0.2.0",
|
30
|
+
"@squiz/dxp-content-tools-modal": "^0.1.0",
|
29
31
|
"@squiz/matrix-resource-browser-plugin": "^2.0.0",
|
30
32
|
"@squiz/resource-browser": "^2.0.0",
|
31
|
-
"@squiz/sds": "^1.0.0-alpha.
|
32
|
-
"@squiz/dam-resource-browser-plugin": "^0.9.0-rc.0",
|
33
|
+
"@squiz/sds": "^1.0.0-alpha.59",
|
33
34
|
"clsx": "2.1.1",
|
34
35
|
"react-hook-form": "7.51.4",
|
35
36
|
"react-image-size": "2.0.0",
|
@@ -10,6 +10,44 @@ jest.mock('@squiz/dxp-ai-client-react', () => ({
|
|
10
10
|
useAiService: jest.fn(),
|
11
11
|
}));
|
12
12
|
|
13
|
+
jest.mock('@remirror/react', () => {
|
14
|
+
const actualModule = jest.requireActual('@remirror/react');
|
15
|
+
|
16
|
+
return {
|
17
|
+
...actualModule,
|
18
|
+
useHelpers: () => ({
|
19
|
+
isSelectionEmpty: jest.fn().mockReturnValue(false),
|
20
|
+
getHTML: jest.fn().mockReturnValue('<strong>test</strong>'),
|
21
|
+
}),
|
22
|
+
useRemirrorContext: () => ({
|
23
|
+
getState: jest.fn().mockReturnValue({
|
24
|
+
doc: {
|
25
|
+
textContent: 'test',
|
26
|
+
slice: jest.fn().mockReturnValue({
|
27
|
+
content: { forEach: jest.fn() },
|
28
|
+
}),
|
29
|
+
},
|
30
|
+
selection: {
|
31
|
+
from: 1,
|
32
|
+
to: 2,
|
33
|
+
},
|
34
|
+
}),
|
35
|
+
view: jest.fn().mockReturnValue({}),
|
36
|
+
}),
|
37
|
+
};
|
38
|
+
});
|
39
|
+
|
40
|
+
jest.mock('@squiz/sds', () => {
|
41
|
+
const actualModule = jest.requireActual('@squiz/sds');
|
42
|
+
|
43
|
+
return {
|
44
|
+
...actualModule,
|
45
|
+
useDialogStore: () => ({
|
46
|
+
updateActiveDialog: jest.fn(),
|
47
|
+
}),
|
48
|
+
};
|
49
|
+
});
|
50
|
+
|
13
51
|
// Cast useAiService to a Jest mock
|
14
52
|
const mockedUseAiService = useAiService as jest.MockedFunction<typeof useAiService>;
|
15
53
|
|
@@ -1,44 +1,143 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { ICON_AI } from '../../../Icons/AiIcon';
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
BUTTON_FORMAT_TRANSPARENT,
|
5
|
+
BUTTON_THEME_DEFAULT,
|
6
|
+
DIALOG_SIZE_XL,
|
7
|
+
DROPDOWN_POSITION_RIGHT,
|
8
|
+
DialogContentProps,
|
9
|
+
DialogProps,
|
10
|
+
Dropdown,
|
11
|
+
DropdownSectionData,
|
12
|
+
useDialogStore,
|
13
|
+
} from '@squiz/sds';
|
4
14
|
import { useAiService } from '@squiz/dxp-ai-client-react';
|
5
|
-
import { VerticalDivider } from '@remirror/react';
|
15
|
+
import { VerticalDivider, useHelpers, useRemirrorContext } from '@remirror/react';
|
16
|
+
import { ICON_DXP_AI, ModalContent } from '@squiz/dxp-content-tools-modal';
|
17
|
+
import { DOMSerializer } from '@remirror/pm/model';
|
18
|
+
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
|
6
19
|
|
7
20
|
const ContentToolsDropdown = () => {
|
8
|
-
const
|
9
|
-
|
10
|
-
const
|
11
|
-
|
12
|
-
|
13
|
-
action: () => alert(JSON.stringify(item, null, 2)),
|
14
|
-
key: item.id,
|
15
|
-
label: <span>{item.name}</span>,
|
16
|
-
},
|
17
|
-
],
|
18
|
-
key: item.id,
|
19
|
-
}));
|
21
|
+
const aiService = useAiService();
|
22
|
+
const { contentTools } = aiService;
|
23
|
+
const { updateActiveDialog } = useDialogStore();
|
24
|
+
const { isSelectionEmpty, getHTML } = useHelpers();
|
25
|
+
const { getState, view } = useRemirrorContext();
|
20
26
|
|
21
27
|
// No content tools to show, don't show dropdown at all
|
22
28
|
if (!contentTools || contentTools?.length === 0) {
|
23
29
|
return null;
|
24
30
|
}
|
25
31
|
|
32
|
+
const hasContent = getState().doc.textContent.length > 0;
|
33
|
+
|
34
|
+
const getSelectedRichText = () => {
|
35
|
+
const { from, to } = getState().selection;
|
36
|
+
const fragment = getState().doc.slice(from, to).content;
|
37
|
+
const div = document.createElement('div');
|
38
|
+
|
39
|
+
fragment.forEach((node) => {
|
40
|
+
div.appendChild(DOMSerializer.fromSchema(getState().schema).serializeNode(node));
|
41
|
+
});
|
42
|
+
|
43
|
+
return div.innerHTML;
|
44
|
+
};
|
45
|
+
|
46
|
+
const onInsertAfter = (content: string) => {
|
47
|
+
const schema = getState().schema;
|
48
|
+
const doc = getState().doc;
|
49
|
+
const tr = getState().tr;
|
50
|
+
|
51
|
+
// Parse the HTML string into a document fragment
|
52
|
+
const element = document.createElement('div');
|
53
|
+
element.innerHTML = content;
|
54
|
+
const newContent = ProseMirrorDOMParser.fromSchema(schema).parse(element);
|
55
|
+
|
56
|
+
// Append the new content to the existing document
|
57
|
+
const newTransaction = tr.insert(doc.content.size, newContent.content);
|
58
|
+
view.dispatch(newTransaction);
|
59
|
+
};
|
60
|
+
|
61
|
+
const onReplace = (content: string) => {
|
62
|
+
const schema = getState().schema;
|
63
|
+
const { from, to, empty } = getState().selection;
|
64
|
+
const { tr } = getState();
|
65
|
+
|
66
|
+
// Parse the HTML string into a document fragment
|
67
|
+
const element = document.createElement('div');
|
68
|
+
element.innerHTML = content;
|
69
|
+
const newContent = ProseMirrorDOMParser.fromSchema(schema).parse(element);
|
70
|
+
|
71
|
+
if (empty) {
|
72
|
+
// If there is no selection, replace the entire document content
|
73
|
+
const newTransaction = tr.replaceWith(0, getState().doc.content.size, newContent.content);
|
74
|
+
view.dispatch(newTransaction);
|
75
|
+
} else {
|
76
|
+
// Replace the selected content with the new content
|
77
|
+
const newTransaction = tr.replaceWith(from, to, newContent.content);
|
78
|
+
view.dispatch(newTransaction);
|
79
|
+
}
|
80
|
+
};
|
81
|
+
|
82
|
+
const dropdownItems: Array<DropdownSectionData> = contentTools?.map((item) => {
|
83
|
+
return {
|
84
|
+
items: [
|
85
|
+
{
|
86
|
+
action: () => {
|
87
|
+
// If we don't have a selection, use all text
|
88
|
+
const richContent = isSelectionEmpty() ? getHTML() : getSelectedRichText(); // NOTE: selected rich text may not work as expected
|
89
|
+
|
90
|
+
const dialogProps: DialogProps = {
|
91
|
+
dialogContent: {
|
92
|
+
renderContent: (dialogContentProps: DialogContentProps) => {
|
93
|
+
return (
|
94
|
+
<ModalContent
|
95
|
+
aiServiceOverride={aiService}
|
96
|
+
content={richContent}
|
97
|
+
contentTool={item}
|
98
|
+
dialogContentProps={dialogContentProps}
|
99
|
+
onInsertAfter={onInsertAfter}
|
100
|
+
onReplace={onReplace}
|
101
|
+
/>
|
102
|
+
);
|
103
|
+
},
|
104
|
+
},
|
105
|
+
dialogSize: DIALOG_SIZE_XL,
|
106
|
+
heading: `Rewrite content to ... ${item.name}`,
|
107
|
+
icon: ICON_DXP_AI,
|
108
|
+
stateHandler: useDialogStore,
|
109
|
+
};
|
110
|
+
|
111
|
+
updateActiveDialog(dialogProps);
|
112
|
+
},
|
113
|
+
key: item.id,
|
114
|
+
label: <span>{item.name}</span>,
|
115
|
+
},
|
116
|
+
],
|
117
|
+
key: item.id,
|
118
|
+
};
|
119
|
+
});
|
120
|
+
|
26
121
|
return (
|
27
122
|
<>
|
28
123
|
<VerticalDivider />
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
124
|
+
{hasContent && (
|
125
|
+
<Dropdown
|
126
|
+
title="Content tools"
|
127
|
+
aria-label="Content tools"
|
128
|
+
buttonProps={{
|
129
|
+
format: BUTTON_FORMAT_TRANSPARENT,
|
130
|
+
icon: ICON_AI,
|
131
|
+
theme: BUTTON_THEME_DEFAULT,
|
132
|
+
}}
|
133
|
+
className="content-tools-dropdown"
|
134
|
+
dropdownPosition={DROPDOWN_POSITION_RIGHT}
|
135
|
+
heading={'Rewrite to...'}
|
136
|
+
sections={dropdownItems ?? []}
|
137
|
+
disabled={!hasContent}
|
138
|
+
/>
|
139
|
+
)}
|
140
|
+
{!hasContent && <div className="content-tools-dropdown--disabled">{ICON_AI}</div>}
|
42
141
|
</>
|
43
142
|
);
|
44
143
|
};
|
package/src/index.scss
CHANGED