Skip to content

Commit e00d81e

Browse files
committed
feat: add basic Input and TextArea component
1 parent 6fd365e commit e00d81e

File tree

10 files changed

+14600
-164
lines changed

10 files changed

+14600
-164
lines changed

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
"immutability-helper": "^3.1.1",
3232
"loadsh": "^0.0.4",
3333
"monaco-editor": "^0.21.2",
34+
"omit.js": "^2.0.2",
3435
"rc-collapse": "^2.0.1",
36+
"rc-textarea": "^0.3.1",
3537
"rc-tree": "^3.10.0",
38+
"rc-util": "^5.5.0",
3639
"react": "^16.13.1",
3740
"react-dnd": "^9.3.4",
3841
"react-dnd-html5-backend": "^9.3.4",

src/common/type.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type LiteralUnion<T extends U, U> = T | (U & {});

src/components/input/TextArea.tsx

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as React from 'react';
2+
import RcTextArea, { TextAreaProps as RcTextAreaProps } from 'rc-textarea';
3+
import { useEffect, useRef } from 'react';
4+
5+
import { classNames, prefixClaName } from 'mo/common/className';
6+
import useMergedState from 'rc-util/lib/hooks/useMergedState';
7+
import { fixControlledValue, resolveOnChange } from './input';
8+
9+
export interface TextAreaProps extends RcTextAreaProps {
10+
bordered?: boolean;
11+
showCount?: boolean;
12+
maxLength?: number;
13+
onChange?: (e) => void;
14+
}
15+
16+
17+
const TextArea = ({ bordered = true, showCount = false, maxLength, className, style , onChange, ...props }: TextAreaProps) => {
18+
const innerRef = useRef(null);
19+
20+
const [value, setValue] = useMergedState(props.defaultValue, {
21+
value: props.value,
22+
});
23+
24+
const prevValue = useRef(props.value);
25+
26+
useEffect(() => {
27+
if (props.value !== undefined || prevValue.current !== props.value) {
28+
setValue(props.value);
29+
prevValue.current = props.value;
30+
}
31+
}, [props.value, prevValue.current]);
32+
33+
const handleSetValue = (val: string, callback?: () => void) => {
34+
if (props.value === undefined) {
35+
setValue(val);
36+
callback?.();
37+
}
38+
};
39+
40+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
41+
handleSetValue(e.target.value);
42+
resolveOnChange(innerRef.current!, e, onChange);
43+
};
44+
45+
const prefixCls = prefixClaName('mo-input');
46+
47+
const textArea = (
48+
<RcTextArea
49+
{...props}
50+
maxLength={maxLength}
51+
className={classNames({
52+
[`${prefixCls}-textarea--borderless`]: !bordered,
53+
[className!]: className && !showCount,
54+
})}
55+
style={showCount ? {} : style}
56+
prefixCls={prefixCls}
57+
onChange={handleChange}
58+
ref={innerRef}
59+
/>
60+
);
61+
62+
let val = fixControlledValue(value) as string;
63+
// Max length value
64+
const hasMaxLength = Number(maxLength) > 0;
65+
val = hasMaxLength ? [...val].slice(0, maxLength).join('') : val;
66+
// Only show text area wrapper when needed
67+
if (showCount) {
68+
const valueLength = [...val].length;
69+
const dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
70+
71+
return (
72+
<div
73+
className={classNames(
74+
`${prefixCls}-textarea`,
75+
`${prefixCls}-textarea--show-count`,
76+
className,
77+
)}
78+
style={style}
79+
data-count={dataCount}
80+
>
81+
{textArea}
82+
</div>
83+
);
84+
}
85+
return textArea;
86+
}
87+
88+
export default TextArea;

src/components/input/index.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Input from './input';
2+
import TextArea from './TextArea';
3+
4+
export { InputProps } from './input';
5+
export { TextAreaProps } from './TextArea';
6+
7+
Input.TextArea = TextArea;
8+
export default Input;

src/components/input/input.tsx

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import './style.scss';
2+
import * as React from 'react';
3+
import { classNames, prefixClaName } from 'mo/common/className';
4+
5+
import TextArea from './TextArea';
6+
import { LiteralUnion } from 'mo/common/type';
7+
8+
type SizeType = 'normal' | 'large'
9+
export interface InputProps {
10+
disabled?: boolean;
11+
size?: SizeType;
12+
type?: LiteralUnion<
13+
| 'button'
14+
| 'checkbox'
15+
| 'search'
16+
| 'submit'
17+
| 'text',
18+
string
19+
>;
20+
placeholder?: string;
21+
value?: string;
22+
defaultValue?: string;
23+
inputClassName?: string;
24+
readonly onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
25+
readonly onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
26+
readonly onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;
27+
readonly onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
28+
readonly onChange?: (e: any) => void;
29+
}
30+
31+
export function fixControlledValue<T>(value: T) {
32+
if (typeof value === 'undefined' || value === null) return '';
33+
return value;
34+
}
35+
36+
export function resolveOnChange(
37+
target: HTMLInputElement | HTMLTextAreaElement,
38+
e:
39+
| React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
40+
| React.MouseEvent<HTMLElement, MouseEvent>,
41+
onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void,
42+
) {
43+
if (onChange) {
44+
let event = e;
45+
if (e.type === 'click') {
46+
// click clear icon
47+
event = Object.create(e);
48+
event.target = target;
49+
event.currentTarget = target;
50+
const originalInputValue = target.value;
51+
// change target ref value cause e.target.value should be '' when clear input
52+
target.value = '';
53+
onChange(event as React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>);
54+
// reset target ref value
55+
target.value = originalInputValue;
56+
return;
57+
}
58+
onChange(event as React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>);
59+
}
60+
}
61+
62+
export function getInputClassName(
63+
prefixCls: string,
64+
size?: SizeType,
65+
disabled?: boolean,
66+
) {
67+
return classNames(prefixCls, {
68+
[`${prefixCls}--normal`]: size === 'normal',
69+
[`${prefixCls}--lg`]: size === 'large',
70+
[`${prefixCls}--disabled`]: disabled,
71+
});
72+
}
73+
74+
export interface InputState {
75+
value: any;
76+
prevValue: any;
77+
}
78+
79+
class Input extends React.Component<InputProps, InputState> {
80+
81+
// static Search: typeof Search;
82+
static TextArea: typeof TextArea;
83+
84+
static defaultProps = {
85+
type: 'text',
86+
};
87+
88+
input: any;
89+
90+
constructor(props: InputProps) {
91+
super(props);
92+
const value = typeof props.value === 'undefined' ? props.defaultValue : props.value;
93+
this.state = {
94+
value,
95+
prevValue: props.value,
96+
};
97+
}
98+
99+
static getDerivedStateFromProps(nextProps: InputProps, { prevValue }: InputState) {
100+
const newState: Partial<InputState> = { prevValue: nextProps.value };
101+
if (nextProps.value !== undefined || prevValue !== nextProps.value) {
102+
newState.value = nextProps.value;
103+
}
104+
return newState;
105+
}
106+
107+
saveInput = (input: HTMLInputElement) => {
108+
this.input = input;
109+
};
110+
111+
setValue(value: string, callback?: () => void) {
112+
if (this.props.value === undefined) {
113+
this.setState({ value }, callback);
114+
} else {
115+
callback?.();
116+
}
117+
}
118+
119+
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
120+
this.setValue(e.target.value);
121+
resolveOnChange(this.input, e, this.props.onChange);
122+
};
123+
124+
handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
125+
const { onPressEnter, onKeyDown } = this.props;
126+
if (e.keyCode === 13 && onPressEnter) {
127+
onPressEnter(e);
128+
}
129+
onKeyDown?.(e);
130+
};
131+
132+
render() {
133+
const { inputClassName, size, disabled = false, onFocus, onBlur } = this.props
134+
return (
135+
<input
136+
onChange={this.handleChange}
137+
onFocus={e => onFocus?.(e)}
138+
onBlur={e => onBlur?.(e)}
139+
onKeyDown={this.handleKeyDown}
140+
className={classNames(
141+
inputClassName,
142+
getInputClassName('input', size, disabled),
143+
)}
144+
ref={this.saveInput}
145+
/>
146+
);
147+
}
148+
}
149+
150+
export default Input;
File renamed without changes.

0 commit comments

Comments
 (0)