mirror of
https://github.com/ant-design/ant-design.git
synced 2025-08-06 16:06:28 +08:00
feat: input.otp add separator api (#52668)
Some checks failed
Publish Any Commit / build (push) Has been cancelled
🔀 Sync mirror to Gitee / mirror (push) Has been cancelled
✅ test / lint (push) Has been cancelled
✅ test / test-react-legacy (16, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (16, 2/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 2/2) (push) Has been cancelled
✅ test / test-node (push) Has been cancelled
✅ test / test-react-latest (dom, 1/2) (push) Has been cancelled
✅ test / test-react-latest (dom, 2/2) (push) Has been cancelled
✅ test / build (push) Has been cancelled
✅ test / test lib/es module (es, 1/2) (push) Has been cancelled
✅ test / test lib/es module (es, 2/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 1/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 2/2) (push) Has been cancelled
👁️ Visual Regression Persist Start / test image (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 2/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Has been cancelled
✅ test / test-coverage (push) Has been cancelled
Some checks failed
Publish Any Commit / build (push) Has been cancelled
🔀 Sync mirror to Gitee / mirror (push) Has been cancelled
✅ test / lint (push) Has been cancelled
✅ test / test-react-legacy (16, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (16, 2/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 1/2) (push) Has been cancelled
✅ test / test-react-legacy (17, 2/2) (push) Has been cancelled
✅ test / test-node (push) Has been cancelled
✅ test / test-react-latest (dom, 1/2) (push) Has been cancelled
✅ test / test-react-latest (dom, 2/2) (push) Has been cancelled
✅ test / build (push) Has been cancelled
✅ test / test lib/es module (es, 1/2) (push) Has been cancelled
✅ test / test lib/es module (es, 2/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 1/2) (push) Has been cancelled
✅ test / test lib/es module (lib, 2/2) (push) Has been cancelled
👁️ Visual Regression Persist Start / test image (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist, 2/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 1/2) (push) Has been cancelled
✅ test / test-react-latest-dist (dist-min, 2/2) (push) Has been cancelled
✅ test / test-coverage (push) Has been cancelled
* feature(component): Input.OPT added separator api * test: add tests for OTP component separator rendering cases * refactor: apply code review suggestions
This commit is contained in:
parent
c9c05c53c9
commit
a8856dfb5b
@ -17,6 +17,7 @@ import type { InputRef } from '../Input';
|
||||
import useStyle from '../style/otp';
|
||||
import OTPInput from './OTPInput';
|
||||
import type { OTPInputProps } from './OTPInput';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface OTPRef {
|
||||
focus: VoidFunction;
|
||||
@ -41,6 +42,7 @@ export interface OTPProps
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
formatter?: (value: string) => string;
|
||||
separator?: ((index: number) => ReactNode) | ReactNode;
|
||||
|
||||
// Status
|
||||
disabled?: boolean;
|
||||
@ -66,6 +68,7 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
value,
|
||||
onChange,
|
||||
formatter,
|
||||
separator,
|
||||
variant,
|
||||
disabled,
|
||||
status: customStatus,
|
||||
@ -229,6 +232,11 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
inputMode,
|
||||
};
|
||||
|
||||
const renderSeparator = (index: number) => {
|
||||
const result = typeof separator === 'function' ? separator(index) : separator;
|
||||
return result ? <span className={`${prefixCls}-separator`}>{result}</span> : null;
|
||||
};
|
||||
|
||||
return wrapCSSVar(
|
||||
<div
|
||||
{...domAttrs}
|
||||
@ -249,21 +257,23 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
const key = `otp-${index}`;
|
||||
const singleValue = valueCells[index] || '';
|
||||
return (
|
||||
<OTPInput
|
||||
ref={(inputEle) => {
|
||||
refs.current[index] = inputEle;
|
||||
}}
|
||||
key={key}
|
||||
index={index}
|
||||
size={mergedSize}
|
||||
htmlSize={1}
|
||||
className={`${prefixCls}-input`}
|
||||
onChange={onInputChange}
|
||||
value={singleValue}
|
||||
onActiveChange={onInputActiveChange}
|
||||
autoFocus={index === 0 && autoFocus}
|
||||
{...inputSharedProps}
|
||||
/>
|
||||
<React.Fragment key={key}>
|
||||
<OTPInput
|
||||
ref={(inputEle) => {
|
||||
refs.current[index] = inputEle;
|
||||
}}
|
||||
index={index}
|
||||
size={mergedSize}
|
||||
htmlSize={1}
|
||||
className={`${prefixCls}-input`}
|
||||
onChange={onInputChange}
|
||||
value={singleValue}
|
||||
onActiveChange={onInputActiveChange}
|
||||
autoFocus={index === 0 && autoFocus}
|
||||
{...inputSharedProps}
|
||||
/>
|
||||
{separator && index < length - 1 && renderSeparator(index)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</FormItemInputContext.Provider>
|
||||
|
@ -10519,6 +10519,110 @@ exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With custom string separator
|
||||
</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=""
|
||||
/>
|
||||
<span
|
||||
class="ant-otp-separator"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<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>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With custom JSX separator
|
||||
</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=""
|
||||
/>
|
||||
<span
|
||||
class="ant-otp-separator"
|
||||
>
|
||||
<span
|
||||
style="color: red;"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
</span>
|
||||
<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>
|
||||
`;
|
||||
|
||||
|
@ -3869,6 +3869,110 @@ exports[`renders components/input/demo/otp.tsx correctly 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With custom string separator
|
||||
</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=""
|
||||
/>
|
||||
<span
|
||||
class="ant-otp-separator"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<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>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With custom JSX separator
|
||||
</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=""
|
||||
/>
|
||||
<span
|
||||
class="ant-otp-separator"
|
||||
>
|
||||
<span
|
||||
style="color:red"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
</span>
|
||||
<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>
|
||||
`;
|
||||
|
||||
|
@ -200,4 +200,41 @@ describe('Input.OTP', () => {
|
||||
|
||||
expect(event.defaultPrevented).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders separator between input fields', () => {
|
||||
const { container } = render(
|
||||
<OTP
|
||||
length={4}
|
||||
separator={(index) => (
|
||||
<span key={index} className="custom-separator">
|
||||
|
|
||||
</span>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
const separators = container.querySelectorAll('.custom-separator');
|
||||
expect(separators.length).toBe(3);
|
||||
separators.forEach((separator) => {
|
||||
expect(separator.textContent).toBe('|');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders separator when separator is a string', () => {
|
||||
const { container } = render(<OTP length={4} separator="-" />);
|
||||
const separators = container.querySelectorAll(`.ant-otp-separator`);
|
||||
expect(separators.length).toBe(3);
|
||||
separators.forEach((separator) => {
|
||||
expect(separator.textContent).toBe('-');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders separator when separator is a element', () => {
|
||||
const customSeparator = <div data-testid="custom-separator">X</div>;
|
||||
const { getAllByTestId } = render(<OTP length={4} separator={customSeparator} />);
|
||||
const separators = getAllByTestId('custom-separator');
|
||||
expect(separators.length).toBe(3);
|
||||
separators.forEach((separator) => {
|
||||
expect(separator.textContent).toBe('X');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -32,6 +32,13 @@ const App: React.FC = () => {
|
||||
<Input.OTP variant="filled" {...sharedProps} />
|
||||
<Title level={5}>With custom display character</Title>
|
||||
<Input.OTP mask="🔒" {...sharedProps} />
|
||||
<Title level={5}>With custom string separator</Title>
|
||||
<Input.OTP separator={(index) => (index === 2 ? '-' : undefined)} {...sharedProps} />
|
||||
<Title level={5}>With custom JSX separator</Title>
|
||||
<Input.OTP
|
||||
separator={(index) => index === 1 && <span style={{ color: 'red' }}>—</span>}
|
||||
{...sharedProps}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -135,6 +135,7 @@ Added in `5.16.0`.
|
||||
| defaultValue | Default value | string | - | |
|
||||
| disabled | Whether the input is disabled | boolean | false | |
|
||||
| formatter | Format display, blank fields will be filled with ` ` | (value: string) => string | - | |
|
||||
| separator | render the separator after the input box of the specified index | ((index: number) => ReactNode) \| ReactNode | - | |
|
||||
| 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' | - | |
|
||||
|
@ -136,6 +136,7 @@ interface CountConfig {
|
||||
| defaultValue | 默认值 | string | - | |
|
||||
| disabled | 是否禁用 | boolean | false | |
|
||||
| formatter | 格式化展示,留空字段会被 ` ` 填充 | (value: string) => string | - | |
|
||||
| separator | 分隔符,在指定索引的输入框后渲染分隔符 | ((index: number) => ReactNode) \| ReactNode | - | |
|
||||
| mask | 自定义展示,和 `formatter` 的区别是不会修改原始值 | boolean \| string | `false` | `5.17.0` |
|
||||
| length | 输入元素数量 | number | 6 | |
|
||||
| status | 设置校验状态 | 'error' \| 'warning' | - | |
|
||||
|
Loading…
Reference in New Issue
Block a user