mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 03:59:01 +08:00
feat: Input.OTP support mask prop (#48257)
* feat: Input.OTP support mask prop * fix: fix * fix: fix * test: add test case * test: add test case * chore: fix * chore: update * chore: remove * chore: rename useOTPSingleValue * fix: fix * fix: fix * chore: rm 3 lib * chore: add 3 lib * fix: fix * fix: fix * test: fix test case * test: fix test case * fix: fix * fix: fix --------- Signed-off-by: lijianan <574980606@qq.com>
This commit is contained in:
parent
4621bd9f5e
commit
53cbceb7db
@ -9,10 +9,14 @@ export interface OTPInputProps extends Omit<InputProps, 'onChange'> {
|
||||
onChange: (index: number, value: string) => void;
|
||||
/** Tell parent to do active offset */
|
||||
onActiveChange: (nextIndex: number) => void;
|
||||
|
||||
mask?: boolean | string;
|
||||
}
|
||||
|
||||
const OTPInput = React.forwardRef<InputRef, OTPInputProps>((props, ref) => {
|
||||
const { value, onChange, onActiveChange, index, ...restProps } = props;
|
||||
const { value, onChange, onActiveChange, index, mask, ...restProps } = props;
|
||||
|
||||
const internalValue = value && typeof mask === 'string' ? mask : value;
|
||||
|
||||
const onInternalChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(index, e.target.value);
|
||||
@ -56,13 +60,14 @@ const OTPInput = React.forwardRef<InputRef, OTPInputProps>((props, ref) => {
|
||||
<Input
|
||||
{...restProps}
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
value={internalValue}
|
||||
onInput={onInternalChange}
|
||||
onFocus={syncSelection}
|
||||
onKeyDown={onInternalKeyDown}
|
||||
onKeyUp={onInternalKeyUp}
|
||||
onMouseDown={syncSelection}
|
||||
onMouseUp={syncSelection}
|
||||
type={mask === true ? 'password' : 'text'}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -5,11 +5,13 @@ import pickAttrs from 'rc-util/lib/pickAttrs';
|
||||
|
||||
import { getMergedStatus } from '../../_util/statusUtils';
|
||||
import type { InputStatus } from '../../_util/statusUtils';
|
||||
import { devUseWarning } from '../../_util/warning';
|
||||
import { ConfigContext } from '../../config-provider';
|
||||
import useCSSVarCls from '../../config-provider/hooks/useCSSVarCls';
|
||||
import useSize from '../../config-provider/hooks/useSize';
|
||||
import type { SizeType } from '../../config-provider/SizeContext';
|
||||
import { FormItemInputContext } from '../../form/context';
|
||||
import type { FormItemStatusContextProps } from '../../form/context';
|
||||
import type { Variant } from '../../form/hooks/useVariants';
|
||||
import type { InputRef } from '../Input';
|
||||
import useStyle from '../style/otp';
|
||||
@ -42,6 +44,8 @@ export interface OTPProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'on
|
||||
// Status
|
||||
disabled?: boolean;
|
||||
status?: InputStatus;
|
||||
|
||||
mask?: boolean | string;
|
||||
}
|
||||
|
||||
function strToArr(str: string) {
|
||||
@ -61,9 +65,19 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
disabled,
|
||||
status: customStatus,
|
||||
autoFocus,
|
||||
mask,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Input.OTP');
|
||||
warning(
|
||||
!(typeof mask === 'string' && mask.length > 1),
|
||||
'usage',
|
||||
'`mask` prop should be a single character.',
|
||||
);
|
||||
}
|
||||
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('otp', customizePrefixCls);
|
||||
|
||||
@ -85,7 +99,7 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
const formContext = React.useContext(FormItemInputContext);
|
||||
const mergedStatus = getMergedStatus(formContext.status, customStatus);
|
||||
|
||||
const proxyFormContext = React.useMemo(
|
||||
const proxyFormContext = React.useMemo<FormItemStatusContextProps>(
|
||||
() => ({
|
||||
...formContext,
|
||||
status: mergedStatus,
|
||||
@ -194,10 +208,11 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
};
|
||||
|
||||
// ======================== Render ========================
|
||||
const inputSharedProps = {
|
||||
const inputSharedProps: Partial<OTPInputProps> = {
|
||||
variant,
|
||||
disabled,
|
||||
status: mergedStatus as InputStatus,
|
||||
mask,
|
||||
};
|
||||
|
||||
return wrapCSSVar(
|
||||
@ -216,10 +231,9 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
)}
|
||||
>
|
||||
<FormItemInputContext.Provider value={proxyFormContext}>
|
||||
{new Array(length).fill(0).map((_, index) => {
|
||||
{Array.from({ length }).map((_, index) => {
|
||||
const key = `otp-${index}`;
|
||||
const singleValue = valueCells[index] || '';
|
||||
|
||||
return (
|
||||
<OTPInput
|
||||
ref={(inputEle) => {
|
||||
|
@ -35,7 +35,13 @@ const actionMap: Record<PropertyKey, keyof React.DOMAttributes<HTMLSpanElement>>
|
||||
type IconPropsType = React.HTMLAttributes<HTMLSpanElement> & React.Attributes;
|
||||
|
||||
const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
const { visibilityToggle = true } = props;
|
||||
const {
|
||||
disabled,
|
||||
action = 'click',
|
||||
visibilityToggle = true,
|
||||
iconRender = defaultIconRender,
|
||||
} = props;
|
||||
|
||||
const visibilityControlled =
|
||||
typeof visibilityToggle === 'object' && visibilityToggle.visible !== undefined;
|
||||
const [visible, setVisible] = useState(() =>
|
||||
@ -53,7 +59,6 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
const removePasswordTimeout = useRemovePasswordTimeout(inputRef);
|
||||
|
||||
const onVisibleChange = () => {
|
||||
const { disabled } = props;
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
@ -70,7 +75,6 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
};
|
||||
|
||||
const getIcon = (prefixCls: string) => {
|
||||
const { action = 'click', iconRender = defaultIconRender } = props;
|
||||
const iconTrigger = actionMap[action] || '';
|
||||
const icon = iconRender(visible);
|
||||
const iconProps: IconPropsType = {
|
||||
|
@ -10116,242 +10116,259 @@ exports[`renders components/input/demo/group.tsx extend context correctly 2`] =
|
||||
|
||||
exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With formatter (Upcase)
|
||||
</h5>
|
||||
With formatter (Upcase)
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
With Disabled
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With Disabled
|
||||
</h5>
|
||||
With Length (8)
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
With variant
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With Length (8)
|
||||
</h5>
|
||||
</div>
|
||||
With custom display character
|
||||
</h5>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
class="ant-otp"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With variant
|
||||
</h5>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/input/demo/otp.tsx extend context correctly 2`] = `[]`;
|
||||
exports[`renders components/input/demo/otp.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: [antd: Input.OTP] \`mask\` prop should be a single character.",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/input/demo/password-input.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
|
@ -3513,237 +3513,250 @@ exports[`renders components/input/demo/group.tsx correctly 1`] = `
|
||||
|
||||
exports[`renders components/input/demo/otp.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With formatter (Upcase)
|
||||
</h5>
|
||||
With formatter (Upcase)
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
With Disabled
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With Disabled
|
||||
</h5>
|
||||
With Length (8)
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-disabled ant-input-outlined ant-otp-input"
|
||||
disabled=""
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
With variant
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With Length (8)
|
||||
</h5>
|
||||
</div>
|
||||
With custom display character
|
||||
</h5>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
class="ant-otp"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With variant
|
||||
</h5>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-filled ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -13,13 +13,13 @@ describe('Input.OTP', () => {
|
||||
mountTest(Input.OTP);
|
||||
rtlTest(Input.OTP);
|
||||
|
||||
function getText(container: HTMLElement) {
|
||||
const inputList = container.querySelectorAll('input');
|
||||
const getText = (container: HTMLElement) => {
|
||||
const inputList = container.querySelectorAll<HTMLInputElement>('input');
|
||||
return Array.from(inputList)
|
||||
.map((input) => input.value || ' ')
|
||||
.join('')
|
||||
.replace(/\s*$/, '');
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
@ -128,4 +128,34 @@ describe('Input.OTP', () => {
|
||||
fireEvent.input(container.querySelector('input')!, { target: { value: 'little' } });
|
||||
expect(getText(container)).toBe('LITTLE');
|
||||
});
|
||||
|
||||
it('support mask prop', () => {
|
||||
// default
|
||||
const { container, rerender } = render(<OTP defaultValue="bamboo" />);
|
||||
expect(getText(container)).toBe('bamboo');
|
||||
|
||||
// support string
|
||||
rerender(<OTP defaultValue="bamboo" mask="*" />);
|
||||
expect(getText(container)).toBe('******');
|
||||
|
||||
// support emoji
|
||||
rerender(<OTP defaultValue="bamboo" mask="🔒" />);
|
||||
expect(getText(container)).toBe('🔒🔒🔒🔒🔒🔒');
|
||||
});
|
||||
|
||||
it('should throw Error when mask.length > 1', () => {
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<OTP mask="abc" />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Input.OTP] `mask` prop should be a single character.',
|
||||
);
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not throw Error when mask.length <= 1', () => {
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<OTP mask="x" />);
|
||||
expect(errSpy).not.toHaveBeenCalled();
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Flex, Input, Typography } from 'antd';
|
||||
import type { GetProp } from 'antd';
|
||||
import { Input, Space, Typography } from 'antd';
|
||||
import type { OTPProps } from 'antd/es/input/OTP';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@ -9,12 +10,12 @@ const App: React.FC = () => {
|
||||
console.log('onChange:', text);
|
||||
};
|
||||
|
||||
const sharedProps = {
|
||||
const sharedProps: OTPProps = {
|
||||
onChange,
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical">
|
||||
<Flex gap="middle" align="flex-start" vertical>
|
||||
<Title level={5}>With formatter (Upcase)</Title>
|
||||
<Input.OTP formatter={(str) => str.toUpperCase()} {...sharedProps} />
|
||||
<Title level={5}>With Disabled</Title>
|
||||
@ -23,7 +24,9 @@ const App: React.FC = () => {
|
||||
<Input.OTP length={8} {...sharedProps} />
|
||||
<Title level={5}>With variant</Title>
|
||||
<Input.OTP variant="filled" {...sharedProps} />
|
||||
</Space>
|
||||
<Title level={5}>With custom display character</Title>
|
||||
<Input.OTP mask="🔒" {...sharedProps} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -124,11 +124,16 @@ Supports all props of `Input`.
|
||||
|
||||
Added in `5.16.0`.
|
||||
|
||||
> Notes for developers
|
||||
>
|
||||
> When the `mask` prop is string, we recommend receiving a single character or a single emoji. If multiple characters or multiple emoji are passed, a warning will be thrown.
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| defaultValue | Default value | string | - | |
|
||||
| disabled | Whether the input is disabled | boolean | false | |
|
||||
| formatter | Format display, blank fields will be filled with ` ` | (value: string) => string | - | |
|
||||
| mask | Custom display, the original value will not be modified | boolean \| string | `false` | `5.17.0` |
|
||||
| length | The number of input elements | number | 6 | |
|
||||
| status | Set validation status | 'error' \| 'warning' | - | |
|
||||
| size | The size of the input box | `small` \| `middle` \| `large` | `middle` | |
|
||||
|
@ -125,11 +125,16 @@ interface CountConfig {
|
||||
|
||||
`5.16.0` 新增。
|
||||
|
||||
> 开发者注意事项:
|
||||
>
|
||||
> 当 `mask` 属性的类型为 string 时,我们强烈推荐接收单个字符或单个 emoji,如果传入多个字符或多个 emoji,则会在控制台抛出警告。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| defaultValue | 默认值 | string | - | |
|
||||
| disabled | 是否禁用 | boolean | false | |
|
||||
| formatter | 格式化展示,留空字段会被 ` ` 填充 | (value: string) => string | - | |
|
||||
| mask | 自定义展示,和 `formatter` 的区别是不会修改原始值 | boolean \| string | `false` | `5.17.0` |
|
||||
| length | 输入元素数量 | number | 6 | |
|
||||
| status | 设置校验状态 | 'error' \| 'warning' | - | |
|
||||
| size | 输入框大小 | `small` \| `middle` \| `large` | `middle` | |
|
||||
|
Loading…
Reference in New Issue
Block a user