diff --git a/src/input/Input.tsx b/src/input/Input.tsx index ba71a22d..b18c157a 100644 --- a/src/input/Input.tsx +++ b/src/input/Input.tsx @@ -1,172 +1,224 @@ -import React, { FC, forwardRef, useRef } from 'react'; -import { CloseCircleFilledIcon } from 'tdesign-icons-react'; -import isFunction from 'lodash/isFunction'; +import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import type { FocusEvent, TouchEvent, CompositionEvent, FormEvent } from 'react'; import classNames from 'classnames'; +import { CloseCircleFilledIcon, BrowseOffIcon, BrowseIcon } from 'tdesign-icons-react'; +import useDefault from 'tdesign-mobile-react/_util/useDefault'; +import parseTNode from 'tdesign-mobile-react/_util/parseTNode'; +import { inputDefaultProps } from './defaultProps'; import { getCharacterLength } from '../_common/js/utils/helper'; -import { TdInputProps } from './type'; import useConfig from '../_util/useConfig'; +import useDefaultProps from '../hooks/useDefaultProps'; +import { TdInputProps } from './type'; +import withNativeProps, { NativeProps } from '../_util/withNativeProps'; -export interface InputProps extends TdInputProps { +export interface InputProps extends TdInputProps, NativeProps { required?: boolean; readonly?: boolean; } -const Input: FC = forwardRef((props, ref) => { +export interface InputRefProps { + focus?: () => void; + blur?: () => void; +} + +const Input = forwardRef((props, ref) => { const { - align = 'left', - autofocus = false, - clearable = false, - disabled = false, - errorMessage = '', - label = '', - maxcharacter = 0, // 半成品 - maxlength = 0, - vertical = false, - name = '', - placeholder = '', + align, + autofocus, + autocomplete, + borderless, + clearable, + clearTrigger, + disabled, + label, + layout, + maxlength, + name, + placeholder, prefixIcon, - // size = 'small', suffix, suffixIcon, - type = 'text', - value = '', - className = '', - defaultValue, - required = false, - readonly = false, + tips, + type, + readonly, onBlur, - onChange, onClear, - onEnter, onFocus, - } = props; + value, + defaultValue, + onChange, + } = useDefaultProps(props, inputDefaultProps); + const [showClear, setShowClear] = useState(false); + const [innerValue, setInnerValue] = useDefault(value, defaultValue, onChange); + const [renderType, setRenderType] = useState(type); + const inputRef = useRef(null); + const focused = useRef(false); + const status = props.status || 'default'; const { classPrefix } = useConfig(); - const prefix = classPrefix; + const rootClassName = `${classPrefix}-input`; + const inputClasses = classNames(`${rootClassName}__control`, { + [`${rootClassName}--${align}`]: align !== 'left', + [`${rootClassName}--${status}`]: status, + [`${rootClassName}__control--disabled`]: disabled, + }); + const rootClasses = classNames(`${rootClassName}`, { + [`${rootClassName}--layout-${layout}`]: layout, + [`${rootClassName}--border`]: borderless, + }); + const resultMaxLength = !isNaN(+maxlength) ? +maxlength : -1; - const compositionRef = useRef(false); + useImperativeHandle(ref, () => ({ + focus, + blur, + })); - function handleChange(e: React.ChangeEvent | React.CompositionEvent) { - let { value } = e.currentTarget; - if (maxcharacter !== 0 && !compositionRef.current) { - const res = getCharacterLength(value, maxcharacter) as { - length: number; - characters: string; - }; - value = res.characters; + useEffect(() => { + const computeShowClear = () => { + if (disabled || readonly) { + return false; + } + if (clearable) { + return clearTrigger === 'always' || (clearTrigger === 'focus' && focused.current); + } + return false; + }; + setShowClear(computeShowClear()); + }, [clearTrigger, clearable, disabled, readonly]); + + useEffect(() => { + if (autofocus) { + focus(); } - isFunction(onChange) && !readonly && onChange(value, { e }); + }, [autofocus]); + + useEffect(() => { + setRenderType(type); + }, [type]); + + function focus() { + focused.current = true; + inputRef.current?.focus(); } - function handleBlur(e: React.FocusEvent) { - const { value } = e.currentTarget; - isFunction(onBlur) && onBlur(value, { e }); + function blur() { + focused.current = false; + inputRef.current?.blur(); } - function handleEnter(e: React.KeyboardEvent) { - if (e.keyCode === 13 || e.key === 'Enter' || e.charCode === 13 || e.which === 13) { - const { value } = e.currentTarget; - isFunction(onEnter) && onEnter(value, { e }); + const inputValueChangeHandle = (e: FormEvent) => { + const { value } = e.target as HTMLInputElement; + const { allowInputOverMax, maxcharacter } = props; + if (!allowInputOverMax && maxcharacter && !Number.isNaN(maxcharacter)) { + const { characters } = getCharacterLength(value, maxcharacter) as { + length: number; + characters: string; + }; + setInnerValue(characters); + } else { + setInnerValue(value); } - } + }; - function handleFocus(e: React.FocusEvent) { - const { value } = e.currentTarget; - isFunction(onFocus) && onFocus(value, { e }); - } + const handleInput = (e: FormEvent) => { + // 中文输入的时候inputType是insertCompositionText所以中文输入的时候禁止触发。 + if (e instanceof InputEvent) { + const checkInputType = e.inputType && e.inputType === 'insertCompositionText'; + if (e.isComposing || checkInputType) return; + } + inputValueChangeHandle(e); + }; - function handleClear(e: React.MouseEvent) { - isFunction(onChange) && !readonly && onChange(''); - isFunction(onClear) && onClear({ e }); - } + const handleClear = (e: TouchEvent) => { + e.preventDefault(); + setInnerValue(''); + focus(); + onClear?.({ e: e as TouchEvent }); + }; - const inputProps: any = {}; - if (maxlength > 0) { - inputProps.maxLength = maxlength; - } - if (defaultValue !== undefined) { - inputProps.defaultValue = defaultValue; - } - if (isFunction(onBlur)) { - inputProps.onBlur = handleBlur; - } - if (isFunction(onEnter)) { - inputProps.onKeyPress = handleEnter; - } - if (isFunction(onFocus)) { - inputProps.onFocus = handleFocus; - } + const handleFocus = (e: FocusEvent) => { + focused.current = true; + onFocus?.(innerValue, { e: e as FocusEvent }); + }; - return ( -
-
-
- {prefixIcon ? prefixIcon : <>} -
- {label && ( -
-
{label}
- {required &&  *} -
- )} + const handleBlur = (e: FocusEvent) => { + focused.current = false; + onBlur?.(innerValue, { e: e as FocusEvent }); + }; + + const handleCompositionend = (e: CompositionEvent) => { + inputValueChangeHandle(e as CompositionEvent); + }; + + const handlePwdIconClick = () => { + if (disabled) { + return; + } + setRenderType((renderType) => (renderType === 'password' ? 'text' : 'password')); + }; + + const renderPrefix = () => ( +
+ {prefixIcon ?
: null} +
{parseTNode(label)}
+
+ ); + + const renderClearable = () => + showClear ? ( +
+
-
-
-
- { - compositionRef.current = true; - }} - onCompositionEnd={(e) => { - compositionRef.current = false; - handleChange(e); - }} - ref={ref} - {...inputProps} - /> - {clearable && ( -
- -
- )} - {suffix &&
{suffix}
} -
- {errorMessage &&
{errorMessage}
} -
-
- {suffixIcon ? suffixIcon : <>} + ) : null; + + const renderSuffix = () => + suffix ?
{parseTNode(suffix)}
: null; + + const renderSuffixIcon = () => { + let tempSuffixIcon = suffixIcon; + if (type === 'password') { + if (renderType === 'password') { + tempSuffixIcon = ; + } else if (renderType === 'text') { + tempSuffixIcon = ; + } + } + return suffixIcon ?
{tempSuffixIcon}
: null; + }; + + const renderTips = () => + tips ?
{parseTNode(tips)}
: null; + + return withNativeProps( + props, +
+ {renderPrefix()} +
+
+ + {renderClearable()} + {renderSuffix()} + {renderSuffixIcon()}
+ {renderTips()}
-
+
, ); }); diff --git a/src/input/_example/align.jsx b/src/input/_example/align.jsx deleted file mode 100644 index d6208344..00000000 --- a/src/input/_example/align.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useState } from 'react'; -import { Input, CellGroup } from 'tdesign-mobile-react'; - -export default function Base() { - const [value1, setValue1] = useState(''); - const [value2, setValue2] = useState(''); - const [value3, setValue3] = useState(''); - - return ( - <> - - { - setValue1(value); - }} - /> - - - { - setValue2(value); - }} - /> - - - { - setValue3(value); - }} - /> - - - ); -} diff --git a/src/input/_example/align.tsx b/src/input/_example/align.tsx new file mode 100644 index 00000000..fbbff593 --- /dev/null +++ b/src/input/_example/align.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; + +export default function Align() { + return ( + <> + + + + + ); +} diff --git a/src/input/_example/banner.tsx b/src/input/_example/banner.tsx new file mode 100644 index 00000000..fc35d8dd --- /dev/null +++ b/src/input/_example/banner.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; +import './style/index.less'; + +export default function Banner() { + return ; +} diff --git a/src/input/_example/base.tsx b/src/input/_example/base.tsx new file mode 100644 index 00000000..5ed86881 --- /dev/null +++ b/src/input/_example/base.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; + +export default function Base() { + return ( + <> + + + + + ); +} diff --git a/src/input/_example/bordered.tsx b/src/input/_example/bordered.tsx new file mode 100644 index 00000000..b689d5fc --- /dev/null +++ b/src/input/_example/bordered.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; +import { ErrorCircleFilledIcon } from 'tdesign-icons-react'; +import './style/index.less'; + +export default function Bordered() { + return ( +
+
标签文字
+ + + +
+ ); +} diff --git a/src/input/_example/custom.tsx b/src/input/_example/custom.tsx new file mode 100644 index 00000000..589904f5 --- /dev/null +++ b/src/input/_example/custom.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; +import './style/index.less'; + +export default function Custom() { + return ( +
+ +
+ ); +} diff --git a/src/input/_example/index.jsx b/src/input/_example/index.jsx index 77b41b62..b5544d64 100644 --- a/src/input/_example/index.jsx +++ b/src/input/_example/index.jsx @@ -1,10 +1,16 @@ import React from 'react'; -import TypeDemo from './type'; -import StatusDemo from './status'; -import SpecialDemo from './special'; -import SizeDemo from './size'; -import AlignDemo from './align'; -import LimitDemo from './limit'; +import BaseDemo from './base'; +import MaxLength from './maxLength'; +import Suffix from './suffix'; +import Prefix from './prefix'; +import Special from './special'; +import Status from './status'; +import LabelDemo from './label'; +import Align from './align'; +import Layout from './layout'; +import Banner from './banner'; +import Bordered from './bordered'; +import Custom from './custom'; import TDemoHeader from '../../../site/mobile/components/DemoHeader'; import TDemoBlock from '../../../site/mobile/components/DemoBlock'; import './style/index.less'; @@ -12,30 +18,45 @@ import './style/index.less'; export default function RadioDemo() { return (
- + - - + + - - - + + - - - + + - - - + + + + + - - + + + + + - - + + + + + + + + + + + + + +
); diff --git a/src/input/_example/label.tsx b/src/input/_example/label.tsx new file mode 100644 index 00000000..d8ab9134 --- /dev/null +++ b/src/input/_example/label.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; + +export default function Label() { + return ; +} diff --git a/src/input/_example/layout.tsx b/src/input/_example/layout.tsx new file mode 100644 index 00000000..7adc5f96 --- /dev/null +++ b/src/input/_example/layout.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; +import { ErrorCircleFilledIcon } from 'tdesign-icons-react'; + +export default function Layout() { + return ( + + + + ); +} diff --git a/src/input/_example/limit.jsx b/src/input/_example/limit.jsx deleted file mode 100644 index 4c8d1eba..00000000 --- a/src/input/_example/limit.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useState } from 'react'; -import { Input, CellGroup } from 'tdesign-mobile-react'; - -export default function Base() { - const [value1, setValue1] = useState(''); - const [value2, setValue2] = useState(''); - - return ( - <> - - { - setValue1(value); - }} - /> - - - { - setValue2(value); - }} - /> - - - ); -} diff --git a/src/input/_example/maxLength.tsx b/src/input/_example/maxLength.tsx new file mode 100644 index 00000000..ba412d54 --- /dev/null +++ b/src/input/_example/maxLength.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; + +export default function MaxLength() { + return ( + <> + + + + ); +} diff --git a/src/input/_example/prefix.tsx b/src/input/_example/prefix.tsx new file mode 100644 index 00000000..d0c6ff57 --- /dev/null +++ b/src/input/_example/prefix.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; +import { AppIcon } from 'tdesign-icons-react'; + +export default function Prefix() { + return ( + <> + } /> + } /> + + ); +} diff --git a/src/input/_example/size.jsx b/src/input/_example/size.jsx deleted file mode 100644 index 7cc66279..00000000 --- a/src/input/_example/size.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useState } from 'react'; -import { Input, CellGroup } from 'tdesign-mobile-react'; - -export default function Base() { - const [value1, setValue1] = useState(''); - const [value2, setValue2] = useState(''); - - return ( - <> - - { - setValue1(value); - }} - /> - - - { - setValue2(value); - }} - /> - - - ); -} diff --git a/src/input/_example/special.jsx b/src/input/_example/special.jsx deleted file mode 100644 index d5bd4af2..00000000 --- a/src/input/_example/special.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState } from 'react'; -import { Input, Button, CellGroup } from 'tdesign-mobile-react'; - -export default function Base() { - const [value1, setValue1] = useState('12132131'); - const [value2, setValue2] = useState(''); - const [value3, setValue3] = useState(''); - const [value4, setValue4] = useState(''); - const [value5, setValue5] = useState(''); - - return ( - <> - - { - setValue1(value); - }} - /> - - - { - setValue2(value); - }} - /> - - - - 发送验证码 - - } - value={value3} - onChange={(value) => { - setValue3(value); - }} - /> - - - { - setValue4(value); - }} - className="t-input-suffix-noseparate" - /> - - - { - setValue5(value); - }} - className="t-input-suffix-noseparate" - /> - - - ); -} diff --git a/src/input/_example/special.tsx b/src/input/_example/special.tsx new file mode 100644 index 00000000..8cde7654 --- /dev/null +++ b/src/input/_example/special.tsx @@ -0,0 +1,66 @@ +import React, { useEffect, useState } from 'react'; +import { Image, Input } from 'tdesign-mobile-react'; +import { BrowseOffIcon } from 'tdesign-icons-react'; +import './style/index.less'; + +export default function Special() { + const [phoneNumber, setPhoneNumber] = useState('17600600600'); + const [tips, setTips] = useState(''); + + useEffect(() => { + function isPhoneNumber() { + if (/^[1][3,4,5,7,8,9][0-9]{9}$/.test(phoneNumber)) { + return ''; + } + return 'error'; + } + + setTips(!isPhoneNumber() ? '手机号输入不正确' : ''); + }, [phoneNumber]); + + const handlePhoneNumberChange = (value) => { + setPhoneNumber(value); + }; + + return ( + <> + } + /> + +
+ +
+ } + /> + +
+
发送验证码
+
+ } + /> + + + + ); +} diff --git a/src/input/_example/status.jsx b/src/input/_example/status.jsx deleted file mode 100644 index e065990c..00000000 --- a/src/input/_example/status.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useState } from 'react'; -import { Input, CellGroup } from 'tdesign-mobile-react'; - -export default function Base() { - const [value1, setValue1] = useState('请输入文字'); - const [value2, setValue2] = useState('一段错误填写的内容'); - const [value3, setValue3] = useState('不可编辑的内容'); - - return ( - - { - setValue1(value); - }} - /> - { - setValue2(value); - }} - /> - { - setValue3(value); - }} - /> - - ); -} diff --git a/src/input/_example/status.tsx b/src/input/_example/status.tsx new file mode 100644 index 00000000..274388df --- /dev/null +++ b/src/input/_example/status.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Input } from 'tdesign-mobile-react'; + +export default function Status() { + const text1 = '已输入文字'; + const text2 = '不可编辑文字'; + return ( + <> + + + + ); +} diff --git a/src/input/_example/style/index.less b/src/input/_example/style/index.less index d46b77e2..d353f61e 100644 --- a/src/input/_example/style/index.less +++ b/src/input/_example/style/index.less @@ -1,22 +1,62 @@ -.t-input-suffix-noseparate { - .t-input__wrap--suffix { - &::after { - border: none; - } - } +.t-input + .t-input { + margin-top: 16px; } -.t-cell-group + .t-cell-group { - margin-top: 16px; +.input-demo { + border-radius: 6px; + margin: 0 16px; } -.demo-group { - .t-cell-group + .t-cell-group { - margin-top: 0; - } +.input-bordered { + background-color: var(--bg-color-demo, #fff); + padding: 16px 16px 24px; + + --td-input-vertical-padding: 12px; - .t-cell-group__title { + &__summary { + color: var(--td-text-color-primary, rgba(0, 0, 0, 0.9)); font-size: 12px; line-height: 20px; + margin-bottom: 8px; + } + + &__input { + border: 1px solid rgba(220, 220, 220, 1); + border-radius: 6px; + } +} + +.input-custom { + padding-bottom: 48rpx; + + --td-input-bg-color: rgba(44, 44, 44, 1); + --td-input-border-color: rgba(75, 75, 75, 1); + --td-input-default-text-color: rgba(255, 255, 255, 1); + --td-input-placeholder-text-color: rgba(255, 255, 255, 0.35); + --td-input-label-text-color: rgba(255, 255, 255, 1); +} + +.input-suffix { + display: flex; + align-items: center; + + &__line { + width: 1px; + height: 24px; + background-color: #f6f6f6; + margin-right: 16px; + } + + &__image { + width: 72px; + height: 36px; + display: block; + margin-top: -6px; + margin-bottom: -6px; + } + + &__verify { + color: rgba(0, 82, 217, 1); + font-size: 16px; } } diff --git a/src/input/_example/suffix.tsx b/src/input/_example/suffix.tsx new file mode 100644 index 00000000..d3c0dd05 --- /dev/null +++ b/src/input/_example/suffix.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Button, Input } from 'tdesign-mobile-react'; +import { InfoCircleFilledIcon, UserAvatarIcon } from 'tdesign-icons-react'; + +export default function Suffix() { + return ( + <> + } /> + + {' 操作按钮 '} + + } + /> + } /> + + ); +} diff --git a/src/input/_example/type.jsx b/src/input/_example/type.jsx deleted file mode 100644 index a64c0930..00000000 --- a/src/input/_example/type.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useState } from 'react'; -import { InfoCircleFilledIcon } from 'tdesign-icons-react'; -import { Input, CellGroup } from 'tdesign-mobile-react'; - -export default function Base() { - const [value1, setValue1] = useState(''); - const [value2, setValue2] = useState(''); - const [value3, setValue3] = useState(''); - const [value4, setValue4] = useState(''); - const [value5, setValue5] = useState(''); - const [value6, setValue6] = useState(''); - - return ( -
- - { - setValue1(value); - }} - /> - - - - { - setValue2(value); - }} - /> - { - setValue3(value); - }} - /> - - - - { - setValue4(value); - }} - /> - - - - } - value={value5} - onChange={(value) => { - setValue5(value); - }} - /> - - - - { - setValue6(value); - }} - /> - -
- ); -} diff --git a/src/input/defaultProps.ts b/src/input/defaultProps.ts new file mode 100644 index 00000000..cde7ff7b --- /dev/null +++ b/src/input/defaultProps.ts @@ -0,0 +1,21 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdInputProps } from './type'; + +export const inputDefaultProps: TdInputProps = { + align: 'left', + allowInputOverMax: false, + autocomplete: undefined, + autofocus: false, + borderless: false, + clearTrigger: 'always', + clearable: false, + disabled: undefined, + layout: 'horizontal', + placeholder: undefined, + readonly: undefined, + status: undefined, + type: 'text', +}; diff --git a/src/input/input.en-US.md b/src/input/input.en-US.md new file mode 100644 index 00000000..b17e7a32 --- /dev/null +++ b/src/input/input.en-US.md @@ -0,0 +1,41 @@ +:: BASE_DOC :: + +## API + +### Input Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N +align | String | left | text align type。options: left/center/right | N +allowInputOverMax | Boolean | false | allow to continue input on value length is over `maxlength` or `maxcharacter` | N +autocomplete | String | undefined | attribute of input element, [see here](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) | N +autofocus | Boolean | false | autofocus on first rendered | N +borderless | Boolean | false | input without border | N +clearTrigger | String | always | show clear icon, clicked to clear input value。options: always / focus | N +clearable | Boolean | false | show clear icon, clicked to clear input value | N +disabled | Boolean | undefined | make input to be disabled | N +errorMessage | String | - | `deprecated` | N +format | Function | - | input value formatter, `type=number` does not work. if you need to format number, `InputNumber` Component might be better。Typescript:`InputFormatType` `type InputFormatType = (value: InputValue) => string`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N +label | TNode | - | text on the left of input。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +layout | String | horizontal | options: vertical/horizontal | N +maxcharacter | Number | - | \- | N +maxlength | String / Number | - | \- | N +name | String | - | \- | N +placeholder | String | undefined | \- | N +prefixIcon | TElement | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +readonly | Boolean | undefined | \- | N +size | String | medium | `deprecated`。options: small/medium。Typescript:`'medium' \| 'small'` | N +status | String | undefined | options: default/success/warning/error | N +suffix | TNode | - | suffix content before suffixIcon。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +suffixIcon | TElement | - | suffix icon of input。Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +tips | TNode | - | tips on the bottom of input, different `status` can make tips to be different color。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +type | String | text | type attribute of input element. if you are using `type=number`, `InputNumber` Component might be better。options: text/number/url/tel/password/search/submit/hidden | N +value | String / Number | - | input value。Typescript:`InputValue` `type InputValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N +defaultValue | String / Number | - | input value。uncontrolled property。Typescript:`InputValue` `type InputValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N +onBlur | Function | | Typescript:`(value: InputValue, context: { e: FocusEvent }) => void`
| N +onChange | Function | | Typescript:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' }) => void`
trigger on input value changed | N +onClear | Function | | Typescript:`(context: { e: TouchEvent }) => void`
| N +onFocus | Function | | Typescript:`(value: InputValue, context: { e: FocusEvent }) => void`
| N +onValidate | Function | | Typescript:`(context: { error?: 'exceed-maximum' \| 'below-minimum' }) => void`
trigger on text length being over max length or max character | N diff --git a/src/input/input.md b/src/input/input.md index ce4a5a8e..8d54c489 100644 --- a/src/input/input.md +++ b/src/input/input.md @@ -4,29 +4,38 @@ ### Input Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N align | String | left | 文本内容位置,居左/居中/居右。可选项:left/center/right | N +allowInputOverMax | Boolean | false | 超出 `maxlength` 或 `maxcharacter` 之后是否允许继续输入 | N +autocomplete | String | undefined | 是否开启自动填充功能,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) | N autofocus | Boolean | false | 自动聚焦 | N +borderless | Boolean | false | 是否开启无边框模式 | N +clearTrigger | String | always | 清空图标触发方式,仅在输入框有值时有效。可选项:always / focus | N clearable | Boolean | false | 是否可清空 | N -disabled | Boolean | false | 是否禁用输入框 | N -errorMessage | String | - | 错误提示文本,值为空不显示 | N -label | TNode | - | 左侧文本。TS 类型:`string | TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +disabled | Boolean | undefined | 是否禁用输入框 | N +errorMessage | String | - | 已废弃。错误提示文本,值为空不显示(废弃属性,如果需要,请更为使用 status 和 tips) | N +format | Function | - | 【开发中】指定输入框展示值的格式。TS 类型:`InputFormatType` `type InputFormatType = (value: InputValue) => string`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N +label | TNode | - | 左侧文本。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +layout | String | horizontal | 标题输入框布局方式。可选项:vertical/horizontal | N maxcharacter | Number | - | 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度。`maxcharacter` 和 `maxlength` 二选一使用 | N -maxlength | Number | - | 用户最多可以输入的文本长度。值小于等于 0 的时候,则不限制输入长度。`maxcharacter` 和 `maxlength` 二选一使用 | N +maxlength | String / Number | - | 用户最多可以输入的文本长度,一个中文等于一个计数长度。默认为空,不限制输入长度。`maxcharacter` 和 `maxlength` 二选一使用 | N name | String | - | 名称 | N placeholder | String | undefined | 占位符 | N prefixIcon | TElement | - | 组件前置图标。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N -size | String | small | 输入框尺寸。可选项:small/medium。TS 类型:`'medium' | 'small'` | N -suffix | TNode | - | 后置图标前的后置内容。TS 类型:`string | TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +readonly | Boolean | undefined | 只读状态 | N +size | String | medium | 已废弃。输入框尺寸。可选项:small/medium。TS 类型:`'medium' \| 'small'` | N +status | String | undefined | 输入框状态。默认情况会由组件内部根据实际情况呈现,如果文本过长引起的状态变化。可选项:default/success/warning/error | N +suffix | TNode | - | 后置图标前的后置内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N suffixIcon | TElement | - | 组件后置图标。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +tips | TNode | - | 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N type | String | text | 输入框类型。可选项:text/number/url/tel/password/search/submit/hidden | N -value | String / Number | - | 输入框的值。TS 类型:`InputValue`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N -defaultValue | String / Number | - | 输入框的值。非受控属性。TS 类型:`InputValue`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N +value | String / Number | - | 输入框的值。TS 类型:`InputValue` `type InputValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N +defaultValue | String / Number | - | 输入框的值。非受控属性。TS 类型:`InputValue` `type InputValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N onBlur | Function | | TS 类型:`(value: InputValue, context: { e: FocusEvent }) => void`
失去焦点时触发 | N -onChange | Function | | TS 类型:`(value: InputValue, context?: { e?: InputEvent | MouseEvent }) => void`
输入框值发生变化时触发 | N -onClear | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
清空按钮点击时触发 | N -onEnter | Function | | TS 类型:`(value: InputValue, context: { e: KeyboardEvent }) => void`
回车键按下时触发 | N +onChange | Function | | TS 类型:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' }) => void`
输入框值发生变化时触发。`trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制 | N +onClear | Function | | TS 类型:`(context: { e: TouchEvent }) => void`
清空按钮点击时触发 | N onFocus | Function | | TS 类型:`(value: InputValue, context: { e: FocusEvent }) => void`
获得焦点时触发 | N +onValidate | Function | | TS 类型:`(context: { error?: 'exceed-maximum' \| 'below-minimum' }) => void`
字数超出限制时触发 | N diff --git a/src/input/style/index.js b/src/input/style/index.js index 941dda0a..1b672ef0 100644 --- a/src/input/style/index.js +++ b/src/input/style/index.js @@ -1 +1 @@ -import '../../_common/style/mobile/components/input/_index.less'; +import '../../_common/style/mobile/components/input/v2/_index.less'; diff --git a/src/input/type.ts b/src/input/type.ts index 6df26046..fd81e396 100644 --- a/src/input/type.ts +++ b/src/input/type.ts @@ -5,7 +5,7 @@ * */ import { TNode, TElement } from '../common'; -import { MouseEvent, KeyboardEvent, FocusEvent, FormEvent } from 'react'; +import { MouseEvent, FocusEvent, FormEvent, CompositionEvent, TouchEvent } from 'react'; export interface TdInputProps { /** @@ -13,11 +13,30 @@ export interface TdInputProps { * @default left */ align?: 'left' | 'center' | 'right'; + /** + * 超出 `maxlength` 或 `maxcharacter` 之后是否允许继续输入 + * @default false + */ + allowInputOverMax?: boolean; + /** + * 是否开启自动填充功能,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) + */ + autocomplete?: string; /** * 自动聚焦 * @default false */ autofocus?: boolean; + /** + * 是否开启无边框模式 + * @default false + */ + borderless?: boolean; + /** + * 清空图标触发方式,仅在输入框有值时有效 + * @default always + */ + clearTrigger?: 'always' | 'focus'; /** * 是否可清空 * @default false @@ -25,26 +44,29 @@ export interface TdInputProps { clearable?: boolean; /** * 是否禁用输入框 - * @default false */ disabled?: boolean; /** - * 错误提示文本,值为空不显示 - * @default '' + * 【开发中】指定输入框展示值的格式 */ - errorMessage?: string; + format?: InputFormatType; /** * 左侧文本 */ label?: TNode; + /** + * 标题输入框布局方式 + * @default horizontal + */ + layout?: 'vertical' | 'horizontal'; /** * 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度。`maxcharacter` 和 `maxlength` 二选一使用 */ maxcharacter?: number; /** - * 用户最多可以输入的文本长度。值小于等于 0 的时候,则不限制输入长度。`maxcharacter` 和 `maxlength` 二选一使用 + * 用户最多可以输入的文本长度,一个中文等于一个计数长度。默认为空,不限制输入长度。`maxcharacter` 和 `maxlength` 二选一使用 */ - maxlength?: number; + maxlength?: string | number; /** * 名称 * @default '' @@ -59,10 +81,13 @@ export interface TdInputProps { */ prefixIcon?: TElement; /** - * 输入框尺寸 - * @default small + * 只读状态 */ - size?: 'medium' | 'small'; + readonly?: boolean; + /** + * 输入框状态。默认情况会由组件内部根据实际情况呈现,如果文本过长引起的状态变化 + */ + status?: 'default' | 'success' | 'warning' | 'error'; /** * 后置图标前的后置内容 */ @@ -71,17 +96,15 @@ export interface TdInputProps { * 组件后置图标 */ suffixIcon?: TElement; + /** + * 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式 + */ + tips?: TNode; /** * 输入框类型 * @default text */ type?: 'text' | 'number' | 'url' | 'tel' | 'password' | 'search' | 'submit' | 'hidden'; - /** - * 是否垂直显示 - * @default false - */ - vertical?: boolean; - /** * 输入框的值 */ @@ -90,34 +113,34 @@ export interface TdInputProps { * 输入框的值,非受控属性 */ defaultValue?: InputValue; - /** - * 传入的class - * @default "" - */ - className?: string; /** * 失去焦点时触发 */ onBlur?: (value: InputValue, context: { e: FocusEvent }) => void; /** - * 输入框值发生变化时触发 + * 输入框值发生变化时触发。`trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制 */ onChange?: ( value: InputValue, - context?: { e?: FormEvent | MouseEvent }, + context?: { + e?: FormEvent | MouseEvent | CompositionEvent; + trigger: 'input' | 'initial' | 'clear'; + }, ) => void; /** * 清空按钮点击时触发 */ - onClear?: (context: { e: MouseEvent }) => void; - /** - * 回车键按下时触发 - */ - onEnter?: (value: InputValue, context: { e: KeyboardEvent }) => void; + onClear?: (context: { e: TouchEvent }) => void; /** * 获得焦点时触发 */ onFocus?: (value: InputValue, context: { e: FocusEvent }) => void; + /** + * 字数超出限制时触发 + */ + onValidate?: (context: { error?: 'exceed-maximum' | 'below-minimum' }) => void; } +export type InputFormatType = (value: InputValue) => string; + export type InputValue = string | number; diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index e582b2bf..f5eeee7e 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -3439,6 +3439,925 @@ exports[`csr snapshot test > csr test src/icon/_example/single.tsx 1`] = `
`; +exports[`csr snapshot test > csr test src/input/_example/align.tsx 1`] = ` +
+
+
+
+ 左对齐 +
+
+
+
+ +
+
+
+
+
+
+ 居中 +
+
+
+
+ +
+
+
+
+
+
+ 右对齐 +
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/banner.tsx 1`] = ` +
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/base.tsx 1`] = ` +
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/bordered.tsx 1`] = ` +
+
+
+ 标签文字 +
+
+
+
+
+
+
+ +
+
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/custom.tsx 1`] = ` +
+
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/label.tsx 1`] = ` +
+
+
+
+ 标签超长时最多十个字 +
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/layout.tsx 1`] = ` +
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/maxLength.tsx 1`] = ` +
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+ 最大输入10个字符 +
+
+
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+ 最大输入10个字符,汉字算两个 +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/prefix.tsx 1`] = ` +
+
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/special.tsx 1`] = ` +
+
+
+
+ 输入密码 +
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+ 验证码 +
+
+
+
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+
+
+
+
+
+ 手机号 +
+
+
+
+ +
+
+
+
+ 发送验证码 +
+
+
+
+
+ 手机号输入不正确 +
+
+
+
+
+
+ 价格 +
+
+
+
+ +
+ 元 +
+
+
+
+
+
+
+ 数量 +
+
+
+
+ +
+ 个 +
+
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/status.tsx 1`] = ` +
+
+
+
+ 标签文字 +
+
+
+
+ +
+
+ 辅助说明 +
+
+
+
+
+
+ 不可编辑 +
+
+
+
+ +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/input/_example/suffix.tsx 1`] = ` +
+
+
+
+ 标签文字 +
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+ 标签文字 +
+
+
+
+ +
+ +
+
+
+
+
+
+
+ 标签文字 +
+
+
+
+ +
+ + + +
+
+
+
+
+`; + exports[`csr snapshot test > csr test src/loading/_example/attach.tsx 1`] = `
ssr test src/icon/_example/index.tsx 1`] = `"
ssr test src/icon/_example/single.tsx 1`] = `"

"`; +exports[`ssr snapshot test > ssr test src/input/_example/align.tsx 1`] = `"
左对齐
居中
右对齐
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/banner.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/base.tsx 1`] = `"
标签文字
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/bordered.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/custom.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/label.tsx 1`] = `"
标签超长时最多十个字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/layout.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/maxLength.tsx 1`] = `"
标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/prefix.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/special.tsx 1`] = `"
输入密码
验证码
手机号
发送验证码
价格
数量
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/status.tsx 1`] = `"
标签文字
辅助说明
不可编辑
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/suffix.tsx 1`] = `"
标签文字
标签文字
标签文字
"`; + exports[`ssr snapshot test > ssr test src/loading/_example/attach.tsx 1`] = `"
Hello, I\`'m Alice. I\`'m going to be a front-end developer.
"`; exports[`ssr snapshot test > ssr test src/loading/_example/base.tsx 1`] = `"
"`; diff --git a/test/snap/__snapshots__/ssr.test.jsx.snap b/test/snap/__snapshots__/ssr.test.jsx.snap index 19259eb2..505c697a 100644 --- a/test/snap/__snapshots__/ssr.test.jsx.snap +++ b/test/snap/__snapshots__/ssr.test.jsx.snap @@ -32,6 +32,30 @@ exports[`ssr snapshot test > ssr test src/icon/_example/index.tsx 1`] = `"
ssr test src/icon/_example/single.tsx 1`] = `"

"`; +exports[`ssr snapshot test > ssr test src/input/_example/align.tsx 1`] = `"
左对齐
居中
右对齐
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/banner.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/base.tsx 1`] = `"
标签文字
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/bordered.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/custom.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/label.tsx 1`] = `"
标签超长时最多十个字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/layout.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/maxLength.tsx 1`] = `"
标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/prefix.tsx 1`] = `"
标签文字
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/special.tsx 1`] = `"
输入密码
验证码
手机号
发送验证码
价格
数量
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/status.tsx 1`] = `"
标签文字
辅助说明
不可编辑
"`; + +exports[`ssr snapshot test > ssr test src/input/_example/suffix.tsx 1`] = `"
标签文字
标签文字
标签文字
"`; + exports[`ssr snapshot test > ssr test src/loading/_example/attach.tsx 1`] = `"
Hello, I\`'m Alice. I\`'m going to be a front-end developer.
"`; exports[`ssr snapshot test > ssr test src/loading/_example/base.tsx 1`] = `"
"`;