diff --git a/public/locales/en/link-input-modal.json b/public/locales/en/link-input-modal.json
index 9928583..7ebdfc8 100644
--- a/public/locales/en/link-input-modal.json
+++ b/public/locales/en/link-input-modal.json
@@ -11,5 +11,7 @@
"newTab": "Open in new tab",
"currentTab": "Open in current window",
"cancel": "Cancel",
- "confirm": "Confirm"
+ "confirm": "Confirm",
+ "displayRequiredError": "Link Display Text is required",
+ "urlRequiredError": "Link URL is required"
}
diff --git a/public/locales/zh-TW/link-input-modal.json b/public/locales/zh-TW/link-input-modal.json
index 80bb129..8ac1273 100644
--- a/public/locales/zh-TW/link-input-modal.json
+++ b/public/locales/zh-TW/link-input-modal.json
@@ -11,5 +11,7 @@
"newTab": "在新分頁中開啟",
"currentTab": "在當前視窗中開啟",
"cancel": "取消",
- "confirm": "確認"
+ "confirm": "確認",
+ "displayRequiredError": "文字連結標題為必填",
+ "urlRequiredError": "文字連結 URL 為必填"
}
diff --git a/src/components/core/radio-group.js b/src/components/core/radio-group.js
new file mode 100644
index 0000000..0fa5e5f
--- /dev/null
+++ b/src/components/core/radio-group.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cn from 'classnames';
+
+const RadioGroup = ({ name, label, options, value, onChange }) => {
+ return (
+
+ {label &&
{label}}
+
+ {options.map((option) => (
+
+
+
+ ))}
+
+
+ );
+};
+
+RadioGroup.propTypes = {
+ name: PropTypes.string.isRequired,
+ label: PropTypes.string,
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ value: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ disabled: PropTypes.bool,
+ })
+ ).isRequired,
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default RadioGroup;
diff --git a/src/components/core/text-input.js b/src/components/core/text-input.js
new file mode 100644
index 0000000..2c70c9c
--- /dev/null
+++ b/src/components/core/text-input.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import cn from 'classnames';
+
+const TextInput = React.forwardRef(
+ (
+ {
+ id,
+ type = 'text',
+ value,
+ onChange,
+ placeholder,
+ disabled = false,
+ error,
+ required,
+ label,
+ hint,
+ className,
+ ...props
+ },
+ ref
+ ) => {
+ return (
+
+ {label && (
+
+ )}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+ }
+);
+
+TextInput.displayName = 'TextInput';
+
+TextInput.propTypes = {
+ id: PropTypes.string,
+ type: PropTypes.string,
+ value: PropTypes.string,
+ onChange: PropTypes.func,
+ placeholder: PropTypes.string,
+ disabled: PropTypes.bool,
+ error: PropTypes.string,
+ required: PropTypes.bool,
+ label: PropTypes.string,
+ hint: PropTypes.string,
+ className: PropTypes.string,
+};
+
+export default TextInput;
diff --git a/src/components/link-input-modal.js b/src/components/link-input-modal.js
index 0d2142a..62cca42 100644
--- a/src/components/link-input-modal.js
+++ b/src/components/link-input-modal.js
@@ -2,12 +2,16 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from '@/lib/i18n';
import BasicModal from '@/components/core/modal/basic-modal';
+import TextInput from '@/components/core/text-input';
+import RadioGroup from '@/components/core/radio-group';
const LinkInputModal = ({ isOpen, onClose, onConfirm }) => {
const [display, setDisplay] = useState('');
const [title, setTitle] = useState('');
const [url, setUrl] = useState('');
const [openInNewTab, setOpenInNewTab] = useState(true);
+ const [displayError, setDisplayError] = useState('');
+ const [urlError, setUrlError] = useState('');
const t = useTranslation('link-input-modal');
const resetForm = () => {
@@ -15,6 +19,8 @@ const LinkInputModal = ({ isOpen, onClose, onConfirm }) => {
setTitle('');
setUrl('');
setOpenInNewTab(true);
+ setDisplayError('');
+ setUrlError('');
};
const handleClose = () => {
@@ -23,7 +29,13 @@ const LinkInputModal = ({ isOpen, onClose, onConfirm }) => {
};
const handleConfirm = () => {
- if (!display.trim() || !url.trim()) return;
+ const isDisplayEmpty = !display.trim();
+ const isUrlEmpty = !url.trim();
+
+ if (isDisplayEmpty) setDisplayError(t('displayRequiredError'));
+ if (isUrlEmpty) setUrlError(t('urlRequiredError'));
+ if (isDisplayEmpty || isUrlEmpty) return;
+
const prefix = openInNewTab ? '@' : '';
const titlePart = title.trim() ? `[[${title.trim()}]]` : '';
const markdown = `${prefix}[${display.trim()}]${titlePart}(${url.trim()})`;
@@ -42,76 +54,48 @@ const LinkInputModal = ({ isOpen, onClose, onConfirm }) => {
confirmLabel={t('confirm')}
>
);