@bigbinary/neeto-editor 0.2.0 → 0.2.1

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 (41) hide show
  1. package/index.js +27 -6
  2. package/package.json +7 -4
  3. package/src/Common/Button.js +95 -0
  4. package/src/Common/Description.js +1 -5
  5. package/src/Common/Dropdown/index.js +6 -2
  6. package/src/Common/Input.js +70 -0
  7. package/src/Common/Label.js +45 -0
  8. package/src/Common/Modal.js +91 -0
  9. package/src/Common/Tab.js +79 -0
  10. package/src/Common/ToolTip.js +37 -0
  11. package/src/Editor/CustomExtensions/BubbleMenu/index.js +2 -2
  12. package/src/Editor/CustomExtensions/FixedMenu/FontSizeOption.js +3 -3
  13. package/src/Editor/CustomExtensions/FixedMenu/TextColorOption.js +1 -1
  14. package/src/Editor/CustomExtensions/FixedMenu/index.js +7 -10
  15. package/src/Editor/CustomExtensions/Image/LinkUploader/URLForm.js +39 -0
  16. package/src/Editor/CustomExtensions/Image/LocalUploader.js +21 -0
  17. package/src/Editor/CustomExtensions/Image/ProgressBar.js +34 -0
  18. package/src/Editor/CustomExtensions/Image/Uploader.js +72 -31
  19. package/src/Editor/CustomExtensions/Image/constants.js +5 -0
  20. package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +0 -1
  21. package/src/Editor/CustomExtensions/Mention/MentionList.js +1 -1
  22. package/src/Editor/CustomExtensions/SlashCommands/ExtensionConfig.js +180 -176
  23. package/src/Editor/CustomExtensions/Variable/index.js +3 -3
  24. package/src/Editor/CustomExtensions/useCustomExtensions.js +2 -1
  25. package/src/Editor/index.js +17 -5
  26. package/src/constants/regexp.js +1 -0
  27. package/src/examples/constants.js +1 -1
  28. package/src/examples/index.js +25 -25
  29. package/src/hooks/useTabBar.js +9 -0
  30. package/src/index.scss +5 -0
  31. package/src/styles/abstracts/_neeto-ui-variables.scss +80 -9
  32. package/src/styles/abstracts/_variables.scss +4 -1
  33. package/src/styles/components/_button.scss +161 -0
  34. package/src/styles/components/_editor.scss +4 -0
  35. package/src/styles/components/_fixed-menu.scss +4 -0
  36. package/src/styles/components/_image-uploader.scss +109 -0
  37. package/src/styles/components/_input.scss +165 -0
  38. package/src/styles/components/_tab.scss +74 -0
  39. package/src/styles/components/_tooltip.scss +152 -0
  40. package/webpack.config.js +7 -0
  41. package/webpack.dev.config.js +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/neeto-editor",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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": [
@@ -15,6 +15,7 @@
15
15
  "@bigbinary/neeto-icons": "^1.8.6",
16
16
  "@risingstack/react-easy-state": "^6.3.0",
17
17
  "@tailwindcss/typography": "^0.4.0",
18
+ "@tippyjs/react": "^4.2.6",
18
19
  "@tiptap/extension-bubble-menu": "^2.0.0-beta.18",
19
20
  "@tiptap/extension-code-block-lowlight": "^2.0.0-beta.24",
20
21
  "@tiptap/extension-color": "^2.0.0-beta.3",
@@ -31,9 +32,10 @@
31
32
  "@tiptap/react": "^2.0.0-beta.37",
32
33
  "@tiptap/starter-kit": "^2.0.0-beta.57",
33
34
  "@tiptap/suggestion": "^2.0.0-beta.55",
34
- "@uppy/core": "^1.18.1",
35
- "@uppy/react": "^1.11.8",
36
- "@uppy/xhr-upload": "^1.7.2",
35
+ "@uppy/core": "^2.1.2",
36
+ "@uppy/react": "^2.1.1",
37
+ "@uppy/url": "^2.0.4",
38
+ "@uppy/xhr-upload": "^2.0.5",
37
39
  "classnames": "^2.3.1",
38
40
  "lodash.isempty": "^4.4.0",
39
41
  "lodash.isplainobject": "^4.0.6",
@@ -43,6 +45,7 @@
43
45
  "react-icons": "^4.2.0",
44
46
  "react-outside-click-handler": "^1.3.0",
45
47
  "react-popper": "^2.2.5",
48
+ "react-router-dom": "^6.0.2",
46
49
  "tippy.js": "^6.3.1"
47
50
  },
48
51
  "devDependencies": {
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import classnames from "classnames";
3
+ import { Link } from "react-router-dom";
4
+ import ToolTip from "common/ToolTip";
5
+
6
+ const noop = () => {};
7
+ const BUTTON_STYLES = {
8
+ primary: "primary",
9
+ secondary: "secondary",
10
+ danger: "danger",
11
+ text: "text",
12
+ link: "link",
13
+ };
14
+ const BUTTON_SIZES = { large: "large", default: "default" };
15
+ const ICON_POSITIONS = { left: "left", right: "right" };
16
+
17
+ const Button = React.forwardRef((props, ref) => {
18
+ let {
19
+ icon = null,
20
+ iconPosition = "right",
21
+ iconSize = 16,
22
+ label = "",
23
+ loading = false,
24
+ onClick = noop,
25
+ to = null,
26
+ type = "button",
27
+ style = "primary",
28
+ fullWidth = false,
29
+ className = "",
30
+ disabled = false,
31
+ size = null,
32
+ href = null,
33
+ tooltipProps = null,
34
+ ...otherProps
35
+ } = props;
36
+
37
+ const handleClick = (e) => {
38
+ if (!loading && !disabled) {
39
+ onClick(e);
40
+ }
41
+ };
42
+
43
+ let Parent, elementSpecificProps;
44
+ if (to) {
45
+ Parent = Link;
46
+ elementSpecificProps = { to };
47
+ } else if (href) {
48
+ Parent = "a";
49
+ elementSpecificProps = { href };
50
+ } else {
51
+ Parent = "button";
52
+ elementSpecificProps = {
53
+ type,
54
+ };
55
+ }
56
+
57
+ let Icon =
58
+ typeof icon == "string"
59
+ ? () => <i className={classnames("neeto-ui-btn__icon", [icon])} />
60
+ : icon || React.Fragment;
61
+
62
+ const spinnerMarginSide =
63
+ iconPosition == "left" ? "marginRight" : "marginLeft";
64
+
65
+ return (
66
+ <ToolTip {...tooltipProps} disabled={!tooltipProps}>
67
+ <Parent
68
+ ref={ref}
69
+ onClick={handleClick}
70
+ className={classnames("neeto-ui-btn", [className], {
71
+ "neeto-ui-btn--style-primary": style === BUTTON_STYLES.primary,
72
+ "neeto-ui-btn--style-secondary": style === BUTTON_STYLES.secondary,
73
+ "neeto-ui-btn--style-danger": style === BUTTON_STYLES.danger,
74
+ "neeto-ui-btn--style-text": style === BUTTON_STYLES.text,
75
+ "neeto-ui-btn--style-link": style === BUTTON_STYLES.link,
76
+ "neeto-ui-btn--size-large": size === BUTTON_SIZES.large,
77
+ "neeto-ui-btn--width-full": fullWidth,
78
+ "neeto-ui-btn--icon-left": iconPosition == ICON_POSITIONS.left,
79
+ "neeto-ui-btn--icon-only": !label,
80
+ disabled: disabled,
81
+ })}
82
+ disabled={disabled}
83
+ {...elementSpecificProps}
84
+ {...otherProps}
85
+ >
86
+ {label && <span>{label}</span>}
87
+ {icon ? (
88
+ <Icon key="2" size={iconSize} className="neeto-ui-btn__icon" />
89
+ ) : null}
90
+ </Parent>
91
+ </ToolTip>
92
+ );
93
+ });
94
+
95
+ export default Button;
@@ -2,11 +2,7 @@ import React from "react";
2
2
  import classNames from "classnames";
3
3
 
4
4
  const Description = ({ children, className }) => {
5
- return (
6
- <p className={classNames("font-serif", { [className]: className })}>
7
- {children}
8
- </p>
9
- );
5
+ return <p className={classNames({ [className]: className })}>{children}</p>;
10
6
  };
11
7
 
12
8
  export default Description;
@@ -77,14 +77,18 @@ const Dropdown = ({
77
77
  }}
78
78
  >
79
79
  <div
80
- className={classnames("neeto-ui-dropdown__wrapper", {
80
+ className={classnames("neeto-ui-dropdown__wrapper h-full", {
81
81
  "neeto-ui-dropdown__wrapper--auto-width": autoWidth,
82
82
  [className]: className,
83
83
  })}
84
84
  {...otherProps}
85
85
  >
86
86
  {customTarget ? (
87
- <div ref={setReference} onClick={handleButtonClick}>
87
+ <div
88
+ ref={setReference}
89
+ onClick={handleButtonClick}
90
+ className="h-full"
91
+ >
88
92
  <Target />
89
93
  </div>
90
94
  ) : (
@@ -0,0 +1,70 @@
1
+ import React from "react";
2
+ import classnames from "classnames";
3
+
4
+ import Label from "./Label";
5
+ import { hyphenize } from "../utils/common";
6
+
7
+ const Input = React.forwardRef((props, ref) => {
8
+ const {
9
+ id,
10
+ size = "small",
11
+ type = "text",
12
+ label,
13
+ error = null,
14
+ suffix = null,
15
+ prefix = null,
16
+ disabled = false,
17
+ helpText = "",
18
+ className = "",
19
+ nakedInput = false,
20
+ contentSize = null,
21
+ required = false,
22
+ ...otherProps
23
+ } = props;
24
+
25
+ return (
26
+ <div className={classnames(["neeto-ui-input__wrapper", className])}>
27
+ {label && (
28
+ <Label
29
+ required={required}
30
+ data-cy={`${hyphenize(label)}-input-label`}
31
+ htmlFor={id}
32
+ >
33
+ {label}
34
+ </Label>
35
+ )}
36
+ <div
37
+ className={classnames("neeto-ui-input", {
38
+ "neeto-ui-input--naked": !!nakedInput,
39
+ "neeto-ui-input--error": !!error,
40
+ "neeto-ui-input--disabled": !!disabled,
41
+ "neeto-ui-input--small": size === "small",
42
+ })}
43
+ >
44
+ {prefix && <div className="neeto-ui-input__prefix">{prefix}</div>}
45
+ <input
46
+ ref={ref}
47
+ type={type}
48
+ disabled={disabled}
49
+ size={contentSize}
50
+ required={required}
51
+ aria-invalid={!!error}
52
+ data-cy={"input-field"}
53
+ {...otherProps}
54
+ />
55
+ {suffix && <div className="neeto-ui-input__suffix">{suffix}</div>}
56
+ </div>
57
+ {!!error && (
58
+ <p
59
+ data-cy={`${hyphenize(label)}-input-error`}
60
+ className="neeto-ui-input__error"
61
+ >
62
+ {error}
63
+ </p>
64
+ )}
65
+ {helpText && <p className="neeto-ui-input__help-text">{helpText}</p>}
66
+ </div>
67
+ );
68
+ });
69
+
70
+ export default Input;
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import classnames from "classnames";
3
+ import { Info } from "@bigbinary/neeto-icons";
4
+
5
+ import Tooltip from "common/ToolTip";
6
+
7
+ const Label = ({
8
+ children,
9
+ className = "",
10
+ required = false,
11
+ helpIconProps = null,
12
+ ...otherProps
13
+ }) => {
14
+ const {
15
+ onClick,
16
+ icon,
17
+ tooltipProps,
18
+ className: helpIconClassName,
19
+ ...otherHelpIconProps
20
+ } = helpIconProps || {};
21
+ const HelpIcon = icon || Info;
22
+ return (
23
+ <label
24
+ className={classnames("neeto-ui-label flex items-center", className)}
25
+ {...otherProps}
26
+ >
27
+ {children}
28
+ {required && <span aria-hidden>*</span>}
29
+ {helpIconProps && (
30
+ <Tooltip {...tooltipProps} disabled={!tooltipProps}>
31
+ <span
32
+ className={classnames("ml-1", {
33
+ [helpIconClassName]: helpIconClassName,
34
+ })}
35
+ onClick={onClick}
36
+ >
37
+ <HelpIcon size={16} {...otherHelpIconProps} />
38
+ </span>
39
+ </Tooltip>
40
+ )}
41
+ </label>
42
+ );
43
+ };
44
+
45
+ export default Label;
@@ -0,0 +1,91 @@
1
+ import React from "react";
2
+
3
+ const Modal = ({
4
+ isVisible,
5
+ onClose,
6
+ title,
7
+ buttonTextPrimary,
8
+ buttonTextSecondary,
9
+ content,
10
+ children = null,
11
+ }) => {
12
+ if (!isVisible) {
13
+ return null;
14
+ }
15
+
16
+ return (
17
+ <div
18
+ className="fixed inset-0 z-10 overflow-y-auto"
19
+ aria-labelledby="modal-title"
20
+ role="dialog"
21
+ aria-modal="true"
22
+ >
23
+ <div
24
+ className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0"
25
+ onClick={onClose}
26
+ >
27
+ <div
28
+ className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-50"
29
+ aria-hidden="true"
30
+ ></div>
31
+
32
+ <span
33
+ className="hidden sm:inline-block sm:align-middle sm:h-screen"
34
+ aria-hidden="true"
35
+ >
36
+ &#8203;
37
+ </span>
38
+
39
+ <div
40
+ className="inline-block overflow-hidden text-left align-bottom transition-all transform bg-white rounded shadow-xl sm:my-8 sm:align-middle sm:max-w-lg"
41
+ onClick={(e) => e.stopPropagation()}
42
+ >
43
+ {title || content ? (
44
+ <div className="px-4 pt-5 pb-4 bg-white sm:p-6 sm:pb-4">
45
+ <div className="sm:flex sm:items-start">
46
+ <div className="mt-3 text-center sm:mt-0 sm:text-left">
47
+ {title ? (
48
+ <h3
49
+ className="text-lg font-medium leading-6 text-gray-900"
50
+ id="modal-title"
51
+ >
52
+ {title}
53
+ </h3>
54
+ ) : null}
55
+ {content ? (
56
+ <div className="mt-2">
57
+ <p className="text-sm text-gray-500">{content}</p>
58
+ </div>
59
+ ) : null}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ ) : null}
64
+ {children}
65
+ {buttonTextPrimary || buttonTextSecondary ? (
66
+ <div className="px-4 py-3 bg-gray-50 sm:px-6 sm:flex sm:flex-row-reverse">
67
+ {buttonTextPrimary ? (
68
+ <button
69
+ type="button"
70
+ className="inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white bg-red-600 border border-transparent rounded-md shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
71
+ >
72
+ {buttonTextPrimary}
73
+ </button>
74
+ ) : null}
75
+ {buttonTextSecondary ? (
76
+ <button
77
+ type="button"
78
+ className="inline-flex justify-center w-full px-4 py-2 mt-3 text-base font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
79
+ >
80
+ {buttonTextSecondary}
81
+ </button>
82
+ ) : null}
83
+ </div>
84
+ ) : null}
85
+ </div>
86
+ </div>
87
+ </div>
88
+ );
89
+ };
90
+
91
+ export default Modal;
@@ -0,0 +1,79 @@
1
+ import React from "react";
2
+ import { NavLink } from "react-router-dom";
3
+ import classnames from "classnames";
4
+
5
+ const noop = () => {};
6
+
7
+ const TAB_SIZES = { large: "large", default: "default" };
8
+
9
+ const Tab = ({
10
+ children,
11
+ size,
12
+ noUnderline,
13
+ className = "",
14
+ ...otherProps
15
+ }) => {
16
+ return (
17
+ <div
18
+ className={classnames(
19
+ {
20
+ "neeto-ui-tab__wrapper flex": true,
21
+ },
22
+ {
23
+ "neeto-ui-tab__wrapper--size-large": size === TAB_SIZES.large,
24
+ },
25
+ {
26
+ "neeto-ui-tab__wrapper--underline-none": noUnderline,
27
+ },
28
+ [className]
29
+ )}
30
+ data-cy="tab-container"
31
+ {...otherProps}
32
+ >
33
+ {children}
34
+ </div>
35
+ );
36
+ };
37
+
38
+ const Item = ({
39
+ active,
40
+ className = "",
41
+ children,
42
+ icon = null,
43
+ onClick = noop,
44
+ activeClassName = "",
45
+ ...otherProps
46
+ }) => {
47
+ let Parent = "button";
48
+ let Icon =
49
+ typeof icon == "string"
50
+ ? () => <i className={icon} data-cy="tab-item-icon" />
51
+ : icon || React.Fragment;
52
+
53
+ if (activeClassName) {
54
+ Parent = NavLink;
55
+ }
56
+ return (
57
+ <Parent
58
+ className={classnames(
59
+ [
60
+ "neeto-ui-tab flex items-center justify-center select-none",
61
+ className,
62
+ ],
63
+ {
64
+ active: active,
65
+ }
66
+ )}
67
+ onClick={onClick}
68
+ data-cy="tab-item"
69
+ {...otherProps}
70
+ >
71
+ {icon && <Icon className="neeto-ui-tab__icon" />}
72
+ {children}
73
+ </Parent>
74
+ );
75
+ };
76
+
77
+ Tab.Item = Item;
78
+
79
+ export default Tab;
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import Tippy from "@tippyjs/react";
3
+ import { followCursor } from "tippy.js";
4
+
5
+ const Tooltip = ({
6
+ content,
7
+ children,
8
+ theme = "dark",
9
+ disabled = false,
10
+ placement, // Remove this prop once this prop is migrated to position in all neeto products
11
+ position = "auto",
12
+ interactive = false,
13
+ ...otherProps
14
+ }) => {
15
+ return (
16
+ <Tippy
17
+ role="tooltip"
18
+ theme={theme}
19
+ content={content}
20
+ arrow={
21
+ "<svg width='10' height='5' viewBox='0 0 10 5' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M10 5H0.926697L3.95208 1.63847C4.74227 0.760478 6.11722 0.754951 6.91445 1.62656L10 5Z' /></svg>"
22
+ }
23
+ disabled={disabled}
24
+ animation={"scale-subtle"}
25
+ placement={placement || position}
26
+ plugins={[followCursor]}
27
+ interactive={interactive}
28
+ duration={[100, 200]}
29
+ zIndex={100001}
30
+ {...otherProps}
31
+ >
32
+ {children}
33
+ </Tippy>
34
+ );
35
+ };
36
+
37
+ export default Tooltip;
@@ -65,7 +65,7 @@ export default function index({ editor, formatterOptions }) {
65
65
  <BubbleMenu
66
66
  editor={editor}
67
67
  tippyOptions={{ arrow: roundArrow }}
68
- className="relative flex rounded shadow editor-command-list--root"
68
+ className="relative flex overflow-hidden rounded shadow editor-command-list--root"
69
69
  >
70
70
  {options
71
71
  .filter(({ optionName }) => formatterOptions.includes(optionName))
@@ -79,7 +79,7 @@ export default function index({ editor, formatterOptions }) {
79
79
  const Option = ({ Icon, command, active, iconSize }) => (
80
80
  <div
81
81
  className={classnames(
82
- "p-3 cursor-pointer editor-command-list--item transition-colors rounded",
82
+ "p-3 cursor-pointer editor-command-list--item transition-colors",
83
83
  {
84
84
  "text-gray-400": !active,
85
85
  "text-white": active,
@@ -3,7 +3,7 @@ import React from "react";
3
3
  import { TextSize } from "@bigbinary/neeto-icons";
4
4
 
5
5
  import { ICON_COLOR_ACTIVE, MENU_ICON_SIZE } from "./constants";
6
- import Dropdown from "../../../Common/Dropdown";
6
+ import Dropdown from "common/Dropdown";
7
7
 
8
8
  const FontSizeOption = ({ onChange }) => {
9
9
  const options = [
@@ -15,13 +15,13 @@ const FontSizeOption = ({ onChange }) => {
15
15
  return (
16
16
  <Dropdown
17
17
  customTarget={() => (
18
- <button className="p-3 transition-colors editor-fixed-menu--item">
18
+ <button className="h-full p-3 transition-colors editor-fixed-menu--item">
19
19
  <TextSize size={MENU_ICON_SIZE} color={ICON_COLOR_ACTIVE} />
20
20
  </button>
21
21
  )}
22
22
  >
23
23
  {options.map(({ label, className, value }) => (
24
- <li className={className} onClick={() => onChange(value)}>
24
+ <li className={className} onClick={() => onChange(value)} key={value}>
25
25
  {label}
26
26
  </li>
27
27
  ))}
@@ -1,6 +1,6 @@
1
1
  import React, { useRef } from "react";
2
2
 
3
- import { TextColor } from "../../../Common/Icons";
3
+ import { TextColor } from "common/Icons";
4
4
  import { ICON_COLOR_ACTIVE, MENU_ICON_SIZE } from "./constants";
5
5
 
6
6
  const TextColorOption = ({ color = "#000", onChange }) => {
@@ -18,10 +18,9 @@ import TextColorOption from "./TextColorOption";
18
18
  import FontSizeOption from "./FontSizeOption";
19
19
 
20
20
  import { ICON_COLOR_ACTIVE, ICON_COLOR_INACTIVE } from "./constants";
21
- import sharedState from "../../sharedState";
22
21
  import Variables from "../Variable";
23
22
 
24
- const FixedMenu = ({ editor, variables }) => {
23
+ const FixedMenu = ({ editor, variables, setImageUploadVisible }) => {
25
24
  if (!editor) {
26
25
  return null;
27
26
  }
@@ -82,11 +81,7 @@ const FixedMenu = ({ editor, variables }) => {
82
81
  },
83
82
  {
84
83
  Icon: Image,
85
- command: ({ editor, range }) => {
86
- sharedState.showImageUpload = true;
87
- sharedState.range = range;
88
- editor.chain().focus().deleteRange(range).run();
89
- },
84
+ command: () => setImageUploadVisible(true),
90
85
  optionName: "image-upload",
91
86
  },
92
87
  ];
@@ -162,7 +157,7 @@ const FixedMenu = ({ editor, variables }) => {
162
157
  );
163
158
 
164
159
  return (
165
- <div className="flex items-center space-x-6 border-t border-l border-r editor-fixed-menu--root">
160
+ <div className="flex space-x-6 border-t border-l border-r editor-fixed-menu--root">
166
161
  <div className="flex">
167
162
  <TextColorOption
168
163
  color={editor.getAttributes("textStyle").color}
@@ -172,8 +167,10 @@ const FixedMenu = ({ editor, variables }) => {
172
167
  {fontStyleOptions.map(renderOptionButton)}
173
168
  </div>
174
169
  {[blockStyleOptions, listStyleOptions, editorOptions].map(
175
- (optionGroup) => (
176
- <div className="flex">{optionGroup.map(renderOptionButton)}</div>
170
+ (optionGroup, index) => (
171
+ <div className="flex" key={index}>
172
+ {optionGroup.map(renderOptionButton)}
173
+ </div>
177
174
  )
178
175
  )}
179
176
  <div className="flex justify-end flex-1">
@@ -0,0 +1,39 @@
1
+ import React, { useState } from "react";
2
+
3
+ import Input from "common/Input";
4
+ import Button from "common/Button";
5
+
6
+ import { UrlRegExp } from "../../../../constants/regexp";
7
+
8
+ const URLField = ({ onSubmit }) => {
9
+ const [urlString, setUrlString] = useState("");
10
+ const [error, setError] = useState("");
11
+
12
+ return (
13
+ <form
14
+ onSubmit={(e) => {
15
+ e.preventDefault();
16
+ if (UrlRegExp.test(urlString)) {
17
+ onSubmit(urlString);
18
+ } else {
19
+ setError("Please provide a valid image url");
20
+ }
21
+ }}
22
+ className="flex flex-col items-center justify-center flex-1 mx-10"
23
+ >
24
+ <div className="flex-row w-full mb-4">
25
+ <Input
26
+ name="url"
27
+ value={urlString}
28
+ placeholder="Paste the image link"
29
+ onFocus={() => setError("")}
30
+ error={error}
31
+ onChange={({ target: { value } }) => setUrlString(value)}
32
+ />
33
+ </div>
34
+ <Button type="submit" label="Upload Image" />
35
+ </form>
36
+ );
37
+ };
38
+
39
+ export default URLField;
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+
3
+ import { DragDrop } from "@uppy/react";
4
+
5
+ const LocalUploader = ({ uppy }) => {
6
+ return (
7
+ <DragDrop
8
+ note="Max. File Size: 5MB"
9
+ uppy={uppy}
10
+ locale={{
11
+ strings: {
12
+ dropHereOr: "Drop your file(s) here or %{browse}",
13
+ browse: "Browse",
14
+ },
15
+ }}
16
+ className="local-upload__root"
17
+ />
18
+ );
19
+ };
20
+
21
+ export default LocalUploader;