@squiz/formatted-text-editor 1.12.0-alpha.39 → 1.12.0-alpha.42

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 (57) hide show
  1. package/README.md +2 -0
  2. package/jest.config.ts +2 -0
  3. package/lib/Editor/Editor.js +2 -13
  4. package/lib/EditorToolbar/EditorToolbar.js +3 -1
  5. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +3 -0
  6. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +29 -0
  7. package/lib/EditorToolbar/Tools/Link/LinkButton.d.ts +7 -0
  8. package/lib/EditorToolbar/Tools/Link/LinkButton.js +8 -0
  9. package/lib/EditorToolbar/Tools/Link/Trigger/TriggerLinkModal.d.ts +3 -0
  10. package/lib/EditorToolbar/Tools/Link/Trigger/TriggerLinkModal.js +12 -0
  11. package/lib/Extensions/Extensions.d.ts +3 -0
  12. package/lib/Extensions/Extensions.js +13 -0
  13. package/lib/index.css +401 -3744
  14. package/lib/ui/Inputs/Select/Select.d.ts +12 -0
  15. package/lib/ui/Inputs/Select/Select.js +19 -0
  16. package/lib/ui/Inputs/Text/TextInput.d.ts +8 -0
  17. package/lib/ui/Inputs/Text/TextInput.js +6 -0
  18. package/lib/ui/Modal/Modal.d.ts +9 -0
  19. package/lib/ui/Modal/Modal.js +30 -0
  20. package/lib/ui/ToolbarButton/ToolbarButton.js +1 -1
  21. package/package.json +10 -7
  22. package/src/Editor/Editor.mock.tsx +40 -0
  23. package/src/Editor/Editor.spec.tsx +28 -2
  24. package/src/Editor/Editor.tsx +3 -23
  25. package/src/Editor/_editor.scss +16 -9
  26. package/src/EditorToolbar/EditorToolbar.tsx +2 -0
  27. package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +1 -1
  28. package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +1 -1
  29. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +21 -0
  30. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +41 -0
  31. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +26 -0
  32. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +25 -0
  33. package/src/EditorToolbar/Tools/Link/Trigger/TriggerLinkModal.spec.tsx +41 -0
  34. package/src/EditorToolbar/Tools/Link/Trigger/TriggerLinkModal.tsx +21 -0
  35. package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.tsx +1 -1
  36. package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.tsx +1 -1
  37. package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +1 -1
  38. package/src/EditorToolbar/_editor-toolbar.scss +1 -1
  39. package/src/Extensions/Extensions.tsx +24 -0
  40. package/src/FormattedTextEditor.spec.tsx +10 -0
  41. package/src/index.scss +5 -2
  42. package/src/ui/DropdownButton/_dropdown-button.scss +3 -2
  43. package/src/ui/Inputs/Select/Select.spec.tsx +24 -0
  44. package/src/ui/Inputs/Select/Select.tsx +67 -0
  45. package/src/ui/Inputs/Text/TextInput.spec.tsx +43 -0
  46. package/src/ui/Inputs/Text/TextInput.tsx +20 -0
  47. package/src/ui/Modal/Modal.spec.tsx +75 -0
  48. package/src/ui/Modal/Modal.tsx +70 -0
  49. package/src/ui/Modal/_modal.scss +24 -0
  50. package/src/ui/ToolbarButton/ToolbarButton.tsx +1 -1
  51. package/src/ui/ToolbarButton/_toolbar-button.scss +5 -25
  52. package/src/ui/_buttons.scss +19 -0
  53. package/src/ui/_forms.scss +16 -0
  54. package/tailwind.config.cjs +23 -0
  55. /package/lib/{extensions → Extensions}/PreformattedExtension/PreformattedExtension.d.ts +0 -0
  56. /package/lib/{extensions → Extensions}/PreformattedExtension/PreformattedExtension.js +0 -0
  57. /package/src/{extensions → Extensions}/PreformattedExtension/PreformattedExtension.tsx +0 -0
@@ -0,0 +1,12 @@
1
+ type SelectProps = {
2
+ id: string;
3
+ label?: string;
4
+ value: any;
5
+ options: {
6
+ id: number;
7
+ name: string;
8
+ key: string;
9
+ }[];
10
+ };
11
+ export declare const Select: ({ id, label, value, options }: SelectProps) => JSX.Element;
12
+ export {};
@@ -0,0 +1,19 @@
1
+ import { Listbox } from '@headlessui/react';
2
+ import React, { useState } from 'react';
3
+ import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded';
4
+ import ExpandLessRoundedIcon from '@mui/icons-material/ExpandLessRounded';
5
+ import clsx from 'clsx';
6
+ export const Select = ({ id, label, value, options }) => {
7
+ const [selectedOption, setSelectedOption] = useState(value);
8
+ return (React.createElement(React.Fragment, null,
9
+ label && (React.createElement("label", { htmlFor: id, className: "sds-form-label" }, label)),
10
+ React.createElement("div", { className: "relative text-md text-gray-800" },
11
+ React.createElement(Listbox, { value: selectedOption, onChange: setSelectedOption, name: id, "aria-labelledby": id }, ({ open }) => (React.createElement(React.Fragment, null,
12
+ React.createElement(Listbox.Label, { className: "sr-only" }, label),
13
+ React.createElement(Listbox.Button, { className: "relative w-full cursor-default rounded border-2 border-gray-300 bg-white py-2 pl-3 pr-10 focus:border-blue-300 focus:outline-none" },
14
+ React.createElement("span", { className: "flex items-center" },
15
+ React.createElement("span", { className: "block truncate" }, selectedOption.name)),
16
+ React.createElement("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2" }, !open ? (React.createElement(ExpandMoreRoundedIcon, { className: "text-gray-500", "aria-hidden": "true" })) : (React.createElement(ExpandLessRoundedIcon, { className: "text-gray-500", "aria-hidden": "true" })))),
17
+ React.createElement(Listbox.Options, { className: "absolute z-10 mt-1 w-full overflow-auto rounded border-2 border-gray-300 bg-white" }, options.map((option) => (React.createElement(Listbox.Option, { key: option.id, className: ({ active }) => clsx(active ? 'bg-gray-100' : 'bg-white', 'relative cursor-default select-none flex hover:bg-gray-100 py-2 px-3'), value: option },
18
+ React.createElement("span", { className: "block truncate" }, option.name)))))))))));
19
+ };
@@ -0,0 +1,8 @@
1
+ type TextInputProps = {
2
+ id: string;
3
+ label?: string;
4
+ value: string;
5
+ onChange: any;
6
+ };
7
+ export declare const TextInput: ({ id, label, value, ...rest }: TextInputProps) => JSX.Element;
8
+ export {};
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export const TextInput = ({ id, label, value, ...rest }) => {
3
+ return (React.createElement(React.Fragment, null,
4
+ label && (React.createElement("label", { htmlFor: id, className: "sds-form-label" }, label)),
5
+ React.createElement("input", { name: id, id: id, type: "text", className: "sds-form-control", defaultValue: value, ...rest })));
6
+ };
@@ -0,0 +1,9 @@
1
+ import { ReactElement } from 'react';
2
+ type ModalProps = {
3
+ children: ReactElement;
4
+ onCancel: () => void;
5
+ onSubmit?: () => void;
6
+ className?: string;
7
+ };
8
+ declare const Modal: ({ children, onCancel, onSubmit, className }: ModalProps) => ReactElement;
9
+ export default Modal;
@@ -0,0 +1,30 @@
1
+ import React, { useEffect } from 'react';
2
+ import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
3
+ const Modal = ({ children, onCancel, onSubmit, className }) => {
4
+ const keydown = (e) => {
5
+ if (e.key === 'Escape') {
6
+ onCancel();
7
+ }
8
+ if (e.key === 'Enter') {
9
+ onSubmit && onSubmit();
10
+ }
11
+ };
12
+ useEffect(() => {
13
+ window.addEventListener('keydown', keydown);
14
+ return () => window.removeEventListener('keydown', keydown);
15
+ }, []);
16
+ return (React.createElement(React.Fragment, null,
17
+ React.createElement("div", { className: `sds-modal-wrapper ${className ? className : ''}` },
18
+ React.createElement("div", { className: "w-modal-sm my-6 mx-auto" },
19
+ React.createElement("div", { className: "sds-modal" },
20
+ React.createElement("div", { className: "sds-modal-header p-6 pb-2" },
21
+ React.createElement("h2", { className: "font-semibold text-gray-900 text-heading-2" }, "Link"),
22
+ React.createElement("button", { className: "ml-auto -mr-3 -mt-3 bg-transparent border-0 text-gray-600 font-semibold outline-none focus:outline-none hover:text-color-gray-800", onClick: onCancel, "aria-label": "Close" },
23
+ React.createElement(CloseRoundedIcon, null))),
24
+ React.createElement("div", { className: "sds-modal-content" }, children),
25
+ React.createElement("div", { className: "sds-modal-footer p-6 pt-3" },
26
+ React.createElement("button", { className: "sds-modal-footer__button bg-gray-200 text-gray-700 mr-2 hover:bg-gray-300", type: "button", onClick: onCancel, "aria-label": "Cancel" }, "Cancel"),
27
+ onSubmit && (React.createElement("button", { className: "sds-modal-footer__button bg-blue-300 text-white hover:bg-blue-400", type: "button", onClick: onSubmit, "aria-label": "Apply" }, "Apply")))))),
28
+ React.createElement("div", { className: "opacity-25 fixed inset-0 z-40 bg-black" })));
29
+ };
30
+ export default Modal;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
  const ToolbarButton = ({ handleOnClick, isDisabled, isActive, icon, label }) => {
3
- return (React.createElement("button", { "aria-label": label, title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `btn toolbar-button ${isActive ? 'is-active' : ''}` }, icon));
3
+ return (React.createElement("button", { "aria-label": label, title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: `sds-btn toolbar-button ${isActive ? 'is-active' : ''}` }, icon));
4
4
  };
5
5
  export default ToolbarButton;
package/package.json CHANGED
@@ -1,29 +1,32 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.12.0-alpha.39",
3
+ "version": "1.12.0-alpha.42",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
7
- "dev": "vite",
7
+ "build": "vite build",
8
8
  "clean": "rimraf ./lib",
9
9
  "compile": "npm run clean && npm run compile:styles && npm run compile:code",
10
10
  "compile:code": "tsc",
11
11
  "compile:styles": "node build.js",
12
+ "dev": "vite",
12
13
  "preview": "vite preview",
13
- "build": "vite build",
14
14
  "test": "jest",
15
- "test:watch": "jest --watch",
16
- "test:e2e": "vite build && vite preview --port 8080 & cypress open"
15
+ "test:e2e": "vite build && vite preview --port 8080 & cypress open",
16
+ "test:watch": "jest --watch"
17
17
  },
18
18
  "dependencies": {
19
+ "@headlessui/react": "1.7.11",
19
20
  "@mui/icons-material": "5.11.0",
20
21
  "@remirror/react": "2.0.25",
22
+ "clsx": "^1.2.1",
21
23
  "flowbite": "^1.6.3"
22
24
  },
23
25
  "devDependencies": {
24
26
  "@testing-library/cypress": "9.0.0",
25
27
  "@testing-library/jest-dom": "5.16.5",
26
- "@testing-library/react": "12.1.5",
28
+ "@testing-library/react": "14.0.0",
29
+ "@testing-library/user-event": "14.4.3",
27
30
  "@types/react": "18.0.26",
28
31
  "@types/react-dom": "18.0.9",
29
32
  "@vitejs/plugin-react": "3.0.0",
@@ -61,5 +64,5 @@
61
64
  "volta": {
62
65
  "node": "16.19.0"
63
66
  },
64
- "gitHead": "cfb46042a71dbebfa5f7f98459d99b84c2da9bed"
67
+ "gitHead": "e6a1ba4e3c9f944bd0006ed59673996dc5dccbfa"
65
68
  }
@@ -0,0 +1,40 @@
1
+ import { Remirror, useRemirror, useRemirrorContext } from '@remirror/react';
2
+ import React from 'react';
3
+ import { EditorToolbar } from '../EditorToolbar/EditorToolbar';
4
+ import { Extensions } from '../Extensions/Extensions';
5
+
6
+ type MockEditorProps = {
7
+ setContent?: any;
8
+ };
9
+
10
+ export const MockEditor = ({ setContent }: MockEditorProps) => {
11
+ const { manager, state, setState } = useRemirror({
12
+ extensions: Extensions,
13
+ selection: 'start',
14
+ stringHandler: 'html',
15
+ });
16
+
17
+ const Component = () => {
18
+ setContent && setContent.mockImplementation(useRemirrorContext().setContent);
19
+ return null;
20
+ };
21
+
22
+ const handleChange = (parameter: { state: any }) => {
23
+ setState(parameter.state);
24
+ };
25
+
26
+ return (
27
+ <Remirror
28
+ manager={manager}
29
+ onChange={handleChange}
30
+ placeholder="Write something"
31
+ label="Text editor"
32
+ state={state}
33
+ autoRender="start"
34
+ >
35
+ <EditorToolbar manager={manager} />
36
+ <Component />
37
+ <EditorToolbar manager={manager} isPopover />
38
+ </Remirror>
39
+ );
40
+ };
@@ -1,7 +1,11 @@
1
+ import { jest } from '@jest/globals';
1
2
  import React from 'react';
2
- import '@testing-library/jest-dom';
3
+ import { act, fireEvent, render, screen } from '@testing-library/react';
4
+ import { MockEditor } from './Editor.mock';
3
5
  import Editor from './Editor';
4
- import { fireEvent, render, screen } from '@testing-library/react';
6
+ import '@testing-library/jest-dom';
7
+
8
+ const setContent: any = jest.fn();
5
9
 
6
10
  describe('Formatted text editor', () => {
7
11
  it('Renders the text editor', () => {
@@ -225,4 +229,26 @@ describe('Formatted text editor', () => {
225
229
 
226
230
  expect(baseElement.querySelectorAll('div.remirror-editor p')).toHaveLength(2);
227
231
  });
232
+
233
+ it('Should allow text input & undo input upon clicking undo button', () => {
234
+ const { baseElement, getByLabelText } = render(<MockEditor setContent={setContent} />);
235
+
236
+ const textContent = `This is a string with a random number: ${Math.random() * 9999}`;
237
+
238
+ // This sets the content of the text editor
239
+ act(() => {
240
+ setContent(`<p>${textContent}</p>`, { triggerChange: true });
241
+ });
242
+
243
+ const editorNode = getByLabelText(`Text editor`);
244
+ expect(editorNode).toBeTruthy();
245
+ expect(editorNode.textContent).toBe(textContent);
246
+
247
+ // Testing if clicking undo button removes text from editor
248
+ const undoButton = baseElement.querySelector('button[title="Undo (cmd+Z)"]') as HTMLButtonElement;
249
+ expect(undoButton).toBeTruthy();
250
+ fireEvent.click(undoButton);
251
+
252
+ expect(editorNode.textContent).toBe('');
253
+ });
228
254
  });
@@ -1,18 +1,8 @@
1
1
  import React from 'react';
2
- import {
3
- BoldExtension,
4
- HeadingExtension,
5
- ItalicExtension,
6
- NodeFormattingExtension,
7
- ParagraphExtension,
8
- UnderlineExtension,
9
- HistoryExtension,
10
- wysiwygPreset,
11
- } from 'remirror/extensions';
12
2
  import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
13
- import { RemirrorContentType, Extension } from '@remirror/core';
3
+ import { RemirrorContentType } from '@remirror/core';
14
4
  import { EditorToolbar } from '../EditorToolbar/EditorToolbar';
15
- import { PreformattedExtension } from '../extensions/PreformattedExtension/PreformattedExtension';
5
+ import { Extensions } from '../Extensions/Extensions';
16
6
 
17
7
  type EditorProps = {
18
8
  content?: RemirrorContentType;
@@ -20,17 +10,7 @@ type EditorProps = {
20
10
 
21
11
  const Editor = ({ content }: EditorProps) => {
22
12
  const { manager, state, setState } = useRemirror({
23
- extensions: () => [
24
- ...(wysiwygPreset() as Extension[]),
25
- new BoldExtension(),
26
- new HeadingExtension(),
27
- new ItalicExtension(),
28
- new NodeFormattingExtension(),
29
- new ParagraphExtension(),
30
- new PreformattedExtension(),
31
- new UnderlineExtension(),
32
- new HistoryExtension(),
33
- ],
13
+ extensions: Extensions,
34
14
  content,
35
15
  selection: 'start',
36
16
  stringHandler: 'html',
@@ -10,11 +10,12 @@
10
10
  }
11
11
 
12
12
  .remirror-editor {
13
- @apply bg-white shadow-none rounded-b;
13
+ @apply bg-white shadow-none rounded-b p-3;
14
+ min-height: 6rem;
14
15
 
15
16
  &:active,
16
- :focus {
17
- /* some focus state */
17
+ &:focus {
18
+ @apply outline-0;
18
19
  }
19
20
 
20
21
  p {
@@ -22,16 +23,22 @@
22
23
  @apply block;
23
24
  }
24
25
  }
25
- }
26
26
 
27
- /* remove existing style */
28
- .remirror-theme .ProseMirror {
29
- &:active,
30
- &:focus {
31
- @apply shadow-none;
27
+ .remirror-is-empty:first-of-type::before {
28
+ position: absolute;
29
+ pointer-events: none;
30
+ height: 0;
31
+ font-style: italic;
32
+ content: attr(data-placeholder);
33
+ @apply text-gray-500;
32
34
  }
33
35
  }
34
36
 
37
+ a {
38
+ @apply text-blue-300;
39
+ text-decoration: underline;
40
+ }
41
+
35
42
  .remirror-theme h1 {
36
43
  font-size: 1.625rem;
37
44
  font-weight: 600;
@@ -8,6 +8,7 @@ import TextAlignButtons from './Tools/TextAlign/TextAlignButtons';
8
8
  import UndoButton from './Tools/Undo/UndoButton';
9
9
  import RedoButton from './Tools/Redo/RedoButton';
10
10
  import TextTypeDropdown from './Tools/TextType/TextTypeDropdown';
11
+ import TriggerLinkModal from './Tools/Link/Trigger/TriggerLinkModal';
11
12
 
12
13
  type EditorToolbarProps = {
13
14
  manager: RemirrorManager<any>;
@@ -38,6 +39,7 @@ export const EditorToolbar = ({ manager, isPopover }: EditorToolbarProps) => {
38
39
  {extensionNames.italic && <ItalicButton />}
39
40
  {extensionNames.underline && <UnderlineButton />}
40
41
  {extensionNames.nodeFormatting && <TextAlignButtons />}
42
+ <TriggerLinkModal />
41
43
  </Toolbar>
42
44
  ) : (
43
45
  <FloatingToolbar className="remirror-floating-popover">
@@ -11,7 +11,7 @@ describe('Bold button', () => {
11
11
 
12
12
  it('Activates the button if clicked', () => {
13
13
  render(<Editor />);
14
- expect(screen.getByRole('button', { name: 'Bold (cmd+B)' }).classList.contains('btn')).toBeTruthy();
14
+ expect(screen.getByRole('button', { name: 'Bold (cmd+B)' }).classList.contains('sds-btn')).toBeTruthy();
15
15
  const bold = screen.getByRole('button', { name: 'Bold (cmd+B)' });
16
16
  fireEvent.click(bold);
17
17
  expect(bold.classList.contains('is-active')).toBeTruthy();
@@ -11,7 +11,7 @@ describe('Italic button', () => {
11
11
 
12
12
  it('Activates the button if clicked', () => {
13
13
  render(<Editor />);
14
- expect(screen.getByRole('button', { name: 'Italic (cmd+I)' }).classList.contains('btn')).toBeTruthy();
14
+ expect(screen.getByRole('button', { name: 'Italic (cmd+I)' }).classList.contains('sds-btn')).toBeTruthy();
15
15
  const italic = screen.getByRole('button', { name: 'Italic (cmd+I)' });
16
16
  fireEvent.click(italic);
17
17
  expect(italic.classList.contains('is-active')).toBeTruthy();
@@ -0,0 +1,21 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
3
+ import React from 'react';
4
+ import LinkForm from './LinkForm';
5
+
6
+ describe('Link Form', () => {
7
+ it('Renders the form with the relevant fields', () => {
8
+ render(<LinkForm />);
9
+ expect(screen.getByLabelText('URL')).toBeInTheDocument();
10
+ expect(screen.getByLabelText('Text')).toBeInTheDocument();
11
+ expect(screen.getByLabelText('Title')).toBeInTheDocument();
12
+ expect(screen.getByLabelText('Target')).toBeInTheDocument();
13
+ });
14
+
15
+ it('Renders the form with the specified class', () => {
16
+ render(<LinkForm />);
17
+
18
+ const formClass = document.querySelector('.sds-form');
19
+ expect(formClass).toBeInTheDocument();
20
+ });
21
+ });
@@ -0,0 +1,41 @@
1
+ import React, { ReactElement, useState } from 'react';
2
+ import { TextInput } from '../../../../ui/Inputs/Text/TextInput';
3
+ import { Select } from '../../../../ui/Inputs/Select/Select';
4
+
5
+ const selectOptions = [
6
+ { id: 1, name: 'Current window', key: '_blank' },
7
+ { id: 2, name: 'New window', key: '_parent' },
8
+ ];
9
+
10
+ const LinkForm = (): ReactElement => {
11
+ const [formValues, setFormValues] = useState({
12
+ href: '',
13
+ target: selectOptions[0],
14
+ title: '',
15
+ text: '',
16
+ });
17
+
18
+ const handleChange = (event: { target: { name: string; value: string } }) => {
19
+ const { name, value } = event.target;
20
+ setFormValues({ ...formValues, [name]: value });
21
+ };
22
+
23
+ return (
24
+ <form action="" method="post" className="sds-form">
25
+ <div className="sds-form-group mb-2">
26
+ <TextInput id="href" label="URL" value={formValues.href} onChange={handleChange} />
27
+ </div>
28
+ <div className="sds-form-group mb-2">
29
+ <TextInput id="text" label="Text" value={formValues.text} onChange={handleChange} />
30
+ </div>
31
+ <div className="sds-form-group mb-2">
32
+ <TextInput id="title" label="Title" value={formValues.title} onChange={handleChange} />
33
+ </div>
34
+ <div className="sds-form-group mb-0">
35
+ <Select id="target" label="Target" value={formValues.target} options={selectOptions} />
36
+ </div>
37
+ </form>
38
+ );
39
+ };
40
+
41
+ export default LinkForm;
@@ -0,0 +1,26 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import React from 'react';
4
+ import LinkButton from './LinkButton';
5
+
6
+ describe('Link button', () => {
7
+ const mockOnClick = jest.fn();
8
+
9
+ it('Renders the link button', () => {
10
+ render(<LinkButton showModal={false} onClick={mockOnClick} enabled />);
11
+ expect(screen.getByRole('button', { name: 'Link' })).toBeInTheDocument();
12
+ });
13
+
14
+ it('Renders a disabled button if this is what you have set', () => {
15
+ render(<LinkButton showModal={false} onClick={mockOnClick} enabled={false} />);
16
+ expect(screen.getByRole('button', { name: 'Link' })).toBeDisabled();
17
+ });
18
+
19
+ it('Calls the modalFire function if you click it', () => {
20
+ render(<LinkButton showModal={false} onClick={mockOnClick} enabled />);
21
+ const linkButton = screen.getByRole('button', { name: 'Link' });
22
+ expect(linkButton).not.toBeDisabled();
23
+ fireEvent.click(linkButton);
24
+ expect(mockOnClick).toHaveBeenCalled();
25
+ });
26
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import ToolbarButton from '../../../ui/ToolbarButton/ToolbarButton';
3
+ import InsertLinkRoundedIcon from '@mui/icons-material/InsertLinkRounded';
4
+
5
+ type LinkButtonProps = {
6
+ onClick: () => void;
7
+ enabled: boolean;
8
+ showModal: boolean;
9
+ };
10
+
11
+ const LinkButton = ({ onClick, showModal, enabled }: LinkButtonProps) => {
12
+ const active = showModal;
13
+
14
+ return (
15
+ <ToolbarButton
16
+ handleOnClick={onClick}
17
+ isDisabled={!enabled}
18
+ isActive={active}
19
+ icon={<InsertLinkRoundedIcon />}
20
+ label={`Link`}
21
+ />
22
+ );
23
+ };
24
+
25
+ export default LinkButton;
@@ -0,0 +1,41 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import React from 'react';
4
+ import TriggerLinkModal from './TriggerLinkModal';
5
+
6
+ describe('TriggerLinkModal', () => {
7
+ it('Renders the trigger button', () => {
8
+ render(<TriggerLinkModal />);
9
+ expect(screen.getByRole('button', { name: 'Link' })).toBeInTheDocument();
10
+ });
11
+
12
+ it('Displays the modal when clicking on the trigger button', () => {
13
+ render(<TriggerLinkModal />);
14
+
15
+ // Open the modal
16
+ const triggerButton = screen.getByRole('button', { name: 'Link' });
17
+ fireEvent.click(triggerButton);
18
+
19
+ const modalHeading = screen.getByRole('heading', {
20
+ name: /Link/i,
21
+ });
22
+ expect(modalHeading).toBeInTheDocument();
23
+ });
24
+
25
+ it('Closes the modal when clicking on the cancel button', () => {
26
+ render(<TriggerLinkModal />);
27
+ // Open the modal
28
+ const triggerButton = screen.getByRole('button', { name: 'Link' });
29
+ fireEvent.click(triggerButton);
30
+ const modalHeading = screen.getByRole('heading', {
31
+ name: /Link/i,
32
+ });
33
+ expect(modalHeading).toBeInTheDocument();
34
+ // Check that the cancel button renders
35
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
36
+ expect(cancelButton).toBeInTheDocument();
37
+ // check that firing the cancel button fires the cancel function
38
+ fireEvent.click(cancelButton);
39
+ expect(modalHeading).not.toBeInTheDocument();
40
+ });
41
+ });
@@ -0,0 +1,21 @@
1
+ import React, { ReactElement, useState } from 'react';
2
+ import LinkButton from '../LinkButton';
3
+ import Modal from '../../../../ui/Modal/Modal';
4
+ import LinkForm from '../Form/LinkForm';
5
+
6
+ const TriggerLinkModal = (): ReactElement => {
7
+ const [showModal, setShowModal] = useState(false);
8
+
9
+ return (
10
+ <>
11
+ <LinkButton showModal={showModal} onClick={() => setShowModal(true)} enabled />
12
+ {showModal ? (
13
+ <Modal onCancel={() => setShowModal(false)} onSubmit={() => console.log('Sumbmit form')}>
14
+ <LinkForm />
15
+ </Modal>
16
+ ) : null}
17
+ </>
18
+ );
19
+ };
20
+
21
+ export default TriggerLinkModal;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { useCommands, useActive } from '@remirror/react';
3
- import { PreformattedExtension } from '../../../../extensions/PreformattedExtension/PreformattedExtension';
3
+ import { PreformattedExtension } from '../../../../Extensions/PreformattedExtension/PreformattedExtension';
4
4
  import DropdownButton from '../../../../ui/DropdownButton/DropdownButton';
5
5
 
6
6
  const PreformattedButton = () => {
@@ -4,7 +4,7 @@ import ParagraphButton from './Paragraph/ParagraphButton';
4
4
  import PreformattedButton from './Preformatted/PreformattedButton';
5
5
  import ToolbarDropdown from '../../../ui/ToolbarDropdown/ToolbarDropdown';
6
6
  import { useActive, VerticalDivider } from '@remirror/react';
7
- import { PreformattedExtension } from '../../../extensions/PreformattedExtension/PreformattedExtension';
7
+ import { PreformattedExtension } from '../../../Extensions/PreformattedExtension/PreformattedExtension';
8
8
 
9
9
  const TextTypeDropdown = () => {
10
10
  const active = useActive<PreformattedExtension>();
@@ -11,7 +11,7 @@ describe('Underline button', () => {
11
11
 
12
12
  it('Activates the button if clicked', () => {
13
13
  render(<Editor />);
14
- expect(screen.getByRole('button', { name: 'Underline (cmd+U)' }).classList.contains('btn')).toBeTruthy();
14
+ expect(screen.getByRole('button', { name: 'Underline (cmd+U)' }).classList.contains('sds-btn')).toBeTruthy();
15
15
  const underline = screen.getByRole('button', { name: 'Underline (cmd+U)' });
16
16
  fireEvent.click(underline);
17
17
  expect(underline.classList.contains('is-active')).toBeTruthy();
@@ -1,5 +1,5 @@
1
1
  .formatted-text-editor .editor-toolbar {
2
- @apply bg-white rounded border-gray-200 border-2 border-solid p-1;
2
+ @apply bg-white border-gray-200 border-b-2 border-solid p-1;
3
3
 
4
4
  display: flex;
5
5
  justify-items: center;
@@ -0,0 +1,24 @@
1
+ import {
2
+ BoldExtension,
3
+ HeadingExtension,
4
+ ItalicExtension,
5
+ NodeFormattingExtension,
6
+ ParagraphExtension,
7
+ UnderlineExtension,
8
+ HistoryExtension,
9
+ wysiwygPreset,
10
+ } from 'remirror/extensions';
11
+ import { PreformattedExtension } from './PreformattedExtension/PreformattedExtension';
12
+ import { Extension } from '@remirror/core';
13
+
14
+ export const Extensions = () => [
15
+ ...(wysiwygPreset() as Extension[]),
16
+ new BoldExtension(),
17
+ new HeadingExtension(),
18
+ new ItalicExtension(),
19
+ new NodeFormattingExtension(),
20
+ new ParagraphExtension(),
21
+ new PreformattedExtension(),
22
+ new UnderlineExtension(),
23
+ new HistoryExtension(),
24
+ ];
@@ -0,0 +1,10 @@
1
+ import { render } from '@testing-library/react';
2
+ import { FormattedTextEditor } from './';
3
+ import React from 'react';
4
+
5
+ describe('<FormattedTextEditor />', () => {
6
+ it('should render "<FormattedTextEditor />" component', () => {
7
+ const { baseElement } = render(<FormattedTextEditor />);
8
+ expect(baseElement).toBeTruthy();
9
+ });
10
+ });
package/src/index.scss CHANGED
@@ -3,8 +3,9 @@
3
3
  @import 'tailwindcss/components';
4
4
  @import 'tailwindcss/utilities';
5
5
 
6
- /* Remirror base */
7
- @import 'remirror/styles/all.css';
6
+ /* Global */
7
+ @import './ui/forms';
8
+ @import './ui/buttons';
8
9
 
9
10
  /* Components */
10
11
  @import './Editor/editor';
@@ -13,3 +14,5 @@
13
14
  @import './ui/ToolbarButton/toolbar-button';
14
15
  @import './ui/ToolbarDropdown/toolbar-dropdown';
15
16
  @import './ui/DropdownButton/dropdown-button';
17
+
18
+ @import './ui/Modal/modal';
@@ -1,7 +1,8 @@
1
1
  .dropdown-button {
2
- padding: 4px 8px;
3
- color: #707070;
4
2
  height: 40px;
5
3
  width: 100%;
6
4
  justify-content: space-between;
5
+ align-items: center;
6
+ display: flex;
7
+ @apply px-2 py-1 text-gray-600;
7
8
  }