feat: mentions support status (#34071)

* test: skip form deps-lint

* perf: remove useless has-feedback classname

* test: raise test coverage

* style: recover import arrangement
This commit is contained in:
MadCcc 2022-02-16 21:14:05 +08:00 committed by GitHub
parent 6a7acfbf6e
commit 65bf6550b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 319 additions and 66 deletions

View File

@ -20562,7 +20562,7 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc
</div>
</div>
<div
class="ant-row ant-form-item ant-form-item-has-error"
class="ant-row ant-form-item ant-form-item-has-feedback ant-form-item-has-error"
>
<div
class="ant-col ant-form-item-label ant-col-xs-24 ant-col-sm-6"
@ -20584,12 +20584,39 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc
class="ant-form-item-control-input-content"
>
<div
class="ant-mentions"
class="ant-mentions-affix-wrapper ant-mentions-affix-wrapper-status-error ant-mentions-affix-wrapper-has-feedback"
>
<textarea
class="rc-textarea"
rows="1"
/>
<div
class="ant-mentions ant-mentions-status-error"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>
<span
class="ant-mentions-feedback-icon"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>

View File

@ -8553,7 +8553,7 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
</div>
</div>
<div
class="ant-row ant-form-item ant-form-item-has-error"
class="ant-row ant-form-item ant-form-item-has-feedback ant-form-item-has-error"
>
<div
class="ant-col ant-form-item-label ant-col-xs-24 ant-col-sm-6"
@ -8575,12 +8575,39 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
class="ant-mentions"
class="ant-mentions-affix-wrapper ant-mentions-affix-wrapper-status-error ant-mentions-affix-wrapper-has-feedback"
>
<textarea
class="rc-textarea"
rows="1"
/>
<div
class="ant-mentions ant-mentions-status-error"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>
<span
class="ant-mentions-feedback-icon"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>

View File

@ -155,7 +155,7 @@ ReactDOM.render(
<Input.Password allowClear placeholder="with input password and allowClear" />
</Form.Item>
<Form.Item label="Fail" validateStatus="error">
<Form.Item label="Fail" validateStatus="error" hasFeedback>
<Mentions />
</Form.Item>

View File

@ -1,8 +1,8 @@
---
order: 19
title:
zh-CN: 自定义校验
en-US: Customized Validation
zh-CN: 自定义状态
en-US: Status
---
## zh-CN

View File

@ -206,3 +206,39 @@ Array [
</div>,
]
`;
exports[`renders ./components/mentions/demo/status.md extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-mentions ant-mentions-status-error"
>
<textarea
class="rc-textarea"
rows="1"
>
@afc163
</textarea>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-mentions ant-mentions-status-warning"
>
<textarea
class="rc-textarea"
rows="1"
>
@afc163
</textarea>
</div>
</div>
</div>
`;

View File

@ -206,3 +206,39 @@ Array [
</div>,
]
`;
exports[`renders ./components/mentions/demo/status.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-mentions ant-mentions-status-error"
>
<textarea
class="rc-textarea"
rows="1"
>
@afc163
</textarea>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-mentions ant-mentions-status-warning"
>
<textarea
class="rc-textarea"
rows="1"
>
@afc163
</textarea>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,51 @@
---
order: 8
title:
zh-CN: 自定义状态
en-US: Status
---
## zh-CN
使用 `status` 为 Mentions 添加状态。可选 `error` 或者 `warning`
## en-US
Add status to Mentions with `status`, which could be `error` or `warning`
```jsx
import { Mentions, Space } from 'antd';
const { Option } = Mentions;
function onChange(value) {
console.log('Change:', value);
}
function onSelect(option) {
console.log('select', option);
}
const MentionsStatuses = () => {
const options = (
<>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</>
);
return (
<Space direction="vertical">
<Mentions onChange={onChange} onSelect={onSelect} defaultValue="@afc163" status="error">
{options}
</Mentions>
<Mentions onChange={onChange} onSelect={onSelect} defaultValue="@afc163" status="warning">
{options}
</Mentions>
</Space>
);
};
ReactDOM.render(<MentionsStatuses />, mountNode);
```

View File

@ -21,32 +21,33 @@ When you need to mention someone or something.
### Mention
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| autoFocus | Auto get focus when component mounted | boolean | false |
| autoSize | Textarea height autosize feature, can be set to true \| false or an object { minRows: 2, maxRows: 6 } | boolean \| object | false |
| defaultValue | Default value | string | - |
| filterOption | Customize filter option logic | false \| (input: string, option: OptionProps) => boolean | - |
| getPopupContainer | Set the mount HTML node for suggestions | () => HTMLElement | - |
| notFoundContent | Set mentions content when not match | ReactNode | `Not Found` |
| placement | Set popup placement | `top` \| `bottom` | `bottom` |
| prefix | Set trigger prefix keyword | string \| string\[] | `@` |
| split | Set split string before and after selected mention | string | ` ` |
| validateSearch | Customize trigger search logic | (text: string, props: MentionsProps) => void | - |
| value | Set value of mentions | string | - |
| onBlur | Trigger when mentions lose focus | () => void | - |
| onChange | Trigger when value changed | (text: string) => void | - |
| onFocus | Trigger when mentions get focus | () => void | - |
| onResize | The callback function that is triggered when textarea resize | function({ width, height }) | - |
| onSearch | Trigger when prefix hit | (text: string, prefix: string) => void | - |
| onSelect | Trigger when user select the option | (option: OptionProps, prefix: string) => void | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoFocus | Auto get focus when component mounted | boolean | false | |
| autoSize | Textarea height autosize feature, can be set to true \| false or an object { minRows: 2, maxRows: 6 } | boolean \| object | false | |
| defaultValue | Default value | string | - | |
| filterOption | Customize filter option logic | false \| (input: string, option: OptionProps) => boolean | - | |
| getPopupContainer | Set the mount HTML node for suggestions | () => HTMLElement | - | |
| notFoundContent | Set mentions content when not match | ReactNode | `Not Found` | |
| placement | Set popup placement | `top` \| `bottom` | `bottom` | |
| prefix | Set trigger prefix keyword | string \| string\[] | `@` | |
| split | Set split string before and after selected mention | string | ` ` | |
| status | Set validation status | 'error' \| 'warning' \| 'success' \| 'validating' | - | 4.19.0 |
| validateSearch | Customize trigger search logic | (text: string, props: MentionsProps) => void | - | |
| value | Set value of mentions | string | - | |
| onBlur | Trigger when mentions lose focus | () => void | - | |
| onChange | Trigger when value changed | (text: string) => void | - | |
| onFocus | Trigger when mentions get focus | () => void | - | |
| onResize | The callback function that is triggered when textarea resize | function({ width, height }) | - | |
| onSearch | Trigger when prefix hit | (text: string, prefix: string) => void | - | |
| onSelect | Trigger when user select the option | (option: OptionProps, prefix: string) => void | - | |
### Mention methods
| Name | Description |
| --- | --- |
| blur() | Remove focus |
| focus() | Get focus |
| Name | Description |
| ------- | ------------ |
| blur() | Remove focus |
| focus() | Get focus |
### Option

View File

@ -5,6 +5,13 @@ import { MentionsProps as RcMentionsProps } from 'rc-mentions/lib/Mentions';
import { composeRef } from 'rc-util/lib/ref';
import Spin from '../spin';
import { ConfigContext } from '../config-provider';
import { FormItemStatusContext } from '../form/context';
import {
getFeedbackIcon,
getMergedStatus,
getStatusClassNames,
InputStatus,
} from '../_util/statusUtils';
export const { Option } = RcMentions;
@ -22,6 +29,7 @@ export interface OptionProps {
export interface MentionProps extends RcMentionsProps {
loading?: boolean;
status?: InputStatus;
}
export interface MentionState {
@ -53,6 +61,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
filterOption,
children,
notFoundContent,
status: customStatus,
...restProps
},
ref,
@ -61,6 +70,8 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
const innerRef = React.useRef<HTMLElement>();
const mergedRef = composeRef(ref, innerRef);
const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext);
const { status: contextStatus, hasFeedback } = React.useContext(FormItemStatusContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const onFocus: React.FocusEventHandler<HTMLTextAreaElement> = (...args) => {
if (restProps.onFocus) {
@ -112,10 +123,11 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
[`${prefixCls}-focused`]: focused,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
getStatusClassNames(prefixCls, mergedStatus),
!hasFeedback && className,
);
return (
const mentions = (
<RcMentions
prefixCls={prefixCls}
notFoundContent={getNotFoundContent()}
@ -131,6 +143,23 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
{getOptions()}
</RcMentions>
);
if (hasFeedback) {
return (
<div
className={classNames(
`${prefixCls}-affix-wrapper`,
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
className,
)}
>
{mentions}
{getFeedbackIcon(prefixCls, mergedStatus)}
</div>
);
}
return mentions;
};
const Mentions = React.forwardRef<unknown, MentionProps>(InternalMentions) as CompoundedComponent;

View File

@ -22,36 +22,37 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg
### Mentions
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| autoFocus | 自动获得焦点 | boolean | false |
| autoSize | 自适应内容高度,可设置为 true \| false 或对象:{ minRows: 2, maxRows: 6 } | boolean \| object | false |
| defaultValue | 默认值 | string | - |
| filterOption | 自定义过滤逻辑 | false \| (input: string, option: OptionProps) => boolean | - |
| getPopupContainer | 指定建议框挂载的 HTML 节点 | () => HTMLElement | - |
| notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | `Not Found` |
| placement | 弹出层展示位置 | `top` \| `bottom` | `bottom` |
| prefix | 设置触发关键字 | string \| string\[] | `@` |
| split | 设置选中项前后分隔符 | string | ` ` |
| validateSearch | 自定义触发验证逻辑 | (text: string, props: MentionsProps) => void | - |
| value | 设置值 | string | - |
| onBlur | 失去焦点时触发 | () => void | - |
| onChange | 值改变时触发 | (text: string) => void | - |
| onFocus | 获得焦点时触发 | () => void | - |
| onResize | resize 回调 | function({ width, height }) | - |
| onSearch | 搜索时触发 | (text: string, prefix: string) => void | - |
| onSelect | 选择选项时触发 | (option: OptionProps, prefix: string) => void | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoFocus | 自动获得焦点 | boolean | false | |
| autoSize | 自适应内容高度,可设置为 true \| false 或对象:{ minRows: 2, maxRows: 6 } | boolean \| object | false | |
| defaultValue | 默认值 | string | - | |
| filterOption | 自定义过滤逻辑 | false \| (input: string, option: OptionProps) => boolean | - | |
| getPopupContainer | 指定建议框挂载的 HTML 节点 | () => HTMLElement | - | |
| notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | `Not Found` | |
| placement | 弹出层展示位置 | `top` \| `bottom` | `bottom` | |
| prefix | 设置触发关键字 | string \| string\[] | `@` | |
| split | 设置选中项前后分隔符 | string | ` ` | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| validateSearch | 自定义触发验证逻辑 | (text: string, props: MentionsProps) => void | - | |
| value | 设置值 | string | - | |
| onBlur | 失去焦点时触发 | () => void | - | |
| onChange | 值改变时触发 | (text: string) => void | - | |
| onFocus | 获得焦点时触发 | () => void | - | |
| onResize | resize 回调 | function({ width, height }) | - | |
| onSearch | 搜索时触发 | (text: string, prefix: string) => void | - | |
| onSelect | 选择选项时触发 | (option: OptionProps, prefix: string) => void | - | |
### Mentions 方法
| 名称 | 描述 |
| --- | --- |
| blur() | 移除焦点 |
| 名称 | 描述 |
| ------- | -------- |
| blur() | 移除焦点 |
| focus() | 获取焦点 |
### Option
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| children | 选项内容 | ReactNode | - |
| value | 选择时填充的值 | string | - |
| 参数 | 说明 | 类型 | 默认值 |
| -------- | -------------- | --------- | ------ |
| children | 选项内容 | ReactNode | - |
| value | 选择时填充的值 | string | - |

View File

@ -1,6 +1,7 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@import './status';
@mention-prefix-cls: ~'@{ant-prefix}-mentions';

View File

@ -3,3 +3,5 @@ import './index.less';
// style dependencies
import '../../empty/style';
import '../../spin/style';
// deps-lint-skip: form

View File

@ -0,0 +1,42 @@
@import '../../input/style/status';
@mention-prefix-cls: ~'@{ant-prefix}-mentions';
.@{mention-prefix-cls} {
&-status-error {
.status-color(@mention-prefix-cls, @error-color, @error-color, @input-bg, @error-color-hover, @error-color-outline);
.status-color-common(@input-prefix-cls, @error-color, @error-color, @input-bg, @error-color-hover, @error-color-outline);
}
&-status-warning {
.status-color(@mention-prefix-cls, @warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
.status-color-common(@input-prefix-cls, @warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
}
&-affix-wrapper {
position: relative;
.@{mention-prefix-cls}-feedback-icon {
position: absolute;
top: 0;
right: @input-padding-horizontal-base;
bottom: 0;
z-index: 1;
display: inline-flex;
align-items: center;
margin: auto;
}
&-status-error {
.@{mention-prefix-cls}-feedback-icon {
color: @error-color;
}
}
&-has-warning {
.@{mention-prefix-cls}-feedback-icon {
color: @warning-color;
}
}
}
}