@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.
Files changed (72) hide show
  1. package/lib/EditorToolbar/Toolbar.js +3 -1
  2. package/lib/EditorToolbar/Tools/Bold/BoldButton.js +2 -2
  3. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +17 -0
  4. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +84 -0
  5. package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +2 -0
  6. package/lib/EditorToolbar/Tools/Image/ImageButton.js +67 -0
  7. package/lib/EditorToolbar/Tools/Image/ImageModal.d.ts +8 -0
  8. package/lib/EditorToolbar/Tools/Image/ImageModal.js +19 -0
  9. package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +2 -2
  10. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +5 -5
  11. package/lib/EditorToolbar/Tools/Link/LinkButton.js +2 -2
  12. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -2
  13. package/lib/EditorToolbar/Tools/Redo/RedoButton.js +2 -2
  14. package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.js +2 -2
  15. package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.js +2 -2
  16. package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.js +2 -2
  17. package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.js +2 -2
  18. package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +2 -2
  19. package/lib/EditorToolbar/Tools/Undo/UndoButton.js +2 -2
  20. package/lib/Extensions/Extensions.d.ts +2 -1
  21. package/lib/Extensions/Extensions.js +2 -0
  22. package/lib/Extensions/ImageExtension/ImageExtension.d.ts +3 -0
  23. package/lib/Extensions/ImageExtension/ImageExtension.js +7 -0
  24. package/lib/index.css +128 -74
  25. package/lib/ui/Button/Button.d.ts +11 -0
  26. package/lib/ui/Button/Button.js +14 -0
  27. package/lib/ui/Fields/Input/Input.d.ts +4 -0
  28. package/lib/ui/{Inputs/Text/TextInput.js → Fields/Input/Input.js} +4 -4
  29. package/package.json +4 -3
  30. package/src/Editor/_editor.scss +2 -49
  31. package/src/EditorToolbar/Toolbar.tsx +2 -0
  32. package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +1 -1
  33. package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +2 -2
  34. package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +23 -0
  35. package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +92 -0
  36. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +79 -0
  37. package/src/EditorToolbar/Tools/Image/ImageButton.tsx +57 -0
  38. package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +83 -0
  39. package/src/EditorToolbar/Tools/Image/ImageModal.tsx +29 -0
  40. package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +1 -1
  41. package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +2 -2
  42. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +5 -5
  43. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +2 -2
  44. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -2
  45. package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +2 -2
  46. package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.tsx +2 -2
  47. package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.tsx +2 -2
  48. package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.tsx +2 -2
  49. package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.tsx +2 -2
  50. package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +1 -1
  51. package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +2 -2
  52. package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +2 -2
  53. package/src/EditorToolbar/_floating-toolbar.scss +6 -0
  54. package/src/EditorToolbar/_toolbar.scss +11 -5
  55. package/src/Extensions/Extensions.ts +2 -0
  56. package/src/Extensions/ImageExtension/ImageExtension.ts +3 -0
  57. package/src/index.scss +2 -2
  58. package/src/ui/Button/Button.spec.tsx +44 -0
  59. package/src/ui/Button/Button.tsx +31 -0
  60. package/src/ui/{_buttons.scss → Button/_button.scss} +19 -1
  61. package/src/ui/{Inputs/Text/TextInput.spec.tsx → Fields/Input/Input.spec.tsx} +8 -8
  62. package/src/ui/{Inputs/Text/TextInput.tsx → Fields/Input/Input.tsx} +4 -4
  63. package/src/ui/_typography.scss +46 -0
  64. package/lib/ui/Inputs/Text/TextInput.d.ts +0 -4
  65. package/lib/ui/ToolbarButton/ToolbarButton.d.ts +0 -10
  66. package/lib/ui/ToolbarButton/ToolbarButton.js +0 -10
  67. package/src/ui/ToolbarButton/ToolbarButton.tsx +0 -26
  68. package/src/ui/ToolbarButton/_toolbar-button.scss +0 -17
  69. /package/lib/ui/{Inputs → Fields}/Select/Select.d.ts +0 -0
  70. /package/lib/ui/{Inputs → Fields}/Select/Select.js +0 -0
  71. /package/src/ui/{Inputs → Fields}/Select/Select.spec.tsx +0 -0
  72. /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 ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 { TextInput } from '../../../../ui/Inputs/Text/TextInput';
3
- import { Select, SelectOptions } from '../../../../ui/Inputs/Select/Select';
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
- <TextInput label="URL" {...register('href')} />
27
+ <Input label="URL" {...register('href')} />
28
28
  </div>
29
29
  <div className="squiz-fte-form-group mb-2">
30
- <TextInput label="Text" {...register('text')} />
30
+ <Input label="Text" {...register('text')} />
31
31
  </div>
32
32
  <div className="squiz-fte-form-group mb-2">
33
- <TextInput label="Title" {...register('title')} />
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 ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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 ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
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
- <ToolbarButton
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
- @apply -my-1 mx-1 border;
14
- margin-right: 2px;
15
- height: auto;
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
@@ -0,0 +1,3 @@
1
+ import { ImageExtension as RemirrorImageExtension } from 'remirror/extensions';
2
+
3
+ export class ImageExtension extends RemirrorImageExtension {}
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/ToolbarButton/toolbar-button';
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 { TextInput } from './TextInput';
4
+ import { Input } from './Input';
5
5
 
6
- describe('Text input', () => {
6
+ describe('Input', () => {
7
7
  const mockOnChange = jest.fn();
8
8
 
9
- const TextInputComponent = () => {
10
- return <TextInput name="text-input" defaultValue="Water" label="Text input" onChange={mockOnChange} />;
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(<TextInputComponent />);
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(<TextInputComponent />);
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(<TextInputComponent />);
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(<TextInputComponent />);
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');