@squiz/formatted-text-editor 1.21.1-alpha.3 → 1.21.1-alpha.4
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/EditorToolbar/Toolbar.js +3 -1
- package/lib/EditorToolbar/Tools/Bold/BoldButton.js +2 -2
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +17 -0
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +84 -0
- package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +67 -0
- package/lib/EditorToolbar/Tools/Image/ImageModal.d.ts +8 -0
- package/lib/EditorToolbar/Tools/Image/ImageModal.js +19 -0
- package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +2 -2
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +5 -5
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +2 -2
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -2
- package/lib/EditorToolbar/Tools/Redo/RedoButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +2 -2
- package/lib/EditorToolbar/Tools/Undo/UndoButton.js +2 -2
- package/lib/Extensions/Extensions.d.ts +2 -1
- package/lib/Extensions/Extensions.js +2 -0
- package/lib/Extensions/ImageExtension/ImageExtension.d.ts +3 -0
- package/lib/Extensions/ImageExtension/ImageExtension.js +7 -0
- package/lib/index.css +128 -74
- package/lib/ui/Button/Button.d.ts +11 -0
- package/lib/ui/Button/Button.js +14 -0
- package/lib/ui/Fields/Input/Input.d.ts +4 -0
- package/lib/ui/{Inputs/Text/TextInput.js → Fields/Input/Input.js} +4 -4
- package/package.json +4 -3
- package/src/Editor/_editor.scss +2 -49
- package/src/EditorToolbar/Toolbar.tsx +2 -0
- package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +23 -0
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +92 -0
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +79 -0
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +57 -0
- package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +83 -0
- package/src/EditorToolbar/Tools/Image/ImageModal.tsx +29 -0
- package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +5 -5
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +2 -2
- package/src/EditorToolbar/_floating-toolbar.scss +6 -0
- package/src/EditorToolbar/_toolbar.scss +11 -5
- package/src/Extensions/Extensions.ts +2 -0
- package/src/Extensions/ImageExtension/ImageExtension.ts +3 -0
- package/src/index.scss +2 -2
- package/src/ui/Button/Button.spec.tsx +44 -0
- package/src/ui/Button/Button.tsx +31 -0
- package/src/ui/{_buttons.scss → Button/_button.scss} +19 -1
- package/src/ui/{Inputs/Text/TextInput.spec.tsx → Fields/Input/Input.spec.tsx} +8 -8
- package/src/ui/{Inputs/Text/TextInput.tsx → Fields/Input/Input.tsx} +4 -4
- package/src/ui/_typography.scss +46 -0
- package/lib/ui/Inputs/Text/TextInput.d.ts +0 -4
- package/lib/ui/ToolbarButton/ToolbarButton.d.ts +0 -10
- package/lib/ui/ToolbarButton/ToolbarButton.js +0 -10
- package/src/ui/ToolbarButton/ToolbarButton.tsx +0 -26
- package/src/ui/ToolbarButton/_toolbar-button.scss +0 -17
- /package/lib/ui/{Inputs → Fields}/Select/Select.d.ts +0 -0
- /package/lib/ui/{Inputs → Fields}/Select/Select.js +0 -0
- /package/src/ui/{Inputs → Fields}/Select/Select.spec.tsx +0 -0
- /package/src/ui/{Inputs → Fields}/Select/Select.tsx +0 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
2
|
+
import ImageRoundedIcon from '@mui/icons-material/ImageRounded';
|
3
|
+
import ImageModal from './ImageModal';
|
4
|
+
import { ImageFormData } from './Form/ImageForm';
|
5
|
+
import Button from '../../../ui/Button/Button';
|
6
|
+
import { useCommands, useKeymap } from '@remirror/react';
|
7
|
+
|
8
|
+
const ImageButton = () => {
|
9
|
+
const [showModal, setShowModal] = useState(false);
|
10
|
+
const { insertImage } = useCommands();
|
11
|
+
const active = showModal;
|
12
|
+
|
13
|
+
const handleClick = () => {
|
14
|
+
if (!showModal) {
|
15
|
+
// form element are uncontrolled, let the event loop run to
|
16
|
+
// update the selected text in state before showing the modal.
|
17
|
+
requestAnimationFrame(() => {
|
18
|
+
setShowModal(true);
|
19
|
+
});
|
20
|
+
}
|
21
|
+
};
|
22
|
+
|
23
|
+
const insertImageFromData = (data: ImageFormData) => {
|
24
|
+
const { src, alt, width, height } = data;
|
25
|
+
if (src) {
|
26
|
+
insertImage({ src, alt, width, height });
|
27
|
+
}
|
28
|
+
};
|
29
|
+
|
30
|
+
const handleSubmit = (data: ImageFormData) => {
|
31
|
+
insertImageFromData(data);
|
32
|
+
setShowModal(false);
|
33
|
+
};
|
34
|
+
|
35
|
+
const handleShortcut = useCallback(() => {
|
36
|
+
handleClick();
|
37
|
+
// Prevent other key handlers being run
|
38
|
+
return true;
|
39
|
+
}, []);
|
40
|
+
|
41
|
+
useKeymap('Mod-l', handleShortcut);
|
42
|
+
|
43
|
+
return (
|
44
|
+
<>
|
45
|
+
<Button
|
46
|
+
handleOnClick={handleClick}
|
47
|
+
isActive={active}
|
48
|
+
icon={<ImageRoundedIcon />}
|
49
|
+
label="Image (cmd+L)"
|
50
|
+
isDisabled={false}
|
51
|
+
/>
|
52
|
+
{showModal && <ImageModal onCancel={() => setShowModal(false)} onSubmit={handleSubmit} />}
|
53
|
+
</>
|
54
|
+
);
|
55
|
+
};
|
56
|
+
|
57
|
+
export default ImageButton;
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { screen, fireEvent } from '@testing-library/react';
|
3
|
+
import React from 'react';
|
4
|
+
import { renderWithEditor } from '../../../../tests';
|
5
|
+
import ImageModal from './ImageModal';
|
6
|
+
|
7
|
+
const mockSubmitFunction = jest.fn();
|
8
|
+
const mockCancelFunction = jest.fn();
|
9
|
+
const setup = () => {
|
10
|
+
const utils = renderWithEditor(<ImageModal onCancel={mockCancelFunction} onSubmit={mockSubmitFunction} />);
|
11
|
+
const sourceInput = screen.getByRole('textbox', { name: /source/i }) as HTMLInputElement;
|
12
|
+
const altInput = screen.getByRole('textbox', { name: /alt/i }) as HTMLInputElement;
|
13
|
+
const widthInput = screen.getByRole('spinbutton', { name: /Width/i }) as HTMLInputElement;
|
14
|
+
const heightInput = screen.getByRole('spinbutton', { name: /Height/i }) as HTMLInputElement;
|
15
|
+
return {
|
16
|
+
sourceInput,
|
17
|
+
altInput,
|
18
|
+
widthInput,
|
19
|
+
heightInput,
|
20
|
+
...utils,
|
21
|
+
};
|
22
|
+
};
|
23
|
+
|
24
|
+
describe('ImageModal', () => {
|
25
|
+
it('Renders the modal with title', async () => {
|
26
|
+
await renderWithEditor(<ImageModal onCancel={mockCancelFunction} onSubmit={mockSubmitFunction} />);
|
27
|
+
const modalHeading = screen.getByRole('heading', { name: 'Image' });
|
28
|
+
expect(modalHeading).toBeInTheDocument();
|
29
|
+
});
|
30
|
+
|
31
|
+
it('Populates the source field when a source is supplied', async () => {
|
32
|
+
const { sourceInput } = setup();
|
33
|
+
fireEvent.change(sourceInput, { target: { value: 'https://httpcats.com/302.jpg' } });
|
34
|
+
expect(sourceInput.value).toBe('https://httpcats.com/302.jpg');
|
35
|
+
});
|
36
|
+
|
37
|
+
it('Renders empty width and height fields if the image source is empty', async () => {
|
38
|
+
const { sourceInput, widthInput, heightInput } = setup();
|
39
|
+
fireEvent.change(sourceInput, { target: { value: '' } });
|
40
|
+
expect(widthInput.value).toBe('');
|
41
|
+
expect(heightInput.value).toBe('');
|
42
|
+
});
|
43
|
+
|
44
|
+
it('Updates the height field with aspect ratio based value from width', async () => {
|
45
|
+
const { widthInput, heightInput } = setup();
|
46
|
+
fireEvent.change(widthInput, { target: { value: '300' } });
|
47
|
+
expect(widthInput.value).toBe('300');
|
48
|
+
expect(heightInput.value).toBe('168.75');
|
49
|
+
});
|
50
|
+
it('Updates the width field with aspect ratio based value from height', async () => {
|
51
|
+
const { widthInput, heightInput } = setup();
|
52
|
+
fireEvent.change(heightInput, { target: { value: '100' } });
|
53
|
+
expect(heightInput.value).toBe('100');
|
54
|
+
expect(widthInput.value).toBe('177.78');
|
55
|
+
});
|
56
|
+
it('Does not change the width when height is changed and aspect ratio link is off', () => {
|
57
|
+
const { widthInput, heightInput } = setup();
|
58
|
+
fireEvent.change(heightInput, { target: { value: '100' } });
|
59
|
+
expect(heightInput.value).toBe('100');
|
60
|
+
expect(widthInput.value).toBe('177.78');
|
61
|
+
fireEvent.click(screen.getByRole('button', { name: /constrain properties/i }));
|
62
|
+
fireEvent.change(heightInput, { target: { value: '200' } });
|
63
|
+
expect(heightInput.value).toBe('200');
|
64
|
+
expect(widthInput.value).toBe('177.78');
|
65
|
+
});
|
66
|
+
it('Does not change the height when width is changed and aspect ratio link is off', () => {
|
67
|
+
const { widthInput, heightInput } = setup();
|
68
|
+
fireEvent.change(widthInput, { target: { value: '450' } });
|
69
|
+
expect(widthInput.value).toBe('450');
|
70
|
+
expect(heightInput.value).toBe('253.13');
|
71
|
+
fireEvent.click(screen.getByRole('button', { name: /constrain properties/i }));
|
72
|
+
fireEvent.change(widthInput, { target: { value: '600' } });
|
73
|
+
expect(widthInput.value).toBe('600');
|
74
|
+
expect(heightInput.value).toBe('253.13');
|
75
|
+
});
|
76
|
+
it('Changes the icon when aspect ratio button is toggled', () => {
|
77
|
+
renderWithEditor(<ImageModal onCancel={mockCancelFunction} onSubmit={mockSubmitFunction} />);
|
78
|
+
expect(screen.getByTestId('InsertLinkRoundedIcon')).toBeInTheDocument();
|
79
|
+
fireEvent.click(screen.getByRole('button', { name: /constrain properties/i }));
|
80
|
+
expect(screen.queryByTestId('InsertLinkRoundedIcon')).not.toBeInTheDocument();
|
81
|
+
expect(screen.getByTestId('LinkOffIcon')).toBeInTheDocument();
|
82
|
+
});
|
83
|
+
});
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { getMarkRanges } from 'remirror';
|
2
|
+
import ImageForm, { ImageFormData } from './Form/ImageForm';
|
3
|
+
import React from 'react';
|
4
|
+
import { useRemirrorContext, useCurrentSelection } from '@remirror/react';
|
5
|
+
import FormModal from '../../../ui/Modal/FormModal';
|
6
|
+
import { SubmitHandler } from 'react-hook-form';
|
7
|
+
|
8
|
+
type ImageModalProps = {
|
9
|
+
onCancel: () => void;
|
10
|
+
onSubmit: SubmitHandler<ImageFormData>;
|
11
|
+
};
|
12
|
+
|
13
|
+
const ImageModal = ({ onCancel, onSubmit }: ImageModalProps) => {
|
14
|
+
const {
|
15
|
+
helpers,
|
16
|
+
view: { state },
|
17
|
+
} = useRemirrorContext();
|
18
|
+
const selection = useCurrentSelection();
|
19
|
+
const currentImage = getMarkRanges(selection, 'image')[0];
|
20
|
+
const selectedImage = helpers.getTextBetween(selection.from, selection.to, state.doc);
|
21
|
+
|
22
|
+
return (
|
23
|
+
<FormModal title="Image" onCancel={onCancel}>
|
24
|
+
<ImageForm data={{ ...currentImage?.mark.attrs, src: selectedImage }} onSubmit={onSubmit} />
|
25
|
+
</FormModal>
|
26
|
+
);
|
27
|
+
};
|
28
|
+
|
29
|
+
export default ImageModal;
|
@@ -14,6 +14,6 @@ describe('Italic button', () => {
|
|
14
14
|
expect(screen.getByRole('button', { name: 'Italic (cmd+I)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
|
15
15
|
const italic = screen.getByRole('button', { name: 'Italic (cmd+I)' });
|
16
16
|
fireEvent.click(italic);
|
17
|
-
expect(italic.classList.contains('is-active')).toBeTruthy();
|
17
|
+
expect(italic.classList.contains('squiz-fte-btn--is-active')).toBeTruthy();
|
18
18
|
});
|
19
19
|
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useActive, useChainedCommands } from '@remirror/react';
|
3
3
|
import { ItalicExtension } from '@remirror/extension-italic';
|
4
|
-
import
|
4
|
+
import Button from '../../../ui/Button/Button';
|
5
5
|
import FormatItalicRoundedIcon from '@mui/icons-material/FormatItalicRounded';
|
6
6
|
|
7
7
|
const ItalicButton = () => {
|
@@ -17,7 +17,7 @@ const ItalicButton = () => {
|
|
17
17
|
};
|
18
18
|
|
19
19
|
return (
|
20
|
-
<
|
20
|
+
<Button
|
21
21
|
handleOnClick={handleSelect}
|
22
22
|
isDisabled={!enabled}
|
23
23
|
isActive={active.italic()}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React, { ReactElement } from 'react';
|
2
|
-
import {
|
3
|
-
import { Select, SelectOptions } from '../../../../ui/
|
2
|
+
import { Input } from '../../../../ui/Fields/Input/Input';
|
3
|
+
import { Select, SelectOptions } from '../../../../ui/Fields/Select/Select';
|
4
4
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
5
5
|
import { UpdateLinkOptions } from '../../../../Extensions/LinkExtension/LinkExtension';
|
6
6
|
|
@@ -24,13 +24,13 @@ const LinkForm = ({ data, onSubmit }: FormProps): ReactElement => {
|
|
24
24
|
return (
|
25
25
|
<form className="squiz-fte-form" onSubmit={handleSubmit(onSubmit)}>
|
26
26
|
<div className="squiz-fte-form-group mb-2">
|
27
|
-
<
|
27
|
+
<Input label="URL" {...register('href')} />
|
28
28
|
</div>
|
29
29
|
<div className="squiz-fte-form-group mb-2">
|
30
|
-
<
|
30
|
+
<Input label="Text" {...register('text')} />
|
31
31
|
</div>
|
32
32
|
<div className="squiz-fte-form-group mb-2">
|
33
|
-
<
|
33
|
+
<Input label="Title" {...register('title')} />
|
34
34
|
</div>
|
35
35
|
<div className="squiz-fte-form-group mb-0">
|
36
36
|
<Select
|
@@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react';
|
|
2
2
|
import InsertLinkRoundedIcon from '@mui/icons-material/InsertLinkRounded';
|
3
3
|
import LinkModal from './LinkModal';
|
4
4
|
import { LinkFormData } from './Form/LinkForm';
|
5
|
-
import
|
5
|
+
import Button from '../../../ui/Button/Button';
|
6
6
|
import { useActive, useCommands, useExtensionEvent } from '@remirror/react';
|
7
7
|
import { LinkExtension } from '../../../Extensions/LinkExtension/LinkExtension';
|
8
8
|
|
@@ -42,7 +42,7 @@ const LinkButton = ({ inPopover = false }: LinkButtonProps) => {
|
|
42
42
|
|
43
43
|
return (
|
44
44
|
<>
|
45
|
-
<
|
45
|
+
<Button
|
46
46
|
handleOnClick={handleClick}
|
47
47
|
isActive={active.link()}
|
48
48
|
icon={<InsertLinkRoundedIcon />}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useRemirrorContext, useChainedCommands } from '@remirror/react';
|
3
|
-
import
|
3
|
+
import Button from '../../../ui/Button/Button';
|
4
4
|
import LinkOffIcon from '@mui/icons-material/LinkOff';
|
5
5
|
|
6
6
|
const RemoveLinkButton = () => {
|
@@ -14,7 +14,7 @@ const RemoveLinkButton = () => {
|
|
14
14
|
};
|
15
15
|
|
16
16
|
return (
|
17
|
-
<
|
17
|
+
<Button
|
18
18
|
handleOnClick={handleClick}
|
19
19
|
isActive={false}
|
20
20
|
isDisabled={!enabled}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useHelpers } from '@remirror/react';
|
3
3
|
import { HistoryExtension } from 'remirror/extensions';
|
4
|
-
import
|
4
|
+
import Button from '../../../ui/Button/Button';
|
5
5
|
import RedoRoundedIcon from '@mui/icons-material/RedoRounded';
|
6
6
|
|
7
7
|
const RedoButton = () => {
|
@@ -17,7 +17,7 @@ const RedoButton = () => {
|
|
17
17
|
const enabled = redoDepth() > 0;
|
18
18
|
|
19
19
|
return (
|
20
|
-
<
|
20
|
+
<Button
|
21
21
|
handleOnClick={handleSelect}
|
22
22
|
isDisabled={!enabled}
|
23
23
|
isActive={false}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands } from '@remirror/react';
|
3
3
|
import { NodeFormattingExtension } from '@remirror/extension-node-formatting';
|
4
|
-
import
|
4
|
+
import Button from '../../../../ui/Button/Button';
|
5
5
|
import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter';
|
6
6
|
|
7
7
|
const CenterAlignButton = () => {
|
@@ -18,7 +18,7 @@ const CenterAlignButton = () => {
|
|
18
18
|
const enabled = centerAlign.enabled();
|
19
19
|
|
20
20
|
return (
|
21
|
-
<
|
21
|
+
<Button
|
22
22
|
handleOnClick={handleSelect}
|
23
23
|
isDisabled={!enabled}
|
24
24
|
isActive={active}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands } from '@remirror/react';
|
3
3
|
import { NodeFormattingExtension } from '@remirror/extension-node-formatting';
|
4
|
-
import
|
4
|
+
import Button from '../../../../ui/Button/Button';
|
5
5
|
import FormatAlignJustifyIcon from '@mui/icons-material/FormatAlignJustify';
|
6
6
|
|
7
7
|
const JustifyAlignButton = () => {
|
@@ -18,7 +18,7 @@ const JustifyAlignButton = () => {
|
|
18
18
|
const enabled = justifyAlign.enabled();
|
19
19
|
|
20
20
|
return (
|
21
|
-
<
|
21
|
+
<Button
|
22
22
|
handleOnClick={handleSelect}
|
23
23
|
isDisabled={!enabled}
|
24
24
|
isActive={active}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands } from '@remirror/react';
|
3
3
|
import { NodeFormattingExtension } from '@remirror/extension-node-formatting';
|
4
|
-
import
|
4
|
+
import Button from '../../../../ui/Button/Button';
|
5
5
|
import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft';
|
6
6
|
|
7
7
|
const LeftAlignButton = () => {
|
@@ -18,7 +18,7 @@ const LeftAlignButton = () => {
|
|
18
18
|
const enabled = leftAlign.enabled();
|
19
19
|
|
20
20
|
return (
|
21
|
-
<
|
21
|
+
<Button
|
22
22
|
handleOnClick={handleSelect}
|
23
23
|
isDisabled={!enabled}
|
24
24
|
isActive={active}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useChainedCommands } from '@remirror/react';
|
3
3
|
import { NodeFormattingExtension } from '@remirror/extension-node-formatting';
|
4
|
-
import
|
4
|
+
import Button from '../../../../ui/Button/Button';
|
5
5
|
import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight';
|
6
6
|
|
7
7
|
const RightAlignButton = () => {
|
@@ -18,7 +18,7 @@ const RightAlignButton = () => {
|
|
18
18
|
const enabled = rightAlign.enabled();
|
19
19
|
|
20
20
|
return (
|
21
|
-
<
|
21
|
+
<Button
|
22
22
|
handleOnClick={handleSelect}
|
23
23
|
isDisabled={!enabled}
|
24
24
|
isActive={active}
|
@@ -14,6 +14,6 @@ describe('Underline button', () => {
|
|
14
14
|
expect(screen.getByRole('button', { name: 'Underline (cmd+U)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
|
15
15
|
const underline = screen.getByRole('button', { name: 'Underline (cmd+U)' });
|
16
16
|
fireEvent.click(underline);
|
17
|
-
expect(underline.classList.contains('is-active')).toBeTruthy();
|
17
|
+
expect(underline.classList.contains('squiz-fte-btn--is-active')).toBeTruthy();
|
18
18
|
});
|
19
19
|
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useActive, useChainedCommands } from '@remirror/react';
|
3
3
|
import { UnderlineExtension } from '@remirror/extension-underline';
|
4
|
-
import
|
4
|
+
import Button from '../../../ui/Button/Button';
|
5
5
|
import FormatUnderlinedRoundedIcon from '@mui/icons-material/FormatUnderlinedRounded';
|
6
6
|
|
7
7
|
const UnderlineButton = () => {
|
@@ -17,7 +17,7 @@ const UnderlineButton = () => {
|
|
17
17
|
};
|
18
18
|
|
19
19
|
return (
|
20
|
-
<
|
20
|
+
<Button
|
21
21
|
handleOnClick={handleSelect}
|
22
22
|
isDisabled={!enabled}
|
23
23
|
isActive={active.underline()}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useHelpers } from '@remirror/react';
|
3
3
|
import { HistoryExtension } from 'remirror/extensions';
|
4
|
-
import
|
4
|
+
import Button from '../../../ui/Button/Button';
|
5
5
|
import UndoRoundedIcon from '@mui/icons-material/UndoRounded';
|
6
6
|
|
7
7
|
const UndoButton = () => {
|
@@ -17,7 +17,7 @@ const UndoButton = () => {
|
|
17
17
|
const enabled = undoDepth() > 0;
|
18
18
|
|
19
19
|
return (
|
20
|
-
<
|
20
|
+
<Button
|
21
21
|
handleOnClick={handleSelect}
|
22
22
|
isDisabled={!enabled}
|
23
23
|
isActive={false}
|
@@ -1,4 +1,10 @@
|
|
1
1
|
/// This class is excluded from the scope of squiz-fte-scope as it is outside of the scoped element
|
2
2
|
.squiz-fte-scope__floating-popover {
|
3
3
|
@apply bg-white border-gray-200 p-1 shadow rounded-md border flex;
|
4
|
+
.squiz-fte-btn {
|
5
|
+
@apply p-1;
|
6
|
+
~ .squiz-fte-btn {
|
7
|
+
margin-left: 2px;
|
8
|
+
}
|
9
|
+
}
|
4
10
|
}
|
@@ -7,10 +7,16 @@
|
|
7
7
|
> *:not(:first-child, .editor-divider) {
|
8
8
|
margin: 0 0 0 2px;
|
9
9
|
}
|
10
|
-
}
|
11
10
|
|
12
|
-
.editor-divider {
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
.editor-divider {
|
12
|
+
@apply -my-1 mx-1 border;
|
13
|
+
margin-right: 2px;
|
14
|
+
height: auto;
|
15
|
+
}
|
16
|
+
.squiz-fte-btn {
|
17
|
+
@apply p-1;
|
18
|
+
~ .squiz-fte-btn {
|
19
|
+
margin-left: 2px;
|
20
|
+
}
|
21
|
+
}
|
16
22
|
}
|
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
} from 'remirror/extensions';
|
10
10
|
import { PreformattedExtension } from './PreformattedExtension/PreformattedExtension';
|
11
11
|
import { LinkExtension } from './LinkExtension/LinkExtension';
|
12
|
+
import { ImageExtension } from './ImageExtension/ImageExtension';
|
12
13
|
|
13
14
|
export const Extensions = () => [
|
14
15
|
new BoldExtension(),
|
@@ -19,6 +20,7 @@ export const Extensions = () => [
|
|
19
20
|
new PreformattedExtension(),
|
20
21
|
new UnderlineExtension(),
|
21
22
|
new HistoryExtension(),
|
23
|
+
new ImageExtension(),
|
22
24
|
new LinkExtension({
|
23
25
|
supportedTargets: [
|
24
26
|
// '_self' is the browser default and will be used when encountering a link with a
|
package/src/index.scss
CHANGED
@@ -4,15 +4,15 @@
|
|
4
4
|
@import 'tailwindcss/utilities';
|
5
5
|
|
6
6
|
/* Global */
|
7
|
+
@import './ui/typography';
|
7
8
|
@import './ui/forms';
|
8
|
-
@import './ui/buttons';
|
9
9
|
|
10
10
|
/* Components */
|
11
11
|
@import './Editor/editor';
|
12
12
|
@import './EditorToolbar/toolbar';
|
13
13
|
@import './EditorToolbar/floating-toolbar';
|
14
14
|
|
15
|
-
@import './ui/
|
15
|
+
@import './ui/Button/button';
|
16
16
|
@import './ui/ToolbarDropdown/toolbar-dropdown';
|
17
17
|
@import './ui/ToolbarDropdownButton/toolbar-dropdown-button';
|
18
18
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, screen } from '@testing-library/react';
|
3
|
+
import React from 'react';
|
4
|
+
import Button from './Button';
|
5
|
+
import AccessTimeRoundedIcon from '@mui/icons-material/AccessTimeRounded';
|
6
|
+
|
7
|
+
describe('Button', () => {
|
8
|
+
const mockOnClick = jest.fn();
|
9
|
+
|
10
|
+
const ButtonComponent = () => {
|
11
|
+
return (
|
12
|
+
<Button
|
13
|
+
handleOnClick={mockOnClick}
|
14
|
+
isDisabled
|
15
|
+
isActive
|
16
|
+
label="Am a button"
|
17
|
+
text="Hello"
|
18
|
+
icon={<AccessTimeRoundedIcon />}
|
19
|
+
/>
|
20
|
+
);
|
21
|
+
};
|
22
|
+
|
23
|
+
it('Renders the label, text and icon', () => {
|
24
|
+
render(<ButtonComponent />);
|
25
|
+
const label = screen.getByLabelText('Am a button');
|
26
|
+
expect(label).toBeInTheDocument();
|
27
|
+
const text = screen.getByText('Hello');
|
28
|
+
expect(text).toBeInTheDocument();
|
29
|
+
const icon = screen.getByTestId('AccessTimeRoundedIcon');
|
30
|
+
expect(icon).toBeInTheDocument();
|
31
|
+
});
|
32
|
+
|
33
|
+
it('Renders the button in a disabled state if set to be disabled', () => {
|
34
|
+
render(<ButtonComponent />);
|
35
|
+
const button = screen.getByLabelText('Am a button');
|
36
|
+
expect(button).toBeDisabled();
|
37
|
+
});
|
38
|
+
|
39
|
+
it('Adds the active class is set to be active', () => {
|
40
|
+
render(<ButtonComponent />);
|
41
|
+
const button = screen.getByLabelText('Am a button');
|
42
|
+
expect(button).toHaveClass('squiz-fte-btn--is-active');
|
43
|
+
});
|
44
|
+
});
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React, { ReactElement } from 'react';
|
2
|
+
import clsx from 'clsx';
|
3
|
+
|
4
|
+
type ButtonProps = {
|
5
|
+
handleOnClick: () => void;
|
6
|
+
isDisabled?: boolean;
|
7
|
+
isActive: boolean;
|
8
|
+
label: string;
|
9
|
+
text?: string;
|
10
|
+
icon?: ReactElement;
|
11
|
+
};
|
12
|
+
|
13
|
+
const Button = ({ handleOnClick, isDisabled, isActive, label, text, icon }: ButtonProps) => {
|
14
|
+
return (
|
15
|
+
<button
|
16
|
+
aria-label={label}
|
17
|
+
title={label}
|
18
|
+
type="button"
|
19
|
+
onClick={handleOnClick}
|
20
|
+
disabled={isDisabled}
|
21
|
+
className={clsx('squiz-fte-btn', isActive && 'squiz-fte-btn--is-active', icon && ' squiz-fte-btn--is-icon')}
|
22
|
+
>
|
23
|
+
<>
|
24
|
+
{text && <span>{text}</span>}
|
25
|
+
{icon && icon}
|
26
|
+
</>
|
27
|
+
</button>
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
export default Button;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
.squiz-fte-btn {
|
2
|
-
@apply font-normal rounded ease-linear transition-all duration-150;
|
2
|
+
@apply font-normal rounded ease-linear transition-all bg-white text-gray-600 duration-150;
|
3
3
|
display: flex;
|
4
4
|
align-items: center;
|
5
5
|
text-align: center;
|
@@ -11,9 +11,27 @@
|
|
11
11
|
border: 1px solid transparent;
|
12
12
|
padding: 6px 12px;
|
13
13
|
|
14
|
+
&--is-icon {
|
15
|
+
padding: 6px;
|
16
|
+
}
|
17
|
+
|
18
|
+
~ .squiz-fte-btn {
|
19
|
+
margin-left: 2px;
|
20
|
+
}
|
21
|
+
|
14
22
|
&.disabled,
|
15
23
|
&[disabled] {
|
16
24
|
cursor: not-allowed;
|
17
25
|
@apply opacity-50;
|
18
26
|
}
|
27
|
+
|
28
|
+
&:hover,
|
29
|
+
&:focus {
|
30
|
+
background-color: rgba(black, 0.04);
|
31
|
+
}
|
32
|
+
|
33
|
+
&--is-active,
|
34
|
+
&:active {
|
35
|
+
@apply text-blue-300 bg-blue-100;
|
36
|
+
}
|
19
37
|
}
|
@@ -1,30 +1,30 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react';
|
3
3
|
import React from 'react';
|
4
|
-
import {
|
4
|
+
import { Input } from './Input';
|
5
5
|
|
6
|
-
describe('
|
6
|
+
describe('Input', () => {
|
7
7
|
const mockOnChange = jest.fn();
|
8
8
|
|
9
|
-
const
|
10
|
-
return <
|
9
|
+
const InputComponent = () => {
|
10
|
+
return <Input name="text-input" defaultValue="Water" label="Text input" onChange={mockOnChange} />;
|
11
11
|
};
|
12
12
|
|
13
13
|
it('Renders the label', () => {
|
14
|
-
render(<
|
14
|
+
render(<InputComponent />);
|
15
15
|
// Check that the supplied label renders
|
16
16
|
const inputLabel = screen.getByLabelText('Text input');
|
17
17
|
expect(inputLabel).toBeInTheDocument();
|
18
18
|
});
|
19
19
|
|
20
20
|
it('Renders the default value', () => {
|
21
|
-
render(<
|
21
|
+
render(<InputComponent />);
|
22
22
|
// Check that default value supplied renders
|
23
23
|
expect(screen.getByDisplayValue('Water')).toBeInTheDocument();
|
24
24
|
});
|
25
25
|
|
26
26
|
it('Changes the value when new value entered', () => {
|
27
|
-
render(<
|
27
|
+
render(<InputComponent />);
|
28
28
|
const input = screen.getByLabelText('Text input') as HTMLInputElement;
|
29
29
|
// Check that default value supplied renders
|
30
30
|
expect(input.value).toBe('Water');
|
@@ -33,7 +33,7 @@ describe('Text input', () => {
|
|
33
33
|
});
|
34
34
|
|
35
35
|
it('Fires the change function when new value entered', () => {
|
36
|
-
render(<
|
36
|
+
render(<InputComponent />);
|
37
37
|
const input = screen.getByLabelText('Text input') as HTMLInputElement;
|
38
38
|
// Check that default value supplied renders
|
39
39
|
expect(input.value).toBe('Water');
|