@c2-markdown-ui/react 0.4.4 → 0.4.5
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.
- package/README.md +24 -6
- package/dist/index.js +4365 -3733
- package/package.json +2 -2
- package/src/lib/MarkdownUI.tsx +47 -9
- package/src/lib/widgets.css +2188 -1372
- package/src/widgets/Button.tsx +39 -39
- package/src/widgets/ButtonGroup.tsx +47 -47
- package/src/widgets/Cascader.tsx +3 -2
- package/src/widgets/Chart.tsx +237 -237
- package/src/widgets/DatePicker.tsx +451 -434
- package/src/widgets/Form.tsx +512 -372
- package/src/widgets/Incomplete.tsx +3 -3
- package/src/widgets/MultipleChoiceQuestion.tsx +82 -82
- package/src/widgets/Quiz.tsx +320 -320
- package/src/widgets/Radio.tsx +57 -26
- package/src/widgets/Select.tsx +178 -142
- package/src/widgets/SelectMulti.tsx +105 -72
- package/src/widgets/ShortAnswerQuestion.tsx +101 -101
- package/src/widgets/Slider.tsx +70 -56
- package/src/widgets/Switch.tsx +14 -2
- package/src/widgets/TextInput.tsx +76 -38
- package/src/widgets/TreeSelect.tsx +649 -0
- package/src/widgets/Widget.tsx +130 -82
- package/src/widgets/choiceUtils.ts +90 -45
- package/src/widgets/index.ts +17 -16
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/MarkdownUI.d.ts +0 -10
- package/dist/lib/MarkdownUI.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -4
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/widgets/Button.d.ts +0 -15
- package/dist/widgets/Button.d.ts.map +0 -1
- package/dist/widgets/ButtonGroup.d.ts +0 -11
- package/dist/widgets/ButtonGroup.d.ts.map +0 -1
- package/dist/widgets/Cascader.d.ts +0 -34
- package/dist/widgets/Cascader.d.ts.map +0 -1
- package/dist/widgets/Chart.d.ts +0 -20
- package/dist/widgets/Chart.d.ts.map +0 -1
- package/dist/widgets/DatePicker.d.ts +0 -28
- package/dist/widgets/DatePicker.d.ts.map +0 -1
- package/dist/widgets/Form.d.ts +0 -47
- package/dist/widgets/Form.d.ts.map +0 -1
- package/dist/widgets/Incomplete.d.ts +0 -3
- package/dist/widgets/Incomplete.d.ts.map +0 -1
- package/dist/widgets/MultipleChoiceQuestion.d.ts +0 -15
- package/dist/widgets/MultipleChoiceQuestion.d.ts.map +0 -1
- package/dist/widgets/NumberInput.d.ts +0 -26
- package/dist/widgets/NumberInput.d.ts.map +0 -1
- package/dist/widgets/Quiz.d.ts +0 -37
- package/dist/widgets/Quiz.d.ts.map +0 -1
- package/dist/widgets/Radio.d.ts +0 -24
- package/dist/widgets/Radio.d.ts.map +0 -1
- package/dist/widgets/Select.d.ts +0 -15
- package/dist/widgets/Select.d.ts.map +0 -1
- package/dist/widgets/SelectMulti.d.ts +0 -20
- package/dist/widgets/SelectMulti.d.ts.map +0 -1
- package/dist/widgets/ShortAnswerQuestion.d.ts +0 -16
- package/dist/widgets/ShortAnswerQuestion.d.ts.map +0 -1
- package/dist/widgets/Slider.d.ts +0 -13
- package/dist/widgets/Slider.d.ts.map +0 -1
- package/dist/widgets/Switch.d.ts +0 -17
- package/dist/widgets/Switch.d.ts.map +0 -1
- package/dist/widgets/TextInput.d.ts +0 -11
- package/dist/widgets/TextInput.d.ts.map +0 -1
- package/dist/widgets/Widget.d.ts +0 -7
- package/dist/widgets/Widget.d.ts.map +0 -1
- package/dist/widgets/chartRemoteData.d.ts +0 -18
- package/dist/widgets/chartRemoteData.d.ts.map +0 -1
- package/dist/widgets/choiceUtils.d.ts +0 -24
- package/dist/widgets/choiceUtils.d.ts.map +0 -1
- package/dist/widgets/index.d.ts +0 -18
- package/dist/widgets/index.d.ts.map +0 -1
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface ShortAnswerQuestionProps {
|
|
4
|
-
question: string;
|
|
5
|
-
placeholder?: string;
|
|
6
|
-
correctAnswer?: string;
|
|
7
|
-
correctAnswers?: string[];
|
|
8
|
-
showFeedback?: boolean;
|
|
9
|
-
/** 是否禁用:禁用后输入框与提交按钮不可交互 */
|
|
10
|
-
disabled?: boolean;
|
|
11
|
-
onchange: (value: { answer: string; isCorrect?: boolean }) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const ShortAnswerQuestion: React.FC<ShortAnswerQuestionProps> = ({
|
|
15
|
-
question,
|
|
16
|
-
placeholder = "Type your answer here...",
|
|
17
|
-
correctAnswer,
|
|
18
|
-
correctAnswers,
|
|
19
|
-
showFeedback = false,
|
|
20
|
-
disabled = false,
|
|
21
|
-
onchange
|
|
22
|
-
}) => {
|
|
23
|
-
const [answer, setAnswer] = useState('');
|
|
24
|
-
const [hasAnswered, setHasAnswered] = useState(false);
|
|
25
|
-
const [isCorrect, setIsCorrect] = useState<boolean | undefined>(undefined);
|
|
26
|
-
|
|
27
|
-
const handleSubmit = () => {
|
|
28
|
-
if (disabled) return;
|
|
29
|
-
if (!answer.trim()) return;
|
|
30
|
-
|
|
31
|
-
setHasAnswered(true);
|
|
32
|
-
|
|
33
|
-
const normalized = (correctAnswers && correctAnswers.length > 0)
|
|
34
|
-
? correctAnswers
|
|
35
|
-
: (typeof correctAnswer === 'string' ? [correctAnswer] : undefined);
|
|
36
|
-
|
|
37
|
-
const correct = normalized
|
|
38
|
-
? normalized.some(a => a.toLowerCase() === answer.trim().toLowerCase())
|
|
39
|
-
: undefined;
|
|
40
|
-
|
|
41
|
-
setIsCorrect(correct);
|
|
42
|
-
onchange({ answer: answer.trim(), isCorrect: correct });
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
46
|
-
if (e.key === 'Enter' && !hasAnswered && !disabled) {
|
|
47
|
-
handleSubmit();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const getFeedbackMessage = () => {
|
|
52
|
-
if (!hasAnswered || !showFeedback) return null;
|
|
53
|
-
|
|
54
|
-
const hasAnswers = (correctAnswers && correctAnswers.length > 0) || typeof correctAnswer !== 'undefined';
|
|
55
|
-
if (!hasAnswers) {
|
|
56
|
-
return `Your answer: ${answer.trim()}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (correctAnswers && correctAnswers.length > 0) {
|
|
60
|
-
return isCorrect
|
|
61
|
-
? '✓ Correct!'
|
|
62
|
-
: `✗ Incorrect. Accepted answers: ${correctAnswers.join(', ')}`;
|
|
63
|
-
}
|
|
64
|
-
return isCorrect ? '✓ Correct!' : `✗ Incorrect. The correct answer is: ${correctAnswer}`;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div className="widget-saq">
|
|
69
|
-
<div className="saq-question">{question}</div>
|
|
70
|
-
<div className="saq-input-container">
|
|
71
|
-
<input
|
|
72
|
-
type="text"
|
|
73
|
-
value={answer}
|
|
74
|
-
onChange={(e) => setAnswer(e.target.value)}
|
|
75
|
-
onKeyDown={handleKeyDown}
|
|
76
|
-
placeholder={placeholder}
|
|
77
|
-
disabled={disabled || (hasAnswered && showFeedback)}
|
|
78
|
-
className={`saq-input ${
|
|
79
|
-
hasAnswered && showFeedback
|
|
80
|
-
? isCorrect ? 'saq-input-correct' : 'saq-input-incorrect'
|
|
81
|
-
: ''
|
|
82
|
-
}`}
|
|
83
|
-
/>
|
|
84
|
-
{!hasAnswered && (
|
|
85
|
-
<button
|
|
86
|
-
onClick={handleSubmit}
|
|
87
|
-
disabled={disabled || !answer.trim()}
|
|
88
|
-
className="saq-submit"
|
|
89
|
-
>
|
|
90
|
-
Submit
|
|
91
|
-
</button>
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
{hasAnswered && showFeedback && getFeedbackMessage() && (
|
|
95
|
-
<div className={`saq-feedback ${isCorrect ? 'correct' : 'incorrect'}`}>
|
|
96
|
-
{getFeedbackMessage()}
|
|
97
|
-
</div>
|
|
98
|
-
)}
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
};
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ShortAnswerQuestionProps {
|
|
4
|
+
question: string;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
correctAnswer?: string;
|
|
7
|
+
correctAnswers?: string[];
|
|
8
|
+
showFeedback?: boolean;
|
|
9
|
+
/** 是否禁用:禁用后输入框与提交按钮不可交互 */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
onchange: (value: { answer: string; isCorrect?: boolean }) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ShortAnswerQuestion: React.FC<ShortAnswerQuestionProps> = ({
|
|
15
|
+
question,
|
|
16
|
+
placeholder = "Type your answer here...",
|
|
17
|
+
correctAnswer,
|
|
18
|
+
correctAnswers,
|
|
19
|
+
showFeedback = false,
|
|
20
|
+
disabled = false,
|
|
21
|
+
onchange
|
|
22
|
+
}) => {
|
|
23
|
+
const [answer, setAnswer] = useState('');
|
|
24
|
+
const [hasAnswered, setHasAnswered] = useState(false);
|
|
25
|
+
const [isCorrect, setIsCorrect] = useState<boolean | undefined>(undefined);
|
|
26
|
+
|
|
27
|
+
const handleSubmit = () => {
|
|
28
|
+
if (disabled) return;
|
|
29
|
+
if (!answer.trim()) return;
|
|
30
|
+
|
|
31
|
+
setHasAnswered(true);
|
|
32
|
+
|
|
33
|
+
const normalized = (correctAnswers && correctAnswers.length > 0)
|
|
34
|
+
? correctAnswers
|
|
35
|
+
: (typeof correctAnswer === 'string' ? [correctAnswer] : undefined);
|
|
36
|
+
|
|
37
|
+
const correct = normalized
|
|
38
|
+
? normalized.some(a => a.toLowerCase() === answer.trim().toLowerCase())
|
|
39
|
+
: undefined;
|
|
40
|
+
|
|
41
|
+
setIsCorrect(correct);
|
|
42
|
+
onchange({ answer: answer.trim(), isCorrect: correct });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
46
|
+
if (e.key === 'Enter' && !hasAnswered && !disabled) {
|
|
47
|
+
handleSubmit();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getFeedbackMessage = () => {
|
|
52
|
+
if (!hasAnswered || !showFeedback) return null;
|
|
53
|
+
|
|
54
|
+
const hasAnswers = (correctAnswers && correctAnswers.length > 0) || typeof correctAnswer !== 'undefined';
|
|
55
|
+
if (!hasAnswers) {
|
|
56
|
+
return `Your answer: ${answer.trim()}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (correctAnswers && correctAnswers.length > 0) {
|
|
60
|
+
return isCorrect
|
|
61
|
+
? '✓ Correct!'
|
|
62
|
+
: `✗ Incorrect. Accepted answers: ${correctAnswers.join(', ')}`;
|
|
63
|
+
}
|
|
64
|
+
return isCorrect ? '✓ Correct!' : `✗ Incorrect. The correct answer is: ${correctAnswer}`;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="widget-saq">
|
|
69
|
+
<div className="saq-question">{question}</div>
|
|
70
|
+
<div className="saq-input-container">
|
|
71
|
+
<input
|
|
72
|
+
type="text"
|
|
73
|
+
value={answer}
|
|
74
|
+
onChange={(e) => setAnswer(e.target.value)}
|
|
75
|
+
onKeyDown={handleKeyDown}
|
|
76
|
+
placeholder={placeholder}
|
|
77
|
+
disabled={disabled || (hasAnswered && showFeedback)}
|
|
78
|
+
className={`saq-input ${
|
|
79
|
+
hasAnswered && showFeedback
|
|
80
|
+
? isCorrect ? 'saq-input-correct' : 'saq-input-incorrect'
|
|
81
|
+
: ''
|
|
82
|
+
}`}
|
|
83
|
+
/>
|
|
84
|
+
{!hasAnswered && (
|
|
85
|
+
<button
|
|
86
|
+
onClick={handleSubmit}
|
|
87
|
+
disabled={disabled || !answer.trim()}
|
|
88
|
+
className="saq-submit"
|
|
89
|
+
>
|
|
90
|
+
Submit
|
|
91
|
+
</button>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
{hasAnswered && showFeedback && getFeedbackMessage() && (
|
|
95
|
+
<div className={`saq-feedback ${isCorrect ? 'correct' : 'incorrect'}`}>
|
|
96
|
+
{getFeedbackMessage()}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
};
|
package/src/widgets/Slider.tsx
CHANGED
|
@@ -1,57 +1,71 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface SliderProps {
|
|
4
|
-
min: number;
|
|
5
|
-
max: number;
|
|
6
|
-
step?: number;
|
|
7
|
-
label?: string;
|
|
8
|
-
default?: number;
|
|
9
|
-
/** 是否禁用 */
|
|
10
|
-
disabled?: boolean;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface SliderProps {
|
|
4
|
+
min: number;
|
|
5
|
+
max: number;
|
|
6
|
+
step?: number;
|
|
7
|
+
label?: string;
|
|
8
|
+
default?: number;
|
|
9
|
+
/** 是否禁用 */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** 表单项说明:label 后 ⓘ,悬浮显示 tooltip */
|
|
12
|
+
hint?: string;
|
|
13
|
+
/** 是否必填(slider 始终有值,仅语义标记) */
|
|
14
|
+
required?: boolean;
|
|
15
|
+
onchange: (value: number) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Slider: React.FC<SliderProps> = ({
|
|
19
|
+
min,
|
|
20
|
+
max,
|
|
21
|
+
step = 1,
|
|
22
|
+
label = '',
|
|
23
|
+
default: initial = min,
|
|
24
|
+
disabled = false,
|
|
25
|
+
hint = '',
|
|
26
|
+
required = false,
|
|
27
|
+
onchange
|
|
28
|
+
}) => {
|
|
29
|
+
const [value, setValue] = useState(initial);
|
|
30
|
+
|
|
31
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
32
|
+
const newValue = Number(e.target.value);
|
|
33
|
+
setValue(newValue);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleComplete = () => {
|
|
37
|
+
onchange(value);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="widget-slider">
|
|
42
|
+
{label || hint || required ? (
|
|
43
|
+
<div className="widget-label-row">
|
|
44
|
+
{required && <span className="widget-required-mark" aria-hidden="true">*</span>}
|
|
45
|
+
{label && <label>{label}</label>}
|
|
46
|
+
{hint && <span className="widget-hint" data-hint={hint}>ⓘ</span>}
|
|
47
|
+
</div>
|
|
48
|
+
) : null}
|
|
49
|
+
<div className="widget-field-content">
|
|
50
|
+
<div className="slider-container">
|
|
51
|
+
<div className="slider-values">
|
|
52
|
+
<span className="min-value">{min}</span>
|
|
53
|
+
<span className="current-value">{value}</span>
|
|
54
|
+
<span className="max-value">{max}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<input
|
|
57
|
+
type="range"
|
|
58
|
+
value={value}
|
|
59
|
+
min={min}
|
|
60
|
+
max={max}
|
|
61
|
+
step={step}
|
|
62
|
+
onChange={handleChange}
|
|
63
|
+
onMouseUp={handleComplete}
|
|
64
|
+
onTouchEnd={handleComplete}
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
57
71
|
};
|
package/src/widgets/Switch.tsx
CHANGED
|
@@ -10,17 +10,23 @@ export interface SwitchProps {
|
|
|
10
10
|
unCheckedText?: string;
|
|
11
11
|
/** 是否禁用 */
|
|
12
12
|
disabled?: boolean;
|
|
13
|
+
/** 表单项说明:label 后 ⓘ,悬浮显示 tooltip */
|
|
14
|
+
hint?: string;
|
|
15
|
+
/** 是否必填(switch 始终有值,仅语义标记) */
|
|
16
|
+
required?: boolean;
|
|
13
17
|
onchange: (value: boolean) => void;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
/** 开关组件(参考 antd Switch):布尔切换控件,可配置选中/未选中文案。
|
|
17
|
-
label
|
|
21
|
+
与表单其他字段同构(label-row 在前、内容在后),表单 layout:horizontal 时 label 在左、开关在右 */
|
|
18
22
|
export const Switch: React.FC<SwitchProps> = ({
|
|
19
23
|
label = '',
|
|
20
24
|
default: initial = false,
|
|
21
25
|
checkedText = '',
|
|
22
26
|
unCheckedText = '',
|
|
23
27
|
disabled = false,
|
|
28
|
+
hint = '',
|
|
29
|
+
required = false,
|
|
24
30
|
onchange
|
|
25
31
|
}) => {
|
|
26
32
|
const [checked, setChecked] = useState(initial);
|
|
@@ -34,7 +40,13 @@ export const Switch: React.FC<SwitchProps> = ({
|
|
|
34
40
|
|
|
35
41
|
return (
|
|
36
42
|
<div className="widget-switch">
|
|
37
|
-
{label
|
|
43
|
+
{label || hint || required ? (
|
|
44
|
+
<div className="widget-label-row switch-label-row">
|
|
45
|
+
{required && <span className="widget-required-mark" aria-hidden="true">*</span>}
|
|
46
|
+
{label && <label>{label}</label>}
|
|
47
|
+
{hint && <span className="widget-hint" data-hint={hint}>ⓘ</span>}
|
|
48
|
+
</div>
|
|
49
|
+
) : null}
|
|
38
50
|
<div className="widget-field-content">
|
|
39
51
|
<button
|
|
40
52
|
type="button"
|
|
@@ -1,38 +1,76 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface TextInputProps {
|
|
4
|
-
placeholder?: string;
|
|
5
|
-
label?: string;
|
|
6
|
-
default?: string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
};
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TextInputProps {
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
default?: string;
|
|
7
|
+
hint?: string;
|
|
8
|
+
/** 是否必填:label 前红色 * */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
/** 正则校验规则(JS 正则源字符串) */
|
|
11
|
+
pattern?: string;
|
|
12
|
+
/** 校验失败提示文案 */
|
|
13
|
+
message?: string;
|
|
14
|
+
/** 外部(form 提交时)传入的错误信息,优先于组件内部即时校验 */
|
|
15
|
+
error?: string;
|
|
16
|
+
/** 是否禁用 */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
onchange: (value: string) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TextInput: React.FC<TextInputProps> = ({
|
|
22
|
+
placeholder = '',
|
|
23
|
+
label = '',
|
|
24
|
+
default: initial = '',
|
|
25
|
+
hint = '',
|
|
26
|
+
required = false,
|
|
27
|
+
pattern,
|
|
28
|
+
message = '',
|
|
29
|
+
error = '',
|
|
30
|
+
disabled = false,
|
|
31
|
+
onchange
|
|
32
|
+
}) => {
|
|
33
|
+
const [value, setValue] = useState(initial);
|
|
34
|
+
const [localError, setLocalError] = useState('');
|
|
35
|
+
|
|
36
|
+
/** 校验当前值:返回错误文案或空串 */
|
|
37
|
+
const validate = (v: string): string => {
|
|
38
|
+
if (required && !v.trim()) return message || (label ? `请填写${label}` : '该项为必填项');
|
|
39
|
+
if (pattern && v && !new RegExp(pattern).test(v)) return message || (label ? `${label}格式不正确` : '格式不正确');
|
|
40
|
+
return '';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const commit = () => {
|
|
44
|
+
setLocalError(validate(value));
|
|
45
|
+
onchange(value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const shownError = error || localError;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={`widget-text-input${shownError ? ' widget-field-error' : ''}`}>
|
|
52
|
+
{label || hint || required ? (
|
|
53
|
+
<div className="widget-label-row">
|
|
54
|
+
{required && <span className="widget-required-mark" aria-hidden="true">*</span>}
|
|
55
|
+
{label && <label>{label}</label>}
|
|
56
|
+
{hint && <span className="widget-hint" data-hint={hint}>ⓘ</span>}
|
|
57
|
+
</div>
|
|
58
|
+
) : null}
|
|
59
|
+
<div className="widget-field-content">
|
|
60
|
+
<input
|
|
61
|
+
type="text"
|
|
62
|
+
className={shownError ? 'widget-input-error' : undefined}
|
|
63
|
+
value={value}
|
|
64
|
+
onChange={(e) => {
|
|
65
|
+
setValue(e.target.value);
|
|
66
|
+
if (localError) setLocalError(validate(e.target.value));
|
|
67
|
+
}}
|
|
68
|
+
onBlur={commit}
|
|
69
|
+
placeholder={placeholder}
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
/>
|
|
72
|
+
{shownError ? <div className="widget-error-text" role="alert">{shownError}</div> : null}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|