feat: select support status (#34084)

* feat: select support status

* test: fix lint
This commit is contained in:
MadCcc 2022-02-16 21:39:48 +08:00 committed by GitHub
parent 8054abe81f
commit 2258335e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 593 additions and 9 deletions

View File

@ -14510,7 +14510,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
class="ant-select ant-select-single ant-select-show-arrow"
class="ant-select ant-select-has-feedback ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
@ -18483,7 +18483,7 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc
class="ant-form-item-control-input-content"
>
<div
class="ant-select ant-select-single ant-select-allow-clear ant-select-show-arrow"
class="ant-select ant-select-status-error ant-select-has-feedback ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
class="ant-select-selector"
@ -18637,6 +18637,29 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc
/>
</svg>
</span>
<span
class="ant-select-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>
</span>
</div>
</div>

View File

@ -5922,7 +5922,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
class="ant-select ant-select-single ant-select-show-arrow"
class="ant-select ant-select-has-feedback ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
@ -7789,7 +7789,7 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
class="ant-select ant-select-single ant-select-allow-clear ant-select-show-arrow"
class="ant-select ant-select-status-error ant-select-has-feedback ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
class="ant-select-selector"
@ -7844,6 +7844,29 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
/>
</svg>
</span>
<span
class="ant-select-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>
</span>
</div>
</div>

View File

@ -7589,6 +7589,259 @@ Array [
]
`;
exports[`renders ./components/select/demo/status.md extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-select ant-select-status-error ant-select-single ant-select-show-arrow"
style="width:100%"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<div>
<div
class="ant-select-dropdown ant-select-dropdown-empty"
style="opacity:0;pointer-events:none"
>
<div>
<div
class="ant-select-item-empty"
id="undefined_list"
role="listbox"
>
<div
class="ant-empty ant-empty-normal ant-empty-small"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-simple"
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
transform="translate(0 1)"
>
<ellipse
class="ant-empty-img-simple-ellipse"
cx="32"
cy="33"
rx="32"
ry="7"
/>
<g
class="ant-empty-img-simple-g"
fill-rule="nonzero"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
class="ant-empty-img-simple-path"
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
/>
</g>
</g>
</svg>
</div>
<div
class="ant-empty-description"
>
No Data
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-status-warning ant-select-single ant-select-show-arrow"
style="width:100%"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<div>
<div
class="ant-select-dropdown ant-select-dropdown-empty"
style="opacity:0;pointer-events:none"
>
<div>
<div
class="ant-select-item-empty"
id="undefined_list"
role="listbox"
>
<div
class="ant-empty ant-empty-normal ant-empty-small"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-simple"
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
transform="translate(0 1)"
>
<ellipse
class="ant-empty-img-simple-ellipse"
cx="32"
cy="33"
rx="32"
ry="7"
/>
<g
class="ant-empty-img-simple-g"
fill-rule="nonzero"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
class="ant-empty-img-simple-path"
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
/>
</g>
</g>
</svg>
</div>
<div
class="ant-empty-description"
>
No Data
</div>
</div>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
`;
exports[`renders ./components/select/demo/suffix.md extend context correctly 1`] = `
Array [
<div

View File

@ -2438,6 +2438,137 @@ Array [
]
`;
exports[`renders ./components/select/demo/status.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-select ant-select-status-error ant-select-single ant-select-show-arrow"
style="width:100%"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-status-warning ant-select-single ant-select-show-arrow"
style="width:100%"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
`;
exports[`renders ./components/select/demo/suffix.md correctly 1`] = `
Array [
<div

View File

@ -0,0 +1,34 @@
---
order: 25
version: 4.19.0
title:
zh-CN: 自定义状态
en-US: Status
---
## zh-CN
使用 `status` 为 Select 添加状态,可选 `error` 或者 `warning`
## en-US
Add status to Select with `status`, which could be `error` or `warning`.
```tsx
import { Select, Space } from 'antd';
const Status: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>
<Select status="error" style={{ width: '100%' }} />
<Select status="warning" style={{ width: '100%' }} />
</Space>
);
ReactDOM.render(<Status />, mountNode);
```
```css
#components-select-demo-status .ant-select {
margin: 0;
}
```

View File

@ -61,6 +61,7 @@ Select component to select value from options.
| showArrow | Whether to show the drop-down arrow | boolean | true(for single select), false(for multiple select) | |
| showSearch | Whether show search input in single mode | boolean | false | |
| size | Size of Select input | `large` \| `middle` \| `small` | `middle` | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| tagRender | Customize tag render, only applies when `mode` is set to `multiple` or `tags` | (props) => ReactNode | - | |
| tokenSeparators | Separator used to tokenize on `tag` and `multiple` mode | string\[] | - | |

View File

@ -6,9 +6,12 @@ import classNames from 'classnames';
import RcSelect, { Option, OptGroup, SelectProps as RcSelectProps, BaseSelectRef } from 'rc-select';
import type { BaseOptionType, DefaultOptionType } from 'rc-select/lib/Select';
import { OptionProps } from 'rc-select/lib/Option';
import { useContext } from 'react';
import { ConfigContext } from '../config-provider';
import getIcons from './utils/iconUtil';
import SizeContext, { SizeType } from '../config-provider/SizeContext';
import { FormItemStatusContext } from '../form/context';
import { getMergedStatus, getStatusClassNames, InputStatus } from '../_util/statusUtils';
import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion';
type RawValue = string | number;
@ -42,6 +45,7 @@ export interface SelectProps<
> {
placement?: SelectCommonPlacement;
mode?: 'multiple' | 'tags';
status?: InputStatus;
}
const SECRET_COMBOBOX_MODE_DO_NOT_USE = 'SECRET_COMBOBOX_MODE_DO_NOT_USE';
@ -58,6 +62,8 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
listItemHeight = 24,
size: customizeSize,
notFoundContent,
status: customStatus,
showArrow,
...props
}: SelectProps<OptionType>,
ref: React.Ref<BaseSelectRef>,
@ -90,6 +96,12 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
}, [props.mode]);
const isMultiple = mode === 'multiple' || mode === 'tags';
const mergedShowArrow =
showArrow !== undefined ? showArrow : props.loading || !(isMultiple || mode === 'combobox');
// ===================== Validation Status =====================
const { status: contextStatus, hasFeedback } = useContext(FormItemStatusContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
// ===================== Empty =====================
let mergedNotFound: React.ReactNode;
@ -105,6 +117,9 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const { suffixIcon, itemIcon, removeIcon, clearIcon } = getIcons({
...props,
multiple: isMultiple,
status: mergedStatus,
hasFeedback,
showArrow: mergedShowArrow,
prefixCls,
});
@ -122,6 +137,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
className,
);
@ -160,6 +176,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
className={mergedClassName}
getPopupContainer={getPopupContainer || getContextPopupContainer}
dropdownClassName={rcSelectRtlDropDownClassName}
showArrow={hasFeedback || showArrow}
/>
);
};

View File

@ -62,6 +62,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true多选为 false | |
| showSearch | 使单选模式可搜索 | boolean | false | |
| size | 选择框大小 | `large` \| `middle` \| `small` | `middle` | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| tagRender | 自定义 tag 内容 render仅在 `mode``multiple``tags` 时生效 | (props) => ReactNode | - | |
| tokenSeparators | 在 `tags``multiple` 模式下自动分词的分隔符 | string\[] | - | |

View File

@ -3,6 +3,7 @@
@import '../../input/style/mixin';
@import './single';
@import './multiple';
@import './status';
@select-prefix-cls: ~'@{ant-prefix}-select';
@select-height-without-border: @input-height-base - 2 * @border-width-base;
@ -120,7 +121,8 @@
position: absolute;
top: 50%;
right: @control-padding-horizontal - 1px;
width: @font-size-sm;
display: flex;
align-items: center;
height: @font-size-sm;
margin-top: (-@font-size-sm / 2);
color: @disabled-color;

View File

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

View File

@ -0,0 +1,80 @@
@import '../../input/style/mixin';
@select-prefix-cls: ~'@{ant-prefix}-select';
.status-color(
@text-color;
@border-color;
@background-color;
@hoverBorderColor;
@outlineColor;
) {
&.@{select-prefix-cls}:not(.@{select-prefix-cls}-disabled):not(.@{select-prefix-cls}-customize-input) {
.@{select-prefix-cls}-selector {
background-color: @background-color;
border-color: @border-color !important;
}
&.@{select-prefix-cls}-open .@{select-prefix-cls}-selector,
&.@{select-prefix-cls}-focused .@{select-prefix-cls}-selector {
.active(@border-color, @hoverBorderColor, @outlineColor);
}
}
.@{select-prefix-cls}-feedback-icon {
color: @text-color;
}
}
.select-status-base(@prefix-cls) {
.@{prefix-cls} {
&-status-error {
.status-color(@error-color, @error-color, @select-background, @error-color-hover, @error-color-outline);
}
&-status-warning {
.status-color(@warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
}
&-status-success {
.@{prefix-cls}-feedback-icon {
color: @success-color;
}
}
&-status-validating {
.@{prefix-cls}-feedback-icon {
color: @primary-color;
}
}
&-status-error,
&-status-warning,
&-status-success,
&-status-validating {
&.@{prefix-cls}-has-feedback {
//.@{prefix-cls}-arrow,
.@{prefix-cls}-clear {
right: 32px;
}
.@{prefix-cls}-selection-selected-value {
padding-right: 42px;
}
}
}
&-feedback-icon {
font-size: @font-size-base;
text-align: center;
visibility: visible;
animation: zoomIn 0.3s @ease-out-back;
pointer-events: none;
&:not(:first-child) {
margin-left: 8px;
}
}
}
}
.select-status-base(@select-prefix-cls);

View File

@ -1,10 +1,13 @@
import * as React from 'react';
import { ReactNode } from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import CheckOutlined from '@ant-design/icons/CheckOutlined';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import SearchOutlined from '@ant-design/icons/SearchOutlined';
import { ValidateStatus } from '../../form/FormItem';
import { getFeedbackIcon } from '../../_util/statusUtils';
export default function getIcons({
suffixIcon,
@ -13,7 +16,10 @@ export default function getIcons({
removeIcon,
loading,
multiple,
hasFeedback,
status,
prefixCls,
showArrow,
}: {
suffixIcon?: React.ReactNode;
clearIcon?: React.ReactNode;
@ -21,7 +27,10 @@ export default function getIcons({
removeIcon?: React.ReactNode;
loading?: boolean;
multiple?: boolean;
hasFeedback?: boolean;
status?: ValidateStatus;
prefixCls: string;
showArrow?: boolean;
}) {
// Clear Icon
let mergedClearIcon = clearIcon;
@ -29,19 +38,27 @@ export default function getIcons({
mergedClearIcon = <CloseCircleFilled />;
}
// Validation Feedback Icon
const getSuffixIconNode = (arrowIcon?: ReactNode) => (
<>
{showArrow !== false && arrowIcon}
{hasFeedback && getFeedbackIcon(prefixCls, status)}
</>
);
// Arrow item icon
let mergedSuffixIcon = null;
if (suffixIcon !== undefined) {
mergedSuffixIcon = suffixIcon;
mergedSuffixIcon = getSuffixIconNode(suffixIcon);
} else if (loading) {
mergedSuffixIcon = <LoadingOutlined spin />;
mergedSuffixIcon = getSuffixIconNode(<LoadingOutlined spin />);
} else {
const iconCls = `${prefixCls}-suffix`;
mergedSuffixIcon = ({ open, showSearch }: { open: boolean; showSearch: boolean }) => {
if (open && showSearch) {
return <SearchOutlined className={iconCls} />;
return getSuffixIconNode(<SearchOutlined className={iconCls} />);
}
return <DownOutlined className={iconCls} />;
return getSuffixIconNode(<DownOutlined className={iconCls} />);
};
}