@bigbinary/neeto-editor 0.1.17 → 0.2.0

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 (52) hide show
  1. package/index.js +16 -14
  2. package/package.json +12 -1
  3. package/src/App.js +5 -28
  4. package/src/Common/Avatar.js +168 -0
  5. package/src/Common/CodeBlock.js +11 -0
  6. package/src/Common/Description.js +12 -0
  7. package/src/Common/Dropdown/index.js +118 -0
  8. package/src/Common/Heading.js +13 -0
  9. package/src/Common/HighlightText.js +7 -0
  10. package/src/Common/Icons/HashtagFilled.js +59 -0
  11. package/src/Common/Icons/TextColor.js +35 -0
  12. package/src/Common/Icons/index.js +2 -0
  13. package/src/Common/ListItems.js +17 -0
  14. package/src/Editor/CustomExtensions/BubbleMenu/index.js +33 -26
  15. package/src/Editor/CustomExtensions/Embeds.js +5 -3
  16. package/src/Editor/CustomExtensions/FixedMenu/FontSizeOption.js +32 -0
  17. package/src/Editor/CustomExtensions/FixedMenu/TextColorOption.js +29 -0
  18. package/src/Editor/CustomExtensions/FixedMenu/constants.js +3 -0
  19. package/src/Editor/CustomExtensions/FixedMenu/index.js +186 -0
  20. package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +67 -0
  21. package/src/Editor/CustomExtensions/Mention/MentionList.js +96 -0
  22. package/src/Editor/CustomExtensions/Mention/helpers.js +23 -0
  23. package/src/Editor/CustomExtensions/Placeholder/ExtensionConfig.js +81 -0
  24. package/src/Editor/CustomExtensions/Placeholder/helpers.js +18 -0
  25. package/src/Editor/CustomExtensions/SlashCommands/Commands.js +5 -10
  26. package/src/Editor/CustomExtensions/SlashCommands/CommandsList.js +15 -16
  27. package/src/Editor/CustomExtensions/SlashCommands/ExtensionConfig.js +191 -150
  28. package/src/Editor/CustomExtensions/Variable/ExtensionConfig.js +208 -0
  29. package/src/Editor/CustomExtensions/Variable/VariableList.js +45 -0
  30. package/src/Editor/CustomExtensions/Variable/VariableSuggestion.js +20 -0
  31. package/src/Editor/CustomExtensions/Variable/helpers.js +31 -0
  32. package/src/Editor/CustomExtensions/Variable/index.js +35 -0
  33. package/src/Editor/CustomExtensions/useCustomExtensions.js +87 -0
  34. package/src/Editor/index.js +42 -37
  35. package/src/examples/constants.js +95 -0
  36. package/src/examples/index.js +186 -0
  37. package/src/hooks/useOutsideClick.js +19 -0
  38. package/src/index.scss +27 -12
  39. package/src/styles/abstracts/_mixins.scss +20 -0
  40. package/src/styles/abstracts/_neeto-ui-variables.scss +36 -0
  41. package/src/styles/abstracts/_variables.scss +10 -0
  42. package/src/styles/components/_avatar.scss +105 -0
  43. package/src/styles/components/_codeblock.scss +16 -0
  44. package/src/{Editor/styles/CommandsList.scss → styles/components/_command-list.scss} +12 -1
  45. package/src/styles/components/_dropdown.scss +69 -0
  46. package/src/styles/components/_editor-variables.scss +12 -0
  47. package/src/{Editor/styles/EditorStyles.scss → styles/components/_editor.scss} +29 -7
  48. package/src/styles/components/_fixed-menu.scss +13 -0
  49. package/src/utils/common.js +13 -0
  50. package/public/logo192.png +0 -0
  51. package/public/logo512.png +0 -0
  52. package/src/logo.svg +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/neeto-editor",
3
- "version": "0.1.17",
3
+ "version": "0.2.0",
4
4
  "main": "./index.js",
5
5
  "description": "Neeto Editor is the library that drives the rich text experience in all Neeto products built at BigBinary",
6
6
  "keywords": [
@@ -12,16 +12,22 @@
12
12
  "author": "BigBinary",
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
+ "@bigbinary/neeto-icons": "^1.8.6",
15
16
  "@risingstack/react-easy-state": "^6.3.0",
16
17
  "@tailwindcss/typography": "^0.4.0",
17
18
  "@tiptap/extension-bubble-menu": "^2.0.0-beta.18",
18
19
  "@tiptap/extension-code-block-lowlight": "^2.0.0-beta.24",
20
+ "@tiptap/extension-color": "^2.0.0-beta.3",
21
+ "@tiptap/extension-document": "^2.0.0-beta.15",
19
22
  "@tiptap/extension-dropcursor": "^2.0.0-beta.13",
20
23
  "@tiptap/extension-highlight": "^2.0.0-beta.13",
21
24
  "@tiptap/extension-image": "^2.0.0-beta.13",
22
25
  "@tiptap/extension-link": "^2.0.0-beta.18",
26
+ "@tiptap/extension-mention": "^2.0.0-beta.80",
23
27
  "@tiptap/extension-placeholder": "^2.0.0-beta.18",
28
+ "@tiptap/extension-text-style": "^2.0.0-beta.17",
24
29
  "@tiptap/extension-typography": "^2.0.0-beta.11",
30
+ "@tiptap/extension-underline": "^2.0.0-beta.21",
25
31
  "@tiptap/react": "^2.0.0-beta.37",
26
32
  "@tiptap/starter-kit": "^2.0.0-beta.57",
27
33
  "@tiptap/suggestion": "^2.0.0-beta.55",
@@ -29,9 +35,14 @@
29
35
  "@uppy/react": "^1.11.8",
30
36
  "@uppy/xhr-upload": "^1.7.2",
31
37
  "classnames": "^2.3.1",
38
+ "lodash.isempty": "^4.4.0",
39
+ "lodash.isplainobject": "^4.0.6",
32
40
  "lowlight": "^1.20.0",
33
41
  "peer-deps-externals-webpack-plugin": "1.0.4",
42
+ "react-hotkeys-hook": "^3.4.4",
34
43
  "react-icons": "^4.2.0",
44
+ "react-outside-click-handler": "^1.3.0",
45
+ "react-popper": "^2.2.5",
35
46
  "tippy.js": "^6.3.1"
36
47
  },
37
48
  "devDependencies": {
package/src/App.js CHANGED
@@ -1,31 +1,8 @@
1
- import React, { Component } from "react";
2
- import Editor from "./Editor/index";
3
- import "./index.scss";
4
-
5
- export default class App extends Component {
6
- ref = React.createRef();
1
+ import React from "react";
7
2
 
8
- getHTML = () => {
9
- return this.ref.current.editor.getHTML();
10
- };
3
+ import Example from "./examples";
4
+ import "./index.scss";
11
5
 
12
- render() {
13
- return (
14
- <div style={{ width: 720, margin: "48px auto" }}>
15
- <div className="flex justify-end">
16
- <button
17
- className="px-3 py-1 text-sm font-medium border border-gray-200 rounded shadow-sm"
18
- onClick={() => {
19
- // eslint-disable-next-line no-console
20
- console.log(this.getHTML());
21
- }}
22
- >
23
- Print output to console
24
- </button>
25
- </div>
26
- <hr className="my-2 border-gray-100" />
27
- <Editor ref={this.ref} />
28
- </div>
29
- );
30
- }
6
+ export default function App() {
7
+ return <Example />;
31
8
  }
@@ -0,0 +1,168 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import classNames from "classnames";
4
+
5
+ const SIZE = {
6
+ small: 24,
7
+ medium: 32,
8
+ large: 40,
9
+ xlarge: 64,
10
+ };
11
+
12
+ const STATUS = {
13
+ online: "online",
14
+ idle: "idle",
15
+ offline: "offline",
16
+ };
17
+
18
+ const COLORS = [
19
+ "#E5E7EB",
20
+ "#FECACA",
21
+ "#FDE68A",
22
+ "#A7F3D0",
23
+ "#BFDBFE",
24
+ "#C7D2FE",
25
+ "#DDD6FE",
26
+ "#FBCFE8",
27
+ ];
28
+
29
+ function Avatar({
30
+ size,
31
+ user,
32
+ isSquare,
33
+ status,
34
+ onClick,
35
+ className,
36
+ ...otherProps
37
+ }) {
38
+ const [loaded, setLoaded] = useState(false);
39
+
40
+ const { name = "", imageUrl } = user;
41
+
42
+ const isMedium = size === "medium";
43
+ const isLarge = size === "large";
44
+ const isXLarge = size === "xlarge";
45
+
46
+ const getInitials = (fullName) => {
47
+ const allNames = fullName.trim().split(" ");
48
+ const initials = allNames.reduce((acc, curr, index) => {
49
+ if (index === 0 || index === allNames.length - 1) {
50
+ acc = `${acc}${curr.charAt(0).toUpperCase()}`;
51
+ }
52
+ return acc;
53
+ }, "");
54
+ return initials;
55
+ };
56
+
57
+ const avatarString = getInitials(name);
58
+
59
+ const getRandomBackgroundColor = () => {
60
+ const charCode =
61
+ (avatarString.charCodeAt(0) || 0) + (avatarString.charCodeAt(1) || 0);
62
+ const bgColor = COLORS[(charCode % 65) % COLORS.length] || COLORS[0];
63
+ return bgColor;
64
+ };
65
+
66
+ const imageContainerStyle = {
67
+ height: SIZE[size],
68
+ width: SIZE[size],
69
+ backgroundColor: getRandomBackgroundColor(),
70
+ };
71
+
72
+ const imageClasses = classNames("neeto-ui-avatar", {
73
+ "neeto-ui-avatar--medium": isMedium,
74
+ "neeto-ui-avatar--large": isLarge,
75
+ "neeto-ui-avatar--xlarge": isXLarge,
76
+ "neeto-ui-avatar--round": !isSquare,
77
+ hidden: !loaded,
78
+ });
79
+
80
+ const placeholderClasses = classNames("neeto-ui-avatar__text", {
81
+ "neeto-ui-avatar__text-medium": isMedium,
82
+ "neeto-ui-avatar__text-large": isLarge,
83
+ "neeto-ui-avatar__text-xlarge": isXLarge,
84
+ });
85
+
86
+ // TODO: Remove 'v2' prefix.
87
+ const statusClasses = classNames("neeto-ui-avatar__status", `${status}`, {
88
+ "neeto-ui-avatar__status-medium": isMedium,
89
+ "neeto-ui-avatar__status-large": isLarge,
90
+ "neeto-ui-avatar__status-xlarge": isXLarge,
91
+ "neeto-ui-avatar__status-square": isSquare,
92
+ });
93
+
94
+ const Indicator = () =>
95
+ status === undefined || status === null ? (
96
+ React.Fragment
97
+ ) : (
98
+ <span className={statusClasses} />
99
+ );
100
+
101
+ const ImagePlaceholder = () => (
102
+ <span className={placeholderClasses}>{avatarString}</span>
103
+ );
104
+
105
+ const shouldDisplayInitials = avatarString && !imageUrl && !loaded;
106
+
107
+ return (
108
+ <div
109
+ onClick={onClick}
110
+ style={imageContainerStyle}
111
+ className={classNames(
112
+ "neeto-ui-avatar--container",
113
+ {
114
+ "neeto-ui-avatar--container-round": !isSquare,
115
+ },
116
+ className
117
+ )}
118
+ {...otherProps}
119
+ >
120
+ <Indicator />
121
+ {shouldDisplayInitials ? (
122
+ <ImagePlaceholder />
123
+ ) : (
124
+ <img
125
+ className={imageClasses}
126
+ onLoad={() => setLoaded(true)}
127
+ onError={() => setLoaded(false)}
128
+ src={imageUrl}
129
+ alt={`avatar-${avatarString}`}
130
+ />
131
+ )}
132
+ </div>
133
+ );
134
+ }
135
+
136
+ Avatar.defaultProps = {
137
+ size: "medium",
138
+ user: {
139
+ imageUrl: "",
140
+ name: "",
141
+ },
142
+ isSquare: false,
143
+ onClick: () => {},
144
+ status: null,
145
+ };
146
+
147
+ Avatar.propTypes = {
148
+ /**
149
+ * Specify the dimension for avatar component.
150
+ */
151
+ size: PropTypes.oneOf(Object.keys(SIZE)),
152
+ user: PropTypes.shape({
153
+ imageUrl: PropTypes.string,
154
+ name: PropTypes.string,
155
+ }),
156
+ isSquare: PropTypes.bool,
157
+ onClick: PropTypes.func,
158
+ /**
159
+ * Specify the status of the user if needed in avatar component.
160
+ */
161
+ status: PropTypes.oneOf(Object.keys(STATUS)),
162
+ /**
163
+ * Specify custom className to be applied on the Avatar Container
164
+ */
165
+ className: PropTypes.string,
166
+ };
167
+
168
+ export default Avatar;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+
3
+ const CodeBlock = ({ children }) => {
4
+ return (
5
+ <div className="flex-1 p-3 mx-3 my-2 codeblock__container">
6
+ <pre>{children}</pre>
7
+ </div>
8
+ );
9
+ };
10
+
11
+ export default CodeBlock;
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+
4
+ const Description = ({ children, className }) => {
5
+ return (
6
+ <p className={classNames("font-serif", { [className]: className })}>
7
+ {children}
8
+ </p>
9
+ );
10
+ };
11
+
12
+ export default Description;
@@ -0,0 +1,118 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import classnames from "classnames";
3
+ import { usePopper } from "react-popper";
4
+ import { Down } from "@bigbinary/neeto-icons";
5
+ import { useHotkeys } from "react-hotkeys-hook";
6
+ import OutsideClickHandler from "react-outside-click-handler";
7
+
8
+ import { hyphenize } from "../../utils/common";
9
+
10
+ const noop = () => {};
11
+
12
+ const Dropdown = ({
13
+ icon,
14
+ label,
15
+ isOpen,
16
+ onClose = noop,
17
+ ulProps,
18
+ position,
19
+ children,
20
+ autoWidth,
21
+ className,
22
+ buttonStyle = "primary",
23
+ buttonProps,
24
+ customTarget,
25
+ disabled = false,
26
+ closeOnEsc = true,
27
+ closeOnSelect = true,
28
+ closeOnOutsideClick = true,
29
+ dropdownModifiers = [],
30
+ ...otherProps
31
+ }) => {
32
+ const Target = customTarget;
33
+ const isControlled = !(isOpen === undefined || isOpen === null);
34
+
35
+ const [visible, setVisibility] = useState(false);
36
+ const [reference, setReference] = useState(null);
37
+ const [popper, setPopper] = useState(null);
38
+
39
+ const { styles, attributes } = usePopper(reference, popper, {
40
+ placement: position || "bottom-end",
41
+ modifiers: dropdownModifiers,
42
+ });
43
+
44
+ const onPopupClose = () => {
45
+ if (!isControlled) setVisibility(false);
46
+ onClose();
47
+ };
48
+
49
+ const handlePopperClick = () => {
50
+ closeOnSelect && onPopupClose();
51
+ };
52
+
53
+ const handleButtonClick = () => {
54
+ setVisibility(!visible);
55
+ };
56
+
57
+ closeOnEsc && useHotkeys("esc", onPopupClose);
58
+
59
+ if (!isControlled) {
60
+ buttonProps = {
61
+ ...buttonProps,
62
+ onClick: () => {
63
+ handleButtonClick();
64
+ },
65
+ };
66
+ }
67
+
68
+ useEffect(() => {
69
+ isControlled && setVisibility(isOpen);
70
+ }, [isOpen]);
71
+
72
+ return (
73
+ <OutsideClickHandler
74
+ useCapture={true}
75
+ onOutsideClick={() => {
76
+ closeOnOutsideClick && onPopupClose();
77
+ }}
78
+ >
79
+ <div
80
+ className={classnames("neeto-ui-dropdown__wrapper", {
81
+ "neeto-ui-dropdown__wrapper--auto-width": autoWidth,
82
+ [className]: className,
83
+ })}
84
+ {...otherProps}
85
+ >
86
+ {customTarget ? (
87
+ <div ref={setReference} onClick={handleButtonClick}>
88
+ <Target />
89
+ </div>
90
+ ) : (
91
+ <div>
92
+ {label}
93
+ <Down />
94
+ </div>
95
+ )}
96
+ {visible && (
97
+ <ul
98
+ className="neeto-ui-dropdown__popup"
99
+ ref={setPopper}
100
+ onClick={handlePopperClick}
101
+ data-cy={`${hyphenize(label)}-dropdown-container`}
102
+ {...ulProps}
103
+ style={{
104
+ display: "block",
105
+ ...styles.offset,
106
+ ...styles.popper,
107
+ }}
108
+ {...attributes.popper}
109
+ >
110
+ {children}
111
+ </ul>
112
+ )}
113
+ </div>
114
+ </OutsideClickHandler>
115
+ );
116
+ };
117
+
118
+ export default Dropdown;
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+
4
+ const Heading = ({ children, type = "main" }) => {
5
+ const className = classNames({
6
+ "mb-3 mt-14 text-3xl font-extrabold": type === "main",
7
+ "mb-1 mt-8 text-xl font-bold": type === "sub",
8
+ });
9
+
10
+ return <h1 className={className}>{children}</h1>;
11
+ };
12
+
13
+ export default Heading;
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+
3
+ const HighlightText = ({ children }) => (
4
+ <span className="px-1 font-mono bg-gray-100">{children}</span>
5
+ );
6
+
7
+ export default HighlightText;
@@ -0,0 +1,59 @@
1
+ /** Icons generated by create-react-icons. Don't edit this file directly. **/
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+
5
+ export function HashtagFilled(props) {
6
+ const { size, color, bgColor, ...other } = props;
7
+ return (
8
+ <svg viewBox="0 0 24 24" fill="none" height={size} width={size} {...other}>
9
+ <path
10
+ d="M21 7V17C21 19.209 19.209 21 17 21H7C4.791 21 3 19.209 3 17V7C3 4.791 4.791 3 7 3H17C19.209 3 21 4.791 21 7Z"
11
+ fill={bgColor}
12
+ stroke={bgColor}
13
+ strokeWidth="1.5"
14
+ strokeLinecap="round"
15
+ strokeLinejoin="round"
16
+ />
17
+ <path
18
+ d="M10 8V16"
19
+ stroke={color}
20
+ strokeWidth="1.389"
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ />
24
+ <path
25
+ d="M14 8V16"
26
+ stroke={color}
27
+ strokeWidth="1.389"
28
+ strokeLinecap="round"
29
+ strokeLinejoin="round"
30
+ />
31
+ <path
32
+ d="M8 14H16"
33
+ stroke={color}
34
+ strokeWidth="1.389"
35
+ strokeLinecap="round"
36
+ strokeLinejoin="round"
37
+ />
38
+ <path
39
+ d="M8 10H16"
40
+ stroke={color}
41
+ strokeWidth="1.389"
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ />
45
+ </svg>
46
+ );
47
+ }
48
+
49
+ HashtagFilled.defaultProps = {
50
+ color: "white",
51
+ bgColor: "#68737D",
52
+ size: 24,
53
+ };
54
+
55
+ HashtagFilled.propTypes = {
56
+ color: PropTypes.string,
57
+ bgColor: PropTypes.string,
58
+ size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
59
+ };
@@ -0,0 +1,35 @@
1
+ /** Icons generated by create-react-icons. Don't edit this file directly. **/
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+
5
+ export function TextColor(props) {
6
+ const { size, color, underlineColor, ...other } = props;
7
+ return (
8
+ <svg viewBox="0 0 24 24" fill="none" height={size} width={size} {...other}>
9
+ <path
10
+ d="M21 20H3"
11
+ stroke={underlineColor || color}
12
+ strokeWidth="1.5"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ />
16
+ <path
17
+ d="M17.3125 15L12.1562 4L7 15M8.03125 12.8H16.2812"
18
+ stroke={color}
19
+ strokeWidth="1.5"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ TextColor.defaultProps = {
28
+ color: "currentColor",
29
+ size: 24,
30
+ };
31
+
32
+ TextColor.propTypes = {
33
+ color: PropTypes.string,
34
+ size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
35
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./TextColor";
2
+ export * from "./HashtagFilled";
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ const ListItems = ({ items, ordered }) => {
4
+ const itemElements = items.map((item, index) => (
5
+ <li key={item.key || index}>{item}</li>
6
+ ));
7
+
8
+ const className = "font-serif text-lg list-inside";
9
+
10
+ return ordered ? (
11
+ <ol className={`${className} list-decimal`}>{itemElements}</ol>
12
+ ) : (
13
+ <ul className={`${className} list-disc`}>{itemElements}</ul>
14
+ );
15
+ };
16
+
17
+ export default ListItems;
@@ -2,13 +2,15 @@ import React from "react";
2
2
  import classnames from "classnames";
3
3
  import { BubbleMenu } from "@tiptap/react";
4
4
  import {
5
- FaBold,
6
- FaItalic,
7
- FaStrikethrough,
8
- FaHighlighter,
9
- FaCode,
10
- FaLink,
11
- } from "react-icons/fa";
5
+ TextBold,
6
+ TextItalic,
7
+ TextCross,
8
+ Link,
9
+ Code,
10
+ Highlight,
11
+ } from "@bigbinary/neeto-icons";
12
+ import { roundArrow } from "tippy.js";
13
+ import "tippy.js/dist/svg-arrow.css";
12
14
 
13
15
  export default function index({ editor, formatterOptions }) {
14
16
  if (!editor) {
@@ -16,25 +18,25 @@ export default function index({ editor, formatterOptions }) {
16
18
  }
17
19
  const options = [
18
20
  {
19
- Icon: FaBold,
21
+ Icon: TextBold,
20
22
  command: () => editor.chain().focus().toggleBold().run(),
21
23
  active: editor.isActive("bold"),
22
24
  optionName: "bold",
23
25
  },
24
26
  {
25
- Icon: FaItalic,
27
+ Icon: TextItalic,
26
28
  command: () => editor.chain().focus().toggleItalic().run(),
27
29
  active: editor.isActive("italic"),
28
30
  optionName: "italic",
29
31
  },
30
32
  {
31
- Icon: FaStrikethrough,
33
+ Icon: TextCross,
32
34
  command: () => editor.chain().focus().toggleStrike().run(),
33
35
  active: editor.isActive("strike"),
34
36
  optionName: "strike",
35
37
  },
36
38
  {
37
- Icon: FaLink,
39
+ Icon: Link,
38
40
  command: () => {
39
41
  if (editor.isActive("link")) {
40
42
  editor.chain().focus().unsetLink().run();
@@ -47,39 +49,44 @@ export default function index({ editor, formatterOptions }) {
47
49
  optionName: "link",
48
50
  },
49
51
  {
50
- Icon: FaCode,
52
+ Icon: Code,
51
53
  command: () => editor.chain().focus().toggleCode().run(),
52
54
  active: editor.isActive("code"),
53
55
  optionName: "code",
54
56
  },
55
57
  {
56
- Icon: FaHighlighter,
58
+ Icon: Highlight,
57
59
  command: () => editor.chain().focus().toggleHighlight().run(),
58
60
  active: editor.isActive("highlight"),
59
61
  optionName: "highlight",
60
62
  },
61
63
  ];
62
64
  return (
63
- <BubbleMenu editor={editor}>
64
- <div className="relative flex items-center overflow-hidden bg-gray-900 rounded shadow">
65
- {options
66
- .filter(({ optionName }) => formatterOptions.includes(optionName))
67
- .map((option) => (
68
- <Option {...option} key={option.optionName} />
69
- ))}
70
- </div>
65
+ <BubbleMenu
66
+ editor={editor}
67
+ tippyOptions={{ arrow: roundArrow }}
68
+ className="relative flex rounded shadow editor-command-list--root"
69
+ >
70
+ {options
71
+ .filter(({ optionName }) => formatterOptions.includes(optionName))
72
+ .map((option) => (
73
+ <Option {...option} key={option.optionName} />
74
+ ))}
71
75
  </BubbleMenu>
72
76
  );
73
77
  }
74
78
 
75
79
  const Option = ({ Icon, command, active, iconSize }) => (
76
80
  <div
77
- className={classnames("p-3 cursor-pointer hover:bg-gray-800", {
78
- "text-gray-400": !active,
79
- "text-white": active,
80
- })}
81
+ className={classnames(
82
+ "p-3 cursor-pointer editor-command-list--item transition-colors rounded",
83
+ {
84
+ "text-gray-400": !active,
85
+ "text-white": active,
86
+ }
87
+ )}
81
88
  onClick={command}
82
89
  >
83
- <Icon size={iconSize || 15} />
90
+ <Icon size={iconSize || 24} />
84
91
  </div>
85
92
  );
@@ -3,9 +3,11 @@ import { Node, mergeAttributes } from "@tiptap/core";
3
3
  export default Node.create({
4
4
  name: "external-video",
5
5
 
6
- defaultOptions: {
7
- inline: false,
8
- HTMLAttributes: {},
6
+ addOptions() {
7
+ return {
8
+ inline: false,
9
+ HTMLAttributes: {},
10
+ };
9
11
  },
10
12
 
11
13
  inline() {