@squiz/formatted-text-editor 1.35.1-alpha.12 → 1.35.1-alpha.13
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/demo/App.tsx +15 -1
- package/lib/Editor/Editor.d.ts +3 -1
- package/lib/Editor/Editor.js +7 -4
- package/lib/EditorToolbar/Toolbar.d.ts +5 -1
- package/lib/EditorToolbar/Toolbar.js +4 -3
- package/lib/hooks/useFocus.d.ts +6 -0
- package/lib/hooks/useFocus.js +18 -0
- package/lib/index.css +23 -0
- package/package.json +4 -4
- package/src/Editor/Editor.spec.tsx +50 -0
- package/src/Editor/Editor.tsx +15 -6
- package/src/Editor/_editor.scss +4 -0
- package/src/EditorToolbar/Toolbar.tsx +7 -3
- package/src/EditorToolbar/_toolbar.scss +15 -1
- package/src/hooks/useFocus.ts +21 -0
package/demo/App.tsx
CHANGED
@@ -3,8 +3,20 @@ import { Editor, remirrorNodeToSquizNode, squizNodeToRemirrorNode } from '../src
|
|
3
3
|
import { RemirrorEventListener, Extension } from '@remirror/core';
|
4
4
|
import ReactDiffViewer from 'react-diff-viewer-continued';
|
5
5
|
import { AppContext } from './AppContext';
|
6
|
+
import Button from '../src/ui/Button/Button';
|
7
|
+
import TextFieldsOutlinedIcon from '@mui/icons-material/TextFieldsOutlined';
|
8
|
+
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
9
|
+
import { VerticalDivider } from '@remirror/react-components';
|
10
|
+
const ComponentHandlers = () => (
|
11
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '5px' }}>
|
12
|
+
<Button icon={<TextFieldsOutlinedIcon />} onClick={(x) => x} />
|
13
|
+
<VerticalDivider />
|
14
|
+
<Button icon={<DeleteOutlineOutlinedIcon />} onClick={(x) => x} />
|
15
|
+
</div>
|
16
|
+
);
|
6
17
|
|
7
18
|
function App() {
|
19
|
+
const [showChildren] = useState(false);
|
8
20
|
const [doc, setDoc] = useState('');
|
9
21
|
const [squizDoc, setSquizDoc] = useState('');
|
10
22
|
const [reconvertedDoc, setReconvertedDoc] = useState('');
|
@@ -41,7 +53,9 @@ function App() {
|
|
41
53
|
editable={editable}
|
42
54
|
content={`<p>Hello <a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/></p>`}
|
43
55
|
onChange={handleEditorChange}
|
44
|
-
|
56
|
+
>
|
57
|
+
{showChildren && <ComponentHandlers />}
|
58
|
+
</Editor>
|
45
59
|
</AppContext>
|
46
60
|
</div>
|
47
61
|
<h1>Document</h1>
|
package/lib/Editor/Editor.d.ts
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
import { ReactNode } from 'react';
|
1
2
|
import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
|
2
3
|
type EditorProps = {
|
3
4
|
content?: RemirrorContentType;
|
4
5
|
onChange?: RemirrorEventListener<Extension>;
|
5
6
|
editable?: boolean;
|
7
|
+
children?: ReactNode;
|
6
8
|
};
|
7
|
-
declare const Editor: ({ content, editable, onChange }: EditorProps) => JSX.Element;
|
9
|
+
declare const Editor: ({ content, editable, onChange, children }: EditorProps) => JSX.Element;
|
8
10
|
export default Editor;
|
package/lib/Editor/Editor.js
CHANGED
@@ -28,10 +28,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
29
29
|
const react_1 = __importStar(require("react"));
|
30
30
|
const react_2 = require("@remirror/react");
|
31
|
+
const clsx_1 = __importDefault(require("clsx"));
|
31
32
|
const EditorToolbar_1 = require("../EditorToolbar");
|
32
33
|
const EditorContext_1 = require("./EditorContext");
|
33
34
|
const Extensions_1 = require("../Extensions/Extensions");
|
34
|
-
const
|
35
|
+
const useFocus_1 = __importDefault(require("../hooks/useFocus"));
|
35
36
|
const WrappedEditor = () => {
|
36
37
|
const preventImagePaste = (0, react_1.useCallback)((event) => {
|
37
38
|
const { clipboardData } = event;
|
@@ -45,7 +46,7 @@ const WrappedEditor = () => {
|
|
45
46
|
(0, react_2.useEditorEvent)('paste', preventImagePaste);
|
46
47
|
return react_1.default.createElement(react_2.EditorComponent, null);
|
47
48
|
};
|
48
|
-
const Editor = ({ content, editable = true, onChange }) => {
|
49
|
+
const Editor = ({ content, editable = true, onChange, children }) => {
|
49
50
|
const { manager, state, setState } = (0, react_2.useRemirror)({
|
50
51
|
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
|
51
52
|
content,
|
@@ -56,10 +57,12 @@ const Editor = ({ content, editable = true, onChange }) => {
|
|
56
57
|
setState(parameter.state);
|
57
58
|
onChange?.(parameter);
|
58
59
|
};
|
60
|
+
const { isEditorFocused, handleFocus, handleBlur } = (0, useFocus_1.default)();
|
59
61
|
return (react_1.default.createElement("div", { className: "squiz-fte-scope" },
|
60
|
-
react_1.default.createElement("div", { className: (0, clsx_1.default)('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled') },
|
62
|
+
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') },
|
61
63
|
react_1.default.createElement(react_2.Remirror, { manager: manager, state: state, editable: editable, onChange: handleChange, placeholder: "Write something", label: "Text editor" },
|
62
|
-
editable && react_1.default.createElement(EditorToolbar_1.Toolbar,
|
64
|
+
editable && react_1.default.createElement(EditorToolbar_1.Toolbar, { isEditorFocused: isEditorFocused }),
|
65
|
+
children,
|
63
66
|
react_1.default.createElement(WrappedEditor, null),
|
64
67
|
editable && react_1.default.createElement(EditorToolbar_1.FloatingToolbar, null)))));
|
65
68
|
};
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Toolbar = void 0;
|
7
7
|
const react_1 = __importDefault(require("react"));
|
8
8
|
const react_components_1 = require("@remirror/react-components");
|
9
|
+
const clsx_1 = __importDefault(require("clsx"));
|
9
10
|
const ItalicButton_1 = __importDefault(require("./Tools/Italic/ItalicButton"));
|
10
11
|
const UnderlineButton_1 = __importDefault(require("./Tools/Underline/UnderlineButton"));
|
11
12
|
const BoldButton_1 = __importDefault(require("./Tools/Bold/BoldButton"));
|
@@ -13,14 +14,14 @@ const TextAlignButtons_1 = __importDefault(require("./Tools/TextAlign/TextAlignB
|
|
13
14
|
const UndoButton_1 = __importDefault(require("./Tools/Undo/UndoButton"));
|
14
15
|
const RedoButton_1 = __importDefault(require("./Tools/Redo/RedoButton"));
|
15
16
|
const TextTypeDropdown_1 = __importDefault(require("./Tools/TextType/TextTypeDropdown"));
|
16
|
-
const hooks_1 = require("../hooks");
|
17
17
|
const LinkButton_1 = __importDefault(require("./Tools/Link/LinkButton"));
|
18
18
|
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
|
-
const
|
21
|
+
const hooks_1 = require("../hooks");
|
22
|
+
const Toolbar = ({ isEditorFocused }) => {
|
22
23
|
const extensionNames = (0, hooks_1.useExtensionNames)();
|
23
|
-
return (react_1.default.createElement(react_components_1.Toolbar, { className:
|
24
|
+
return (react_1.default.createElement(react_components_1.Toolbar, { className: (0, clsx_1.default)('remirror-toolbar editor-toolbar', isEditorFocused === true ? 'show' : 'hide') },
|
24
25
|
extensionNames.history && (react_1.default.createElement(react_1.default.Fragment, null,
|
25
26
|
react_1.default.createElement(UndoButton_1.default, null),
|
26
27
|
react_1.default.createElement(RedoButton_1.default, null),
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const react_1 = require("react");
|
4
|
+
const useEditorFocus = () => {
|
5
|
+
const [isEditorFocused, setIsEditorFocused] = (0, react_1.useState)(false);
|
6
|
+
const handleFocus = (0, react_1.useCallback)(() => {
|
7
|
+
setIsEditorFocused(true);
|
8
|
+
}, []);
|
9
|
+
const handleBlur = (0, react_1.useCallback)(() => {
|
10
|
+
setIsEditorFocused(false);
|
11
|
+
}, []);
|
12
|
+
return {
|
13
|
+
isEditorFocused,
|
14
|
+
handleFocus,
|
15
|
+
handleBlur,
|
16
|
+
};
|
17
|
+
};
|
18
|
+
exports.default = useEditorFocus;
|
package/lib/index.css
CHANGED
@@ -835,6 +835,14 @@
|
|
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);
|
841
|
+
box-shadow:
|
842
|
+
var(--tw-ring-offset-shadow, 0 0 #0000),
|
843
|
+
var(--tw-ring-shadow, 0 0 #0000),
|
844
|
+
var(--tw-shadow);
|
845
|
+
}
|
838
846
|
.squiz-fte-scope .editor-toolbar,
|
839
847
|
.squiz-fte-scope__floating-popover {
|
840
848
|
border-bottom-width: 2px;
|
@@ -846,6 +854,7 @@
|
|
846
854
|
padding: 0.25rem;
|
847
855
|
display: flex;
|
848
856
|
justify-items: center;
|
857
|
+
flex-wrap: wrap;
|
849
858
|
}
|
850
859
|
.squiz-fte-scope .editor-toolbar > *:not(:first-child, .MuiDivider-root),
|
851
860
|
.squiz-fte-scope__floating-popover > *:not(:first-child, .MuiDivider-root) {
|
@@ -857,6 +866,8 @@
|
|
857
866
|
margin-bottom: -0.25rem;
|
858
867
|
margin-left: 0.25rem;
|
859
868
|
margin-right: 0.25rem;
|
869
|
+
margin-top: 0px;
|
870
|
+
margin-bottom: 0px;
|
860
871
|
border-width: 1px;
|
861
872
|
margin-right: 2px;
|
862
873
|
height: auto;
|
@@ -870,6 +881,18 @@
|
|
870
881
|
.squiz-fte-scope__floating-popover .squiz-fte-btn ~ .squiz-fte-btn {
|
871
882
|
margin-left: 2px;
|
872
883
|
}
|
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;
|
893
|
+
opacity: 0;
|
894
|
+
transition: max-height 0.3s ease, opacity 0.4s ease;
|
895
|
+
}
|
873
896
|
.squiz-fte-scope__floating-popover {
|
874
897
|
display: flex;
|
875
898
|
border-radius: 6px;
|
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.13",
|
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.13",
|
24
|
+
"@squiz/resource-browser": "1.35.1-alpha.13",
|
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": "1bfc8c5f43eb37847e86d3dc77b4e30d203a703b"
|
79
79
|
}
|
@@ -5,7 +5,23 @@ 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);
|
9
|
+
const handleFocusMock = jest.fn();
|
10
|
+
const handleBlurMock = jest.fn();
|
11
|
+
jest.mock('../hooks/useFocus', () => ({
|
12
|
+
__esModule: true,
|
13
|
+
default: () => ({
|
14
|
+
isEditorFocused: isEditorFocused(),
|
15
|
+
handleFocus: handleFocusMock,
|
16
|
+
handleBlur: handleBlurMock,
|
17
|
+
}),
|
18
|
+
}));
|
19
|
+
|
8
20
|
describe('Formatted text editor', () => {
|
21
|
+
afterEach(() => {
|
22
|
+
jest.clearAllMocks();
|
23
|
+
});
|
24
|
+
|
9
25
|
it('Renders the text editor', () => {
|
10
26
|
render(<Editor />);
|
11
27
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
@@ -311,4 +327,38 @@ describe('Formatted text editor', () => {
|
|
311
327
|
|
312
328
|
expect(buttonLabels).toEqual([]);
|
313
329
|
});
|
330
|
+
|
331
|
+
it('triggers handleFocus when editor is focused', async () => {
|
332
|
+
const { getByLabelText } = render(<Editor />);
|
333
|
+
const editorInput = getByLabelText('Text editor');
|
334
|
+
|
335
|
+
fireEvent.focus(editorInput);
|
336
|
+
|
337
|
+
expect(handleBlurMock).toHaveBeenCalledTimes(0);
|
338
|
+
expect(handleFocusMock).toHaveBeenCalledTimes(1);
|
339
|
+
});
|
340
|
+
|
341
|
+
it('triggers handleBlur when editor is blurred', () => {
|
342
|
+
const { getByLabelText } = render(<Editor />);
|
343
|
+
const editorInput = getByLabelText('Text editor');
|
344
|
+
|
345
|
+
fireEvent.blur(editorInput);
|
346
|
+
|
347
|
+
expect(handleBlurMock).toHaveBeenCalledTimes(1);
|
348
|
+
expect(handleFocusMock).toHaveBeenCalledTimes(0);
|
349
|
+
});
|
350
|
+
|
351
|
+
it('should apply hide class when focus hook returns false', () => {
|
352
|
+
isEditorFocused.mockReturnValue(false);
|
353
|
+
const { container } = render(<Editor />);
|
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();
|
363
|
+
});
|
314
364
|
});
|
package/src/Editor/Editor.tsx
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
import React, { useContext, useCallback } from 'react';
|
1
|
+
import React, { useContext, useCallback, ReactNode } from 'react';
|
2
2
|
import { EditorComponent, Remirror, useRemirror, useEditorEvent } from '@remirror/react';
|
3
3
|
import { RemirrorContentType, RemirrorEventListener, Extension } from '@remirror/core';
|
4
|
+
import { ClipboardEventHandler } from '@remirror/extension-events/dist-types/events-extension';
|
5
|
+
import clsx from 'clsx';
|
4
6
|
import { Toolbar, FloatingToolbar } from '../EditorToolbar';
|
5
7
|
import { EditorContext } from './EditorContext';
|
6
8
|
import { createExtensions } from '../Extensions/Extensions';
|
7
|
-
import
|
8
|
-
import { ClipboardEventHandler } from '@remirror/extension-events/dist-types/events-extension';
|
9
|
+
import useEditorFocus from '../hooks/useFocus';
|
9
10
|
|
10
11
|
type EditorProps = {
|
11
12
|
content?: RemirrorContentType;
|
12
13
|
onChange?: RemirrorEventListener<Extension>;
|
13
14
|
editable?: boolean;
|
15
|
+
children?: ReactNode;
|
14
16
|
};
|
15
17
|
|
16
18
|
const WrappedEditor = () => {
|
@@ -29,7 +31,7 @@ const WrappedEditor = () => {
|
|
29
31
|
return <EditorComponent />;
|
30
32
|
};
|
31
33
|
|
32
|
-
const Editor = ({ content, editable = true, onChange }: EditorProps) => {
|
34
|
+
const Editor = ({ content, editable = true, onChange, children }: EditorProps) => {
|
33
35
|
const { manager, state, setState } = useRemirror({
|
34
36
|
extensions: createExtensions(useContext(EditorContext)),
|
35
37
|
content,
|
@@ -42,9 +44,15 @@ const Editor = ({ content, editable = true, onChange }: EditorProps) => {
|
|
42
44
|
onChange?.(parameter);
|
43
45
|
};
|
44
46
|
|
47
|
+
const { isEditorFocused, handleFocus, handleBlur } = useEditorFocus();
|
48
|
+
|
45
49
|
return (
|
46
50
|
<div className="squiz-fte-scope">
|
47
|
-
<div
|
51
|
+
<div
|
52
|
+
onBlur={handleBlur}
|
53
|
+
onFocus={handleFocus}
|
54
|
+
className={clsx('remirror-theme formatted-text-editor', !editable && 'formatted-text-editor--is-disabled')}
|
55
|
+
>
|
48
56
|
<Remirror
|
49
57
|
manager={manager}
|
50
58
|
state={state}
|
@@ -53,7 +61,8 @@ const Editor = ({ content, editable = true, onChange }: EditorProps) => {
|
|
53
61
|
placeholder="Write something"
|
54
62
|
label="Text editor"
|
55
63
|
>
|
56
|
-
{editable && <Toolbar />}
|
64
|
+
{editable && <Toolbar isEditorFocused={isEditorFocused} />}
|
65
|
+
{children}
|
57
66
|
<WrappedEditor />
|
58
67
|
{editable && <FloatingToolbar />}
|
59
68
|
</Remirror>
|
package/src/Editor/_editor.scss
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { Toolbar as RemirrorToolbar, VerticalDivider } from '@remirror/react-components';
|
3
|
+
import clsx from 'clsx';
|
3
4
|
import ItalicButton from './Tools/Italic/ItalicButton';
|
4
5
|
import UnderlineButton from './Tools/Underline/UnderlineButton';
|
5
6
|
import BoldButton from './Tools/Bold/BoldButton';
|
@@ -7,17 +8,20 @@ import TextAlignButtons from './Tools/TextAlign/TextAlignButtons';
|
|
7
8
|
import UndoButton from './Tools/Undo/UndoButton';
|
8
9
|
import RedoButton from './Tools/Redo/RedoButton';
|
9
10
|
import TextTypeDropdown from './Tools/TextType/TextTypeDropdown';
|
10
|
-
import { useExtensionNames } from '../hooks';
|
11
11
|
import LinkButton from './Tools/Link/LinkButton';
|
12
12
|
import ImageButton from './Tools/Image/ImageButton';
|
13
13
|
import RemoveLinkButton from './Tools/Link/RemoveLinkButton';
|
14
14
|
import ClearFormattingButton from './Tools/ClearFormatting/ClearFormattingButton';
|
15
|
+
import { useExtensionNames } from '../hooks';
|
15
16
|
|
16
|
-
|
17
|
+
type ToolbarProps = {
|
18
|
+
isEditorFocused: boolean;
|
19
|
+
};
|
20
|
+
export const Toolbar = ({ isEditorFocused }: ToolbarProps) => {
|
17
21
|
const extensionNames = useExtensionNames();
|
18
22
|
|
19
23
|
return (
|
20
|
-
<RemirrorToolbar className=
|
24
|
+
<RemirrorToolbar className={clsx('remirror-toolbar editor-toolbar', isEditorFocused === true ? 'show' : 'hide')}>
|
21
25
|
{extensionNames.history && (
|
22
26
|
<>
|
23
27
|
<UndoButton />
|
@@ -3,20 +3,34 @@
|
|
3
3
|
|
4
4
|
display: flex;
|
5
5
|
justify-items: center;
|
6
|
+
flex-wrap: wrap;
|
6
7
|
|
7
8
|
> *:not(:first-child, .MuiDivider-root) {
|
8
9
|
margin: 0 0 0 2px;
|
9
10
|
}
|
10
11
|
|
11
12
|
.MuiDivider-root {
|
12
|
-
@apply -my-1 mx-1 border;
|
13
|
+
@apply -my-1 mx-1 border mt-0 mb-0;
|
13
14
|
margin-right: 2px;
|
14
15
|
height: auto;
|
15
16
|
}
|
17
|
+
|
16
18
|
.squiz-fte-btn {
|
17
19
|
@apply p-1 font-bold;
|
18
20
|
~ .squiz-fte-btn {
|
19
21
|
margin-left: 2px;
|
20
22
|
}
|
21
23
|
}
|
24
|
+
|
25
|
+
&.show {
|
26
|
+
max-height: 200px;
|
27
|
+
opacity: 1;
|
28
|
+
transition: max-height 0.3s ease, opacity 0.4s ease;
|
29
|
+
}
|
30
|
+
|
31
|
+
&.hide {
|
32
|
+
max-height: 0;
|
33
|
+
opacity: 0;
|
34
|
+
transition: max-height 0.3s ease, opacity 0.4s ease;
|
35
|
+
}
|
22
36
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { useState, useCallback } from 'react';
|
2
|
+
|
3
|
+
const useEditorFocus = () => {
|
4
|
+
const [isEditorFocused, setIsEditorFocused] = useState(false);
|
5
|
+
|
6
|
+
const handleFocus = useCallback(() => {
|
7
|
+
setIsEditorFocused(true);
|
8
|
+
}, []);
|
9
|
+
|
10
|
+
const handleBlur = useCallback(() => {
|
11
|
+
setIsEditorFocused(false);
|
12
|
+
}, []);
|
13
|
+
|
14
|
+
return {
|
15
|
+
isEditorFocused,
|
16
|
+
handleFocus,
|
17
|
+
handleBlur,
|
18
|
+
};
|
19
|
+
};
|
20
|
+
|
21
|
+
export default useEditorFocus;
|