@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.
@@ -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 = false }) => {
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 { isEditorFocused, handleFocus, handleBlur } = (0, useFocus_1.default)(isFocused);
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, onFocus: handleFocus, className: (0, clsx_1.default)('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled') },
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, { isEditorFocused: isEditorFocused }),
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;
@@ -1,5 +1,5 @@
1
1
  type ToolbarProps = {
2
- isEditorFocused: boolean;
2
+ isVisible: boolean;
3
3
  };
4
- export declare const Toolbar: ({ isEditorFocused }: ToolbarProps) => JSX.Element;
4
+ export declare const Toolbar: ({ isVisible }: ToolbarProps) => JSX.Element;
5
5
  export {};
@@ -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 = ({ isEditorFocused }) => {
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', isEditorFocused === true ? 'show' : 'hide') },
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),
@@ -1,6 +1,8 @@
1
- declare const useEditorFocus: (isFocused: boolean) => {
2
- isEditorFocused: boolean;
1
+ import { FocusEventHandler, RefObject } from 'react';
2
+ declare const useFocus: (initialState: boolean) => {
3
3
  handleFocus: () => void;
4
- handleBlur: () => void;
4
+ handleBlur: FocusEventHandler<HTMLDivElement>;
5
+ isVisible: boolean;
6
+ wrapperRef: RefObject<HTMLDivElement>;
5
7
  };
6
- export default useEditorFocus;
8
+ export default useFocus;
@@ -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 useEditorFocus = (isFocused) => {
5
- const [isEditorFocused, setIsEditorFocused] = (0, react_1.useState)(isFocused);
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
- setIsEditorFocused(true);
8
+ setIsVisible(true);
8
9
  }, []);
9
- const handleBlur = (0, react_1.useCallback)(() => {
10
- setIsEditorFocused(false);
11
- }, []);
12
- return {
13
- isEditorFocused,
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 = useEditorFocus;
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(.editor-toolbar.show, .show.squiz-fte-scope__floating-popover) {
839
- --tw-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 24px 12px rgba(0,0,0,0.12);
840
- --tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 1px 24px 12px var(--tw-shadow-color);
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 .editor-toolbar.show,
885
- .show.squiz-fte-scope__floating-popover {
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
- transition: max-height 0.3s ease, opacity 0.4s ease;
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.24",
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",
24
- "@squiz/resource-browser": "1.35.1-alpha.24",
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": "a0cdd85b7eb8ee15a882d57f0c89fb84c13e4e04"
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 isEditorFocused = jest.fn().mockReturnValue(true);
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
- isEditorFocused: isEditorFocused(),
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
- isEditorFocused.mockReturnValue(false);
352
+ isVisible.mockReturnValue(true);
353
353
  const { container } = render(<Editor />);
354
354
 
355
- expect(container.querySelector('.hide')).toBeInTheDocument();
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
  });
@@ -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 useEditorFocus from '../hooks/useFocus';
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 = false }: EditorProps) => {
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 { isEditorFocused, handleFocus, handleBlur } = useEditorFocus(isFocused);
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
- onFocus={handleFocus}
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 isEditorFocused={isEditorFocused} />}
73
+ {editable && <Toolbar isVisible={isVisible} />}
73
74
  {children}
74
75
  <WrappedEditor />
75
76
  {editable && <FloatingToolbar />}
@@ -49,7 +49,7 @@
49
49
  display: inline;
50
50
  }
51
51
 
52
- &:has(.editor-toolbar.show) {
53
- @apply shadow-lg;
52
+ &:has(.show-toolbar) {
53
+ @apply shadow-md border-0;
54
54
  }
55
55
  }
@@ -50,7 +50,12 @@ export const FloatingToolbar = () => {
50
50
  }
51
51
 
52
52
  return (
53
- <RemirrorFloatingToolbar className="squiz-fte-scope squiz-fte-scope__floating-popover" positioner={positioner}>
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
- isEditorFocused: boolean;
18
+ isVisible: boolean;
19
19
  };
20
- export const Toolbar = ({ isEditorFocused }: ToolbarProps) => {
20
+ export const Toolbar = ({ isVisible }: ToolbarProps) => {
21
21
  const extensionNames = useExtensionNames();
22
22
 
23
23
  return (
24
- <RemirrorToolbar className={clsx('remirror-toolbar editor-toolbar', isEditorFocused === true ? 'show' : 'hide')}>
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 -my-1 mx-1 border mt-0 mb-0;
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
- &.show {
26
- max-height: 200px;
27
- opacity: 1;
28
- transition: max-height 0.3s ease, opacity 0.4s ease;
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
- &.hide {
32
- max-height: 0;
33
- opacity: 0;
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
  }
@@ -1,21 +1,28 @@
1
- import { useState, useCallback } from 'react';
1
+ import { useState, useCallback, FocusEvent, FocusEventHandler, RefObject, createRef } from 'react';
2
2
 
3
- const useEditorFocus = (isFocused: boolean) => {
4
- const [isEditorFocused, setIsEditorFocused] = useState(isFocused);
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
- setIsEditorFocused(true);
15
+ setIsVisible(true);
8
16
  }, []);
9
17
 
10
- const handleBlur = useCallback(() => {
11
- setIsEditorFocused(false);
12
- }, []);
13
-
14
- return {
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 useEditorFocus;
28
+ export default useFocus;
@@ -1,4 +1,4 @@
1
- .squiz-fte-btn {
1
+ button.squiz-fte-btn {
2
2
  @apply font-bold rounded ease-linear transition-all bg-white text-gray-600 duration-150;
3
3
  display: flex;
4
4
  align-items: center;
@@ -1,72 +1,74 @@
1
- a {
2
- @apply text-blue-300;
3
- text-decoration: underline;
4
- }
1
+ .remirror-editor {
2
+ a {
3
+ @apply text-blue-300;
4
+ text-decoration: underline;
5
+ }
5
6
 
6
- h1 {
7
- font-size: 1.625rem;
8
- font-weight: 600;
9
- letter-spacing: -0.2px;
10
- line-height: 2rem;
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
- font-size: 1.25rem;
15
- font-weight: 600;
16
- letter-spacing: -0.5px;
17
- line-height: 1.5rem;
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
- font-size: 1.125rem;
22
- font-weight: 600;
23
- letter-spacing: -0.2px;
24
- line-height: 1.375rem;
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
- font-size: 1rem;
29
- font-weight: 700;
30
- letter-spacing: -0.2px;
31
- line-height: 1.25rem;
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
- font-size: 1rem;
36
- font-weight: 600;
37
- letter-spacing: -0.2px;
38
- line-height: 1.25rem;
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
- font-size: 0.875rem;
43
- font-weight: 600;
44
- letter-spacing: -0.2px;
45
- line-height: 1.25rem;
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
- display: flex;
49
+ .code-block,
50
+ .preformatted {
51
+ display: flex;
51
52
 
52
- .material-symbols-rounded {
53
- @apply text-gray-700 text-xlg;
54
- pointer-events: none;
55
- margin-right: 0.375rem;
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
- .block-divider {
59
- @apply bg-gray-200;
60
- width: 4px;
61
- border-radius: 0.75rem;
62
- margin-right: 0.625rem;
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
- code,
66
- pre {
67
- @apply text-gray-700;
68
- font-size: 0.75rem;
69
- padding-top: 0.75rem;
70
- padding-bottom: 0.75rem;
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
  }