feat: select add status & placement

pull/5820/head
tangjinzhou 2022-05-21 22:03:56 +08:00
parent af9371fe6f
commit 527dec6078
12 changed files with 226 additions and 26 deletions

View File

@ -1,7 +1,7 @@
<docs>
---
order: 19
version: 4.19.0
version: 3.3.0
title:
zh-CN: 自定义状态
en-US: Status

View File

@ -18,6 +18,8 @@
<OptionLabelProp />
<BigData />
<fieldNamesVue />
<placementVue />
<statusVue />
</demo-sort>
</template>
<script lang="ts">
@ -39,6 +41,8 @@ import OptionLabelProp from './option-label-prop.vue';
import BigData from './big-data.vue';
import Responsive from './responsive.vue';
import fieldNamesVue from './field-names.vue';
import placementVue from './placement.vue';
import statusVue from './status.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import { defineComponent } from 'vue';
@ -46,6 +50,8 @@ export default defineComponent({
CN,
US,
components: {
placementVue,
statusVue,
fieldNamesVue,
Basic,
Size,

View File

@ -0,0 +1,50 @@
<docs>
---
order: 28
title:
zh-CN: 弹出位置
en-US: Popup Placement
---
## zh-CN
可以通过 `placement` 手动指定弹出的位置
## en-US
You can manually specify the position of the popup via `placement`.
</docs>
<template>
<a-radio-group v-model:value="placement">
<a-radio-button value="topLeft">topLeft</a-radio-button>
<a-radio-button value="topRight">topRight</a-radio-button>
<a-radio-button value="bottomLeft">bottomLeft</a-radio-button>
<a-radio-button value="bottomRight">bottomRight</a-radio-button>
</a-radio-group>
<br />
<br />
<a-select
v-model:value="value"
style="width: 120px"
:dropdown-match-select-width="false"
:placement="placement"
>
<a-select-option value="HangZhou">HangZhou #310000</a-select-option>
<a-select-option value="NingBo">NingBo #315000</a-select-option>
<a-select-option value="WenZhou">WenZhou #325000</a-select-option>
</a-select>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const placement = ref('topLeft' as const);
return {
placement,
value: ref('HangZhou'),
};
},
});
</script>

View File

@ -0,0 +1,38 @@
<docs>
---
order: 19
version: 3.3.0
title:
zh-CN: 自定义状态
en-US: Status
---
## zh-CN
使用 `status` DatePicker 添加状态可选 `error` 或者 `warning`
## en-US
Add status to DatePicker with `status`, which could be `error` or `warning`.
</docs>
<template>
<a-space direction="vertical" style="width: 100%">
<a-select status="error" style="width: 100%" />
<a-select status="warning" style="width: 100%" />
</a-space>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
return {};
},
});
</script>
<style scoped>
#components-select-demo-status .ant-select {
margin: 0;
}
</style>

View File

@ -57,14 +57,16 @@ Select component to select value from options.
| optionLabelProp | Which prop value of option will render as content of select. | string | `children` \| `label`(when use options) | |
| options | Data of the selectOption, manual construction work is no longer needed if this property has been set | array&lt;{value, label, [disabled, key, title]}> | \[] | |
| placeholder | Placeholder of select | string\|slot | - | |
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | 3.3.0 |
| removeIcon | The custom remove icon | VNode \| slot | - | |
| searchValue | The current input "search" text | string | - | |
| showArrow | Whether to show the drop-down arrow | boolean | true | |
| showSearch | Whether show search input in single mode. | boolean | false | |
| size | Size of Select input. `default` `large` `small` | string | default | |
| status | Set validation status | 'error' \| 'warning' | - | 3.3.0 |
| suffixIcon | The custom suffix icon | VNode \| slot | - | |
| tagRender | Customize tag render, only applies when `mode` is set to `multiple` or `tags` | slot \| (props) => any | - | |
| tokenSeparators | Separator used to tokenize on tag/multiple mode | string\[] | | |
| tokenSeparators | Separator used to tokenize, only applies when `mode="tags"` | string\[] | - | |
| value(v-model) | Current selected option. | string\|number\|string\[]\|number\[] | - | |
| virtual | Disable virtual scroll when set to false | boolean | true | 3.0 |
@ -114,7 +116,7 @@ Select component to select value from options.
### The dropdown is closed when click `dropdownRender` area?
See the [dropdownRender example](/components/select/#components-select-demo-custom-dropdown).
Dropdown menu will be closed if click `dropdownRender` area, you can prevent it by wrapping `@mousedown.prevent` See the [dropdownRender example](/components/select/#components-select-demo-custom-dropdown).
### Why is `placeholder` not displayed?

View File

@ -9,10 +9,13 @@ import getIcons from './utils/iconUtil';
import PropTypes from '../_util/vue-types';
import useConfigInject from '../_util/hooks/useConfigInject';
import omit from '../_util/omit';
import { useInjectFormItemContext } from '../form/FormItemContext';
import { getTransitionName } from '../_util/transition';
import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext';
import type { SelectCommonPlacement } from '../_util/transition';
import { getTransitionDirection, getTransitionName } from '../_util/transition';
import type { SizeType } from '../config-provider';
import { initDefaultProps } from '../_util/props-util';
import type { InputStatus } from '../_util/statusUtils';
import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils';
type RawValue = string | number;
@ -48,6 +51,8 @@ export const selectProps = () => ({
bordered: { type: Boolean, default: true },
transitionName: String,
choiceTransitionName: { type: String, default: '' },
placement: String as PropType<SelectCommonPlacement>,
status: String as PropType<InputStatus>,
'onUpdate:value': Function as PropType<(val: SelectValue) => void>,
});
@ -81,6 +86,8 @@ const Select = defineComponent({
setup(props, { attrs, emit, slots, expose }) {
const selectRef = ref<BaseSelectRef>();
const formItemContext = useInjectFormItemContext();
const formItemInputContext = FormItemInputContext.useInject();
const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
const focus = () => {
selectRef.value?.focus();
};
@ -111,16 +118,33 @@ const Select = defineComponent({
props,
);
const rootPrefixCls = computed(() => getPrefixCls());
// ===================== Placement =====================
const placement = computed(() => {
if (props.placement !== undefined) {
return props.placement;
}
return direction.value === 'rtl'
? ('bottomRight' as SelectCommonPlacement)
: ('bottomLeft' as SelectCommonPlacement);
});
const transitionName = computed(() =>
getTransitionName(rootPrefixCls.value, 'slide-up', props.transitionName),
getTransitionName(
rootPrefixCls.value,
getTransitionDirection(placement.value),
props.transitionName,
),
);
const mergedClassName = computed(() =>
classNames({
classNames(
{
[`${prefixCls.value}-lg`]: size.value === 'large',
[`${prefixCls.value}-sm`]: size.value === 'small',
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
[`${prefixCls.value}-borderless`]: !props.bordered,
}),
[`${prefixCls.value}-in-form-item`]: formItemInputContext.isFormItemInput,
},
getStatusClassNames(prefixCls.value, mergedStatus.value, formItemInputContext.hasFeedback),
),
);
const triggerChange: SelectProps['onChange'] = (...args) => {
emit('update:value', args[0]);
@ -137,6 +161,12 @@ const Select = defineComponent({
scrollTo,
});
const isMultiple = computed(() => mode.value === 'multiple' || mode.value === 'tags');
const mergedShowArrow = computed(() =>
props.showArrow !== undefined
? props.showArrow
: props.loading || !(isMultiple.value || mode.value === 'combobox'),
);
return () => {
const {
notFoundContent,
@ -148,8 +178,9 @@ const Select = defineComponent({
dropdownMatchSelectWidth,
id = formItemContext.id.value,
placeholder = slots.placeholder?.(),
showArrow,
} = props;
const { hasFeedback, feedbackIcon } = formItemInputContext;
const { renderEmpty, getPopupContainer: getContextPopupContainer } = configProvider;
// ===================== Empty =====================
@ -170,6 +201,9 @@ const Select = defineComponent({
...props,
multiple: isMultiple.value,
prefixCls: prefixCls.value,
hasFeedback,
feedbackIcon,
showArrow: mergedShowArrow.value,
},
slots,
);
@ -182,9 +216,10 @@ const Select = defineComponent({
'clearIcon',
'size',
'bordered',
'status',
]);
const rcSelectRtlDropDownClassName = classNames(dropdownClassName, {
const rcSelectRtlDropdownClassName = classNames(dropdownClassName, {
[`${prefixCls.value}-dropdown-${direction.value}`]: direction.value === 'rtl',
});
return (
@ -207,7 +242,7 @@ const Select = defineComponent({
notFoundContent={mergedNotFound}
class={[mergedClassName.value, attrs.class]}
getPopupContainer={getPopupContainer || getContextPopupContainer}
dropdownClassName={rcSelectRtlDropDownClassName}
dropdownClassName={rcSelectRtlDropdownClassName}
onChange={triggerChange}
onBlur={handleBlur}
id={id}
@ -218,6 +253,7 @@ const Select = defineComponent({
tagRender={props.tagRender || slots.tagRender}
optionLabelRender={slots.optionLabel}
maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder}
showArrow={hasFeedback || showArrow}
></RcSelect>
);
};

View File

@ -57,14 +57,16 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value`。 | string | `children` \| `label`(设置 options 时) | |
| options | options 数据,如果设置则不需要手动构造 selectOption 节点 | array&lt;{value, label, [disabled, key, title]}> | \[] | |
| placeholder | 选择框默认文字 | string\|slot | - | |
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | 3.3.0 |
| removeIcon | 自定义的多选框清除图标 | VNode \| slot | - | |
| searchValue | 控制搜索文本 | string | - | |
| showArrow | 是否显示下拉小箭头 | boolean | true | |
| showSearch | 使单选模式可搜索 | boolean | false | |
| size | 选择框大小,可选 `large` `small` | string | default | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 |
| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - | |
| tagRender | 自定义 tag 内容 render仅在 `mode``multiple``tags` 时生效 | slot \| (props) => any | - | 3.0 |
| tokenSeparators | 在 tags 和 multiple 模式下自动分词的分隔符 | string\[] | | |
| tokenSeparators | 自动分词的分隔符,仅在 `mode="tags"` 时生效 | string\[] | - | |
| value(v-model) | 指定当前选中的条目 | string\|string\[]\|number\|number\[] | - | |
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 3.0 |
@ -114,7 +116,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
### 点击 `dropdownRender` 里的内容浮层关闭怎么办?
看下 [dropdownRender 例子](/components/select-cn/#components-select-demo-custom-dropdown) 里的说明。
自定义内容点击时会关闭浮层,如果不喜欢关闭,可以添加 `@mousedown.prevent` 进行阻止。 看下 [dropdownRender 例子](/components/select-cn/#components-select-demo-custom-dropdown) 里的说明。
### 为什么 `placeholder` 不显示

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;
@ -12,7 +13,7 @@
position: relative;
background-color: @select-background;
border: @border-width-base @border-style-base @select-border-color;
border-radius: @border-radius-base;
border-radius: @control-border-radius;
transition: all 0.3s @ease-in-out;
input {
@ -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;
@ -145,6 +147,10 @@
.@{select-prefix-cls}-disabled & {
cursor: not-allowed;
}
> *:not(:last-child) {
margin-inline-end: @padding-xs;
}
}
// ========================== Clear ==========================
@ -315,6 +321,10 @@
border-color: transparent !important;
box-shadow: none !important;
}
&&-in-form-item {
width: 100%;
}
}
@import './rtl';

View File

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

View File

@ -110,7 +110,7 @@
cursor: pointer;
> .@{iconfont-css-prefix} {
vertical-align: -0.2em;
vertical-align: middle;
}
&:hover {

View File

@ -0,0 +1,48 @@
@import '../../input/style/mixin';
@select-prefix-cls: ~'@{ant-prefix}-select';
.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} {
&-status-error {
.select-status-color(@error-color, @error-color, @select-background, @error-color-hover, @error-color-outline);
}
&-status-warning {
.select-status-color(@warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
}
&-status-error,
&-status-warning,
&-status-success,
&-status-validating {
&.@{select-prefix-cls}-has-feedback {
//.@{prefix-cls}-arrow,
.@{select-prefix-cls}-clear {
right: 32px;
}
.@{select-prefix-cls}-selection-selected-value {
padding-right: 42px;
}
}
}
}

View File

@ -6,7 +6,7 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
export default function getIcons(props: any, slots: any = {}) {
const { loading, multiple, prefixCls } = props;
const { loading, multiple, prefixCls, hasFeedback, feedbackIcon, showArrow } = props;
const suffixIcon = props.suffixIcon || (slots.suffixIcon && slots.suffixIcon());
const clearIcon = props.clearIcon || (slots.clearIcon && slots.clearIcon());
const menuItemSelectedIcon =
@ -17,20 +17,26 @@ export default function getIcons(props: any, slots: any = {}) {
if (!clearIcon) {
mergedClearIcon = <CloseCircleFilled />;
}
// Validation Feedback Icon
const getSuffixIconNode = arrowIcon => (
<>
{showArrow !== false && arrowIcon}
{hasFeedback && feedbackIcon}
</>
);
// 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 class={iconCls} />;
return getSuffixIconNode(<SearchOutlined class={iconCls} />);
}
return <DownOutlined class={iconCls} />;
return getSuffixIconNode(<DownOutlined class={iconCls} />);
};
}