@squiz/formatted-text-editor 1.35.1-alpha.24 → 1.35.1-alpha.26
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/lib/Editor/Editor.js +4 -4
- package/lib/EditorToolbar/FloatingToolbar.js +1 -1
- package/lib/EditorToolbar/Toolbar.d.ts +2 -2
- package/lib/EditorToolbar/Toolbar.js +2 -2
- package/lib/hooks/useFocus.d.ts +6 -4
- package/lib/hooks/useFocus.js +11 -11
- package/lib/index.css +48 -50
- package/package.json +4 -4
- package/src/Editor/Editor.spec.tsx +4 -11
- package/src/Editor/Editor.tsx +6 -5
- package/src/Editor/_editor.scss +2 -2
- package/src/EditorToolbar/FloatingToolbar.tsx +6 -1
- package/src/EditorToolbar/Toolbar.tsx +7 -3
- package/src/EditorToolbar/_toolbar.scss +10 -11
- package/src/hooks/useFocus.ts +20 -13
- package/src/ui/Button/_button.scss +1 -1
- package/src/ui/_typography.scss +62 -60
package/lib/Editor/Editor.js
CHANGED
@@ -46,7 +46,7 @@ const WrappedEditor = () => {
|
|
46
46
|
(0, react_2.useEditorEvent)('paste', preventImagePaste);
|
47
47
|
return react_1.default.createElement(react_2.EditorComponent, null);
|
48
48
|
};
|
49
|
-
const Editor = ({ content, editable = true, onChange, children, isFocused
|
49
|
+
const Editor = ({ content, editable = true, onChange, children, isFocused }) => {
|
50
50
|
const { manager, state, setState } = (0, react_2.useRemirror)({
|
51
51
|
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
|
52
52
|
content,
|
@@ -57,7 +57,7 @@ const Editor = ({ content, editable = true, onChange, children, isFocused = fals
|
|
57
57
|
setState(parameter.state);
|
58
58
|
onChange?.(parameter);
|
59
59
|
};
|
60
|
-
const {
|
60
|
+
const { isVisible, handleFocus, handleBlur, wrapperRef } = (0, useFocus_1.default)(isFocused || false);
|
61
61
|
// On initial load, check if we need to focus the actual text content
|
62
62
|
(0, react_1.useEffect)(() => {
|
63
63
|
if (isFocused) {
|
@@ -65,9 +65,9 @@ const Editor = ({ content, editable = true, onChange, children, isFocused = fals
|
|
65
65
|
}
|
66
66
|
}, []);
|
67
67
|
return (react_1.default.createElement("div", { className: "squiz-fte-scope" },
|
68
|
-
react_1.default.createElement("div", { onBlur: handleBlur,
|
68
|
+
react_1.default.createElement("div", { ref: wrapperRef, onBlur: handleBlur, onFocusCapture: handleFocus, className: (0, clsx_1.default)('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled') },
|
69
69
|
react_1.default.createElement(react_2.Remirror, { manager: manager, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor" },
|
70
|
-
editable && react_1.default.createElement(EditorToolbar_1.Toolbar, {
|
70
|
+
editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isVisible: isVisible }),
|
71
71
|
children,
|
72
72
|
react_1.default.createElement(WrappedEditor, null),
|
73
73
|
editable && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null)))));
|
@@ -71,6 +71,6 @@ const FloatingToolbar = () => {
|
|
71
71
|
if (extensionNames.clearFormatting && clearFormatting.enabled()) {
|
72
72
|
buttons.push(react_1.default.createElement(ClearFormattingButton_1.default, { key: "clearFormatting" }));
|
73
73
|
}
|
74
|
-
return (react_1.default.createElement(react_2.FloatingToolbar, { className: "squiz-fte-scope squiz-fte-scope__floating-popover", positioner: positioner }, buttons));
|
74
|
+
return (react_1.default.createElement(react_2.FloatingToolbar, { className: "squiz-fte-scope squiz-fte-scope__floating-popover", positioner: positioner, tabIndex: 0, role: 'toolbar' }, buttons));
|
75
75
|
};
|
76
76
|
exports.FloatingToolbar = FloatingToolbar;
|
@@ -19,9 +19,9 @@ const ImageButton_1 = __importDefault(require("./Tools/Image/ImageButton"));
|
|
19
19
|
const RemoveLinkButton_1 = __importDefault(require("./Tools/Link/RemoveLinkButton"));
|
20
20
|
const ClearFormattingButton_1 = __importDefault(require("./Tools/ClearFormatting/ClearFormattingButton"));
|
21
21
|
const hooks_1 = require("../hooks");
|
22
|
-
const Toolbar = ({
|
22
|
+
const Toolbar = ({ isVisible }) => {
|
23
23
|
const extensionNames = (0, hooks_1.useExtensionNames)();
|
24
|
-
return (react_1.default.createElement(react_components_1.Toolbar, { className: (0, clsx_1.default)('remirror-toolbar editor-toolbar',
|
24
|
+
return (react_1.default.createElement(react_components_1.Toolbar, { className: (0, clsx_1.default)('remirror-toolbar editor-toolbar header-toolbar', isVisible && 'show-toolbar'), role: "toolbar", tabIndex: 0 },
|
25
25
|
extensionNames.history && (react_1.default.createElement(react_1.default.Fragment, null,
|
26
26
|
react_1.default.createElement(UndoButton_1.default, null),
|
27
27
|
react_1.default.createElement(RedoButton_1.default, null),
|
package/lib/hooks/useFocus.d.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import { FocusEventHandler, RefObject } from 'react';
|
2
|
+
declare const useFocus: (initialState: boolean) => {
|
3
3
|
handleFocus: () => void;
|
4
|
-
handleBlur:
|
4
|
+
handleBlur: FocusEventHandler<HTMLDivElement>;
|
5
|
+
isVisible: boolean;
|
6
|
+
wrapperRef: RefObject<HTMLDivElement>;
|
5
7
|
};
|
6
|
-
export default
|
8
|
+
export default useFocus;
|
package/lib/hooks/useFocus.js
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
const react_1 = require("react");
|
4
|
-
const
|
5
|
-
const
|
4
|
+
const useFocus = (initialState) => {
|
5
|
+
const wrapperRef = (0, react_1.createRef)();
|
6
|
+
const [isVisible, setIsVisible] = (0, react_1.useState)(initialState);
|
6
7
|
const handleFocus = (0, react_1.useCallback)(() => {
|
7
|
-
|
8
|
+
setIsVisible(true);
|
8
9
|
}, []);
|
9
|
-
const handleBlur = (
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
handleFocus,
|
15
|
-
handleBlur,
|
10
|
+
const handleBlur = (event) => {
|
11
|
+
const isOutside = wrapperRef.current !== null && !wrapperRef.current.contains(event.relatedTarget);
|
12
|
+
if (isOutside) {
|
13
|
+
setIsVisible(false);
|
14
|
+
}
|
16
15
|
};
|
16
|
+
return { handleFocus, handleBlur, isVisible, wrapperRef };
|
17
17
|
};
|
18
|
-
exports.default =
|
18
|
+
exports.default = useFocus;
|
package/lib/index.css
CHANGED
@@ -638,71 +638,71 @@
|
|
638
638
|
.squiz-fte-scope .filter {
|
639
639
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important;
|
640
640
|
}
|
641
|
-
.squiz-fte-scope a {
|
641
|
+
.squiz-fte-scope .remirror-editor a {
|
642
642
|
--tw-text-opacity: 1;
|
643
643
|
color: rgb(7 116 210 / var(--tw-text-opacity));
|
644
644
|
text-decoration: underline;
|
645
645
|
}
|
646
|
-
.squiz-fte-scope h1 {
|
646
|
+
.squiz-fte-scope .remirror-editor h1 {
|
647
647
|
font-size: 1.625rem;
|
648
648
|
font-weight: 600;
|
649
649
|
letter-spacing: -0.2px;
|
650
650
|
line-height: 2rem;
|
651
651
|
}
|
652
|
-
.squiz-fte-scope h2 {
|
652
|
+
.squiz-fte-scope .remirror-editor h2 {
|
653
653
|
font-size: 1.25rem;
|
654
654
|
font-weight: 600;
|
655
655
|
letter-spacing: -0.5px;
|
656
656
|
line-height: 1.5rem;
|
657
657
|
}
|
658
|
-
.squiz-fte-scope h3 {
|
658
|
+
.squiz-fte-scope .remirror-editor h3 {
|
659
659
|
font-size: 1.125rem;
|
660
660
|
font-weight: 600;
|
661
661
|
letter-spacing: -0.2px;
|
662
662
|
line-height: 1.375rem;
|
663
663
|
}
|
664
|
-
.squiz-fte-scope h4 {
|
664
|
+
.squiz-fte-scope .remirror-editor h4 {
|
665
665
|
font-size: 1rem;
|
666
666
|
font-weight: 700;
|
667
667
|
letter-spacing: -0.2px;
|
668
668
|
line-height: 1.25rem;
|
669
669
|
}
|
670
|
-
.squiz-fte-scope h5 {
|
670
|
+
.squiz-fte-scope .remirror-editor h5 {
|
671
671
|
font-size: 1rem;
|
672
672
|
font-weight: 600;
|
673
673
|
letter-spacing: -0.2px;
|
674
674
|
line-height: 1.25rem;
|
675
675
|
}
|
676
|
-
.squiz-fte-scope h6 {
|
676
|
+
.squiz-fte-scope .remirror-editor h6 {
|
677
677
|
font-size: 0.875rem;
|
678
678
|
font-weight: 600;
|
679
679
|
letter-spacing: -0.2px;
|
680
680
|
line-height: 1.25rem;
|
681
681
|
}
|
682
|
-
.squiz-fte-scope .code-block,
|
683
|
-
.squiz-fte-scope .preformatted {
|
682
|
+
.squiz-fte-scope .remirror-editor .code-block,
|
683
|
+
.squiz-fte-scope .remirror-editor .preformatted {
|
684
684
|
display: flex;
|
685
685
|
}
|
686
|
-
.squiz-fte-scope .code-block .material-symbols-rounded,
|
687
|
-
.squiz-fte-scope .preformatted .material-symbols-rounded {
|
686
|
+
.squiz-fte-scope .remirror-editor .code-block .material-symbols-rounded,
|
687
|
+
.squiz-fte-scope .remirror-editor .preformatted .material-symbols-rounded {
|
688
688
|
font-size: 1.125rem;
|
689
689
|
--tw-text-opacity: 1;
|
690
690
|
color: rgb(79 79 79 / var(--tw-text-opacity));
|
691
691
|
pointer-events: none;
|
692
692
|
margin-right: 0.375rem;
|
693
693
|
}
|
694
|
-
.squiz-fte-scope .code-block .block-divider,
|
695
|
-
.squiz-fte-scope .preformatted .block-divider {
|
694
|
+
.squiz-fte-scope .remirror-editor .code-block .block-divider,
|
695
|
+
.squiz-fte-scope .remirror-editor .preformatted .block-divider {
|
696
696
|
--tw-bg-opacity: 1;
|
697
697
|
background-color: rgb(237 237 237 / var(--tw-bg-opacity));
|
698
698
|
width: 4px;
|
699
699
|
border-radius: 0.75rem;
|
700
700
|
margin-right: 0.625rem;
|
701
701
|
}
|
702
|
-
.squiz-fte-scope .code-block code,
|
703
|
-
.squiz-fte-scope .code-block pre,
|
704
|
-
.squiz-fte-scope .preformatted code,
|
705
|
-
.squiz-fte-scope .preformatted pre {
|
702
|
+
.squiz-fte-scope .remirror-editor .code-block code,
|
703
|
+
.squiz-fte-scope .remirror-editor .code-block pre,
|
704
|
+
.squiz-fte-scope .remirror-editor .preformatted code,
|
705
|
+
.squiz-fte-scope .remirror-editor .preformatted pre {
|
706
706
|
--tw-text-opacity: 1;
|
707
707
|
color: rgb(79 79 79 / var(--tw-text-opacity));
|
708
708
|
font-size: 0.75rem;
|
@@ -835,9 +835,10 @@
|
|
835
835
|
.squiz-fte-scope .formatted-text-editor img {
|
836
836
|
display: inline;
|
837
837
|
}
|
838
|
-
.formatted-text-editor:has(.
|
839
|
-
|
840
|
-
--tw-shadow
|
838
|
+
.squiz-fte-scope .formatted-text-editor:has(.show-toolbar) {
|
839
|
+
border-width: 0px;
|
840
|
+
--tw-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 12px 4px rgba(0,0,0,0.12);
|
841
|
+
--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 1px 12px 4px var(--tw-shadow-color);
|
841
842
|
box-shadow:
|
842
843
|
var(--tw-ring-offset-shadow, 0 0 #0000),
|
843
844
|
var(--tw-ring-shadow, 0 0 #0000),
|
@@ -845,6 +846,8 @@
|
|
845
846
|
}
|
846
847
|
.squiz-fte-scope .editor-toolbar,
|
847
848
|
.squiz-fte-scope__floating-popover {
|
849
|
+
border-top-left-radius: 4px;
|
850
|
+
border-top-right-radius: 4px;
|
848
851
|
border-bottom-width: 2px;
|
849
852
|
border-style: solid;
|
850
853
|
--tw-border-opacity: 1;
|
@@ -862,12 +865,10 @@
|
|
862
865
|
}
|
863
866
|
.squiz-fte-scope .editor-toolbar .MuiDivider-root,
|
864
867
|
.squiz-fte-scope__floating-popover .MuiDivider-root {
|
865
|
-
margin-top: -0.25rem;
|
866
|
-
margin-bottom: -0.25rem;
|
867
|
-
margin-left: 0.25rem;
|
868
|
-
margin-right: 0.25rem;
|
869
868
|
margin-top: 0px;
|
870
869
|
margin-bottom: 0px;
|
870
|
+
margin-left: 0.25rem;
|
871
|
+
margin-right: 0.25rem;
|
871
872
|
border-width: 1px;
|
872
873
|
margin-right: 2px;
|
873
874
|
height: auto;
|
@@ -881,17 +882,14 @@
|
|
881
882
|
.squiz-fte-scope__floating-popover .squiz-fte-btn ~ .squiz-fte-btn {
|
882
883
|
margin-left: 2px;
|
883
884
|
}
|
884
|
-
.squiz-fte-scope .
|
885
|
-
.
|
886
|
-
max-height: 200px;
|
887
|
-
opacity: 1;
|
888
|
-
transition: max-height 0.3s ease, opacity 0.4s ease;
|
889
|
-
}
|
890
|
-
.squiz-fte-scope .editor-toolbar.hide,
|
891
|
-
.hide.squiz-fte-scope__floating-popover {
|
892
|
-
max-height: 0;
|
885
|
+
.squiz-fte-scope .header-toolbar {
|
886
|
+
transition: opacity 0.2s linear, max-height 0.2s linear;
|
893
887
|
opacity: 0;
|
894
|
-
|
888
|
+
max-height: 0;
|
889
|
+
}
|
890
|
+
.squiz-fte-scope .header-toolbar.show-toolbar {
|
891
|
+
opacity: 1;
|
892
|
+
max-height: 15vh;
|
895
893
|
}
|
896
894
|
.squiz-fte-scope__floating-popover {
|
897
895
|
display: flex;
|
@@ -913,7 +911,7 @@
|
|
913
911
|
margin-top: 0px;
|
914
912
|
margin-bottom: 0px;
|
915
913
|
}
|
916
|
-
.squiz-fte-scope .squiz-fte-btn {
|
914
|
+
.squiz-fte-scope button.squiz-fte-btn {
|
917
915
|
border-radius: 4px;
|
918
916
|
--tw-bg-opacity: 1;
|
919
917
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
@@ -935,23 +933,23 @@
|
|
935
933
|
border: 1px solid transparent;
|
936
934
|
padding: 6px 12px;
|
937
935
|
}
|
938
|
-
.squiz-fte-scope .squiz-fte-btn--is-icon {
|
936
|
+
.squiz-fte-scope button.squiz-fte-btn--is-icon {
|
939
937
|
padding: 6px;
|
940
938
|
}
|
941
|
-
.squiz-fte-scope .squiz-fte-btn ~ .squiz-fte-btn {
|
939
|
+
.squiz-fte-scope button.squiz-fte-btn ~ .squiz-fte-btn {
|
942
940
|
margin-left: 2px;
|
943
941
|
}
|
944
|
-
.squiz-fte-scope .squiz-fte-btn.disabled,
|
945
|
-
.squiz-fte-scope .squiz-fte-btn[disabled] {
|
942
|
+
.squiz-fte-scope button.squiz-fte-btn.disabled,
|
943
|
+
.squiz-fte-scope button.squiz-fte-btn[disabled] {
|
946
944
|
cursor: not-allowed;
|
947
945
|
opacity: 0.5;
|
948
946
|
}
|
949
|
-
.squiz-fte-scope .squiz-fte-btn:hover,
|
950
|
-
.squiz-fte-scope .squiz-fte-btn:focus {
|
947
|
+
.squiz-fte-scope button.squiz-fte-btn:hover,
|
948
|
+
.squiz-fte-scope button.squiz-fte-btn:focus {
|
951
949
|
background-color: rgba(0, 0, 0, 0.04);
|
952
950
|
}
|
953
|
-
.squiz-fte-scope .squiz-fte-btn--is-active,
|
954
|
-
.squiz-fte-scope .squiz-fte-btn:active {
|
951
|
+
.squiz-fte-scope button.squiz-fte-btn--is-active,
|
952
|
+
.squiz-fte-scope button.squiz-fte-btn:active {
|
955
953
|
--tw-bg-opacity: 1;
|
956
954
|
background-color: rgb(230 241 250 / var(--tw-bg-opacity));
|
957
955
|
--tw-text-opacity: 1;
|
@@ -1150,7 +1148,7 @@
|
|
1150
1148
|
.squiz-fte-scope__floating-popover .squiz-fte-modal-footer__button ~ .squiz-fte-btn {
|
1151
1149
|
margin-left: 2px;
|
1152
1150
|
}
|
1153
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button {
|
1151
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button {
|
1154
1152
|
border-radius: 4px;
|
1155
1153
|
--tw-bg-opacity: 1;
|
1156
1154
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
@@ -1172,19 +1170,19 @@
|
|
1172
1170
|
border: 1px solid transparent;
|
1173
1171
|
padding: 6px 12px;
|
1174
1172
|
}
|
1175
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button ~ .squiz-fte-btn {
|
1173
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button ~ .squiz-fte-btn {
|
1176
1174
|
margin-left: 2px;
|
1177
1175
|
}
|
1178
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button.disabled,
|
1179
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button[disabled] {
|
1176
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button.disabled,
|
1177
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button[disabled] {
|
1180
1178
|
cursor: not-allowed;
|
1181
1179
|
opacity: 0.5;
|
1182
1180
|
}
|
1183
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button:hover,
|
1184
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button:focus {
|
1181
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button:hover,
|
1182
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button:focus {
|
1185
1183
|
background-color: rgba(0, 0, 0, 0.04);
|
1186
1184
|
}
|
1187
|
-
.squiz-fte-scope .squiz-fte-modal-footer__button:active {
|
1185
|
+
.squiz-fte-scope button.squiz-fte-modal-footer__button:active {
|
1188
1186
|
--tw-bg-opacity: 1;
|
1189
1187
|
background-color: rgb(230 241 250 / var(--tw-bg-opacity));
|
1190
1188
|
--tw-text-opacity: 1;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "1.35.1-alpha.
|
3
|
+
"version": "1.35.1-alpha.26",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"scripts": {
|
@@ -20,8 +20,8 @@
|
|
20
20
|
"@headlessui/react": "1.7.11",
|
21
21
|
"@mui/icons-material": "5.11.16",
|
22
22
|
"@remirror/react": "2.0.25",
|
23
|
-
"@squiz/dx-json-schema-lib": "1.35.1-alpha.
|
24
|
-
"@squiz/resource-browser": "1.35.1-alpha.
|
23
|
+
"@squiz/dx-json-schema-lib": "1.35.1-alpha.26",
|
24
|
+
"@squiz/resource-browser": "1.35.1-alpha.26",
|
25
25
|
"clsx": "1.2.1",
|
26
26
|
"react-hook-form": "7.43.2",
|
27
27
|
"react-image-size": "2.0.0",
|
@@ -75,5 +75,5 @@
|
|
75
75
|
"volta": {
|
76
76
|
"node": "18.15.0"
|
77
77
|
},
|
78
|
-
"gitHead": "
|
78
|
+
"gitHead": "9c32fce90da5943c353c20cda9931efd5d5516af"
|
79
79
|
}
|
@@ -5,13 +5,13 @@ import '@testing-library/jest-dom';
|
|
5
5
|
import { renderWithEditor } from '../../tests';
|
6
6
|
import ImageButton from '../EditorToolbar/Tools/Image/ImageButton';
|
7
7
|
|
8
|
-
const
|
8
|
+
const isVisible = jest.fn().mockReturnValue(false);
|
9
9
|
const handleFocusMock = jest.fn();
|
10
10
|
const handleBlurMock = jest.fn();
|
11
11
|
jest.mock('../hooks/useFocus', () => ({
|
12
12
|
__esModule: true,
|
13
13
|
default: () => ({
|
14
|
-
|
14
|
+
isVisible: isVisible(),
|
15
15
|
handleFocus: handleFocusMock,
|
16
16
|
handleBlur: handleBlurMock,
|
17
17
|
}),
|
@@ -349,16 +349,9 @@ describe('Formatted text editor', () => {
|
|
349
349
|
});
|
350
350
|
|
351
351
|
it('should apply hide class when focus hook returns false', () => {
|
352
|
-
|
352
|
+
isVisible.mockReturnValue(true);
|
353
353
|
const { container } = render(<Editor />);
|
354
354
|
|
355
|
-
expect(container.querySelector('.
|
356
|
-
});
|
357
|
-
|
358
|
-
it('should apply show class when focus hook returns true', () => {
|
359
|
-
isEditorFocused.mockReturnValue(true);
|
360
|
-
const { container } = render(<Editor />);
|
361
|
-
|
362
|
-
expect(container.querySelector('.show')).toBeInTheDocument();
|
355
|
+
expect(container.querySelector('.show-toolbar')).toBeInTheDocument();
|
363
356
|
});
|
364
357
|
});
|
package/src/Editor/Editor.tsx
CHANGED
@@ -6,7 +6,7 @@ import clsx from 'clsx';
|
|
6
6
|
import { Toolbar, FloatingToolbar } from '../EditorToolbar';
|
7
7
|
import { EditorContext } from './EditorContext';
|
8
8
|
import { createExtensions } from '../Extensions/Extensions';
|
9
|
-
import
|
9
|
+
import useFocus from '../hooks/useFocus';
|
10
10
|
|
11
11
|
type EditorProps = {
|
12
12
|
content?: RemirrorContentType;
|
@@ -32,7 +32,7 @@ const WrappedEditor = () => {
|
|
32
32
|
return <EditorComponent />;
|
33
33
|
};
|
34
34
|
|
35
|
-
const Editor = ({ content, editable = true, onChange, children, isFocused
|
35
|
+
const Editor = ({ content, editable = true, onChange, children, isFocused }: EditorProps) => {
|
36
36
|
const { manager, state, setState } = useRemirror({
|
37
37
|
extensions: createExtensions(useContext(EditorContext)),
|
38
38
|
content,
|
@@ -45,7 +45,7 @@ const Editor = ({ content, editable = true, onChange, children, isFocused = fals
|
|
45
45
|
onChange?.(parameter);
|
46
46
|
};
|
47
47
|
|
48
|
-
const {
|
48
|
+
const { isVisible, handleFocus, handleBlur, wrapperRef } = useFocus(isFocused || false);
|
49
49
|
|
50
50
|
// On initial load, check if we need to focus the actual text content
|
51
51
|
useEffect(() => {
|
@@ -57,8 +57,9 @@ const Editor = ({ content, editable = true, onChange, children, isFocused = fals
|
|
57
57
|
return (
|
58
58
|
<div className="squiz-fte-scope">
|
59
59
|
<div
|
60
|
+
ref={wrapperRef}
|
60
61
|
onBlur={handleBlur}
|
61
|
-
|
62
|
+
onFocusCapture={handleFocus}
|
62
63
|
className={clsx('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled')}
|
63
64
|
>
|
64
65
|
<Remirror
|
@@ -69,7 +70,7 @@ const Editor = ({ content, editable = true, onChange, children, isFocused = fals
|
|
69
70
|
placeholder="Write something"
|
70
71
|
label="Text editor"
|
71
72
|
>
|
72
|
-
{editable && <Toolbar
|
73
|
+
{editable && <Toolbar isVisible={isVisible} />}
|
73
74
|
{children}
|
74
75
|
<WrappedEditor />
|
75
76
|
{editable && <FloatingToolbar />}
|
package/src/Editor/_editor.scss
CHANGED
@@ -50,7 +50,12 @@ export const FloatingToolbar = () => {
|
|
50
50
|
}
|
51
51
|
|
52
52
|
return (
|
53
|
-
<RemirrorFloatingToolbar
|
53
|
+
<RemirrorFloatingToolbar
|
54
|
+
className="squiz-fte-scope squiz-fte-scope__floating-popover"
|
55
|
+
positioner={positioner}
|
56
|
+
tabIndex={0}
|
57
|
+
role={'toolbar'}
|
58
|
+
>
|
54
59
|
{buttons}
|
55
60
|
</RemirrorFloatingToolbar>
|
56
61
|
);
|
@@ -15,13 +15,17 @@ import ClearFormattingButton from './Tools/ClearFormatting/ClearFormattingButton
|
|
15
15
|
import { useExtensionNames } from '../hooks';
|
16
16
|
|
17
17
|
type ToolbarProps = {
|
18
|
-
|
18
|
+
isVisible: boolean;
|
19
19
|
};
|
20
|
-
export const Toolbar = ({
|
20
|
+
export const Toolbar = ({ isVisible }: ToolbarProps) => {
|
21
21
|
const extensionNames = useExtensionNames();
|
22
22
|
|
23
23
|
return (
|
24
|
-
<RemirrorToolbar
|
24
|
+
<RemirrorToolbar
|
25
|
+
className={clsx('remirror-toolbar editor-toolbar header-toolbar', isVisible && 'show-toolbar')}
|
26
|
+
role="toolbar"
|
27
|
+
tabIndex={0}
|
28
|
+
>
|
25
29
|
{extensionNames.history && (
|
26
30
|
<>
|
27
31
|
<UndoButton />
|
@@ -1,5 +1,5 @@
|
|
1
1
|
.editor-toolbar {
|
2
|
-
@apply bg-white border-gray-200 border-b-2 border-solid p-1;
|
2
|
+
@apply bg-white border-gray-200 border-b-2 border-solid p-1 rounded-t;
|
3
3
|
|
4
4
|
display: flex;
|
5
5
|
justify-items: center;
|
@@ -10,7 +10,7 @@
|
|
10
10
|
}
|
11
11
|
|
12
12
|
.MuiDivider-root {
|
13
|
-
@apply
|
13
|
+
@apply my-0 mx-1 border;
|
14
14
|
margin-right: 2px;
|
15
15
|
height: auto;
|
16
16
|
}
|
@@ -21,16 +21,15 @@
|
|
21
21
|
margin-left: 2px;
|
22
22
|
}
|
23
23
|
}
|
24
|
+
}
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
}
|
26
|
+
.header-toolbar {
|
27
|
+
transition: opacity 0.2s linear, max-height 0.2s linear;
|
28
|
+
opacity: 0;
|
29
|
+
max-height: 0;
|
30
30
|
|
31
|
-
&.
|
32
|
-
|
33
|
-
|
34
|
-
transition: max-height 0.3s ease, opacity 0.4s ease;
|
31
|
+
&.show-toolbar {
|
32
|
+
opacity: 1;
|
33
|
+
max-height: 15vh;
|
35
34
|
}
|
36
35
|
}
|
package/src/hooks/useFocus.ts
CHANGED
@@ -1,21 +1,28 @@
|
|
1
|
-
import { useState, useCallback } from 'react';
|
1
|
+
import { useState, useCallback, FocusEvent, FocusEventHandler, RefObject, createRef } from 'react';
|
2
2
|
|
3
|
-
const
|
4
|
-
|
3
|
+
const useFocus = (
|
4
|
+
initialState: boolean,
|
5
|
+
): {
|
6
|
+
handleFocus: () => void;
|
7
|
+
handleBlur: FocusEventHandler<HTMLDivElement>;
|
8
|
+
isVisible: boolean;
|
9
|
+
wrapperRef: RefObject<HTMLDivElement>;
|
10
|
+
} => {
|
11
|
+
const wrapperRef = createRef<HTMLDivElement>();
|
12
|
+
const [isVisible, setIsVisible] = useState(initialState);
|
5
13
|
|
6
14
|
const handleFocus = useCallback(() => {
|
7
|
-
|
15
|
+
setIsVisible(true);
|
8
16
|
}, []);
|
9
17
|
|
10
|
-
const handleBlur =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
isEditorFocused,
|
16
|
-
handleFocus,
|
17
|
-
handleBlur,
|
18
|
+
const handleBlur: FocusEventHandler<HTMLDivElement> = (event: FocusEvent<HTMLDivElement>) => {
|
19
|
+
const isOutside = wrapperRef.current !== null && !wrapperRef.current.contains(event.relatedTarget as Node);
|
20
|
+
if (isOutside) {
|
21
|
+
setIsVisible(false);
|
22
|
+
}
|
18
23
|
};
|
24
|
+
|
25
|
+
return { handleFocus, handleBlur, isVisible, wrapperRef };
|
19
26
|
};
|
20
27
|
|
21
|
-
export default
|
28
|
+
export default useFocus;
|
package/src/ui/_typography.scss
CHANGED
@@ -1,72 +1,74 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
.remirror-editor {
|
2
|
+
a {
|
3
|
+
@apply text-blue-300;
|
4
|
+
text-decoration: underline;
|
5
|
+
}
|
5
6
|
|
6
|
-
h1 {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
}
|
7
|
+
h1 {
|
8
|
+
font-size: 1.625rem;
|
9
|
+
font-weight: 600;
|
10
|
+
letter-spacing: -0.2px;
|
11
|
+
line-height: 2rem;
|
12
|
+
}
|
12
13
|
|
13
|
-
h2 {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
}
|
14
|
+
h2 {
|
15
|
+
font-size: 1.25rem;
|
16
|
+
font-weight: 600;
|
17
|
+
letter-spacing: -0.5px;
|
18
|
+
line-height: 1.5rem;
|
19
|
+
}
|
19
20
|
|
20
|
-
h3 {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
}
|
21
|
+
h3 {
|
22
|
+
font-size: 1.125rem;
|
23
|
+
font-weight: 600;
|
24
|
+
letter-spacing: -0.2px;
|
25
|
+
line-height: 1.375rem;
|
26
|
+
}
|
26
27
|
|
27
|
-
h4 {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
}
|
28
|
+
h4 {
|
29
|
+
font-size: 1rem;
|
30
|
+
font-weight: 700;
|
31
|
+
letter-spacing: -0.2px;
|
32
|
+
line-height: 1.25rem;
|
33
|
+
}
|
33
34
|
|
34
|
-
h5 {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
}
|
35
|
+
h5 {
|
36
|
+
font-size: 1rem;
|
37
|
+
font-weight: 600;
|
38
|
+
letter-spacing: -0.2px;
|
39
|
+
line-height: 1.25rem;
|
40
|
+
}
|
40
41
|
|
41
|
-
h6 {
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
}
|
42
|
+
h6 {
|
43
|
+
font-size: 0.875rem;
|
44
|
+
font-weight: 600;
|
45
|
+
letter-spacing: -0.2px;
|
46
|
+
line-height: 1.25rem;
|
47
|
+
}
|
47
48
|
|
48
|
-
.code-block,
|
49
|
-
.preformatted {
|
50
|
-
|
49
|
+
.code-block,
|
50
|
+
.preformatted {
|
51
|
+
display: flex;
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
.material-symbols-rounded {
|
54
|
+
@apply text-gray-700 text-xlg;
|
55
|
+
pointer-events: none;
|
56
|
+
margin-right: 0.375rem;
|
57
|
+
}
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
.block-divider {
|
60
|
+
@apply bg-gray-200;
|
61
|
+
width: 4px;
|
62
|
+
border-radius: 0.75rem;
|
63
|
+
margin-right: 0.625rem;
|
64
|
+
}
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
66
|
+
code,
|
67
|
+
pre {
|
68
|
+
@apply text-gray-700;
|
69
|
+
font-size: 0.75rem;
|
70
|
+
padding-top: 0.75rem;
|
71
|
+
padding-bottom: 0.75rem;
|
72
|
+
}
|
71
73
|
}
|
72
74
|
}
|