feat: select add status & placement
parent
af9371fe6f
commit
527dec6078
|
@ -1,7 +1,7 @@
|
|||
<docs>
|
||||
---
|
||||
order: 19
|
||||
version: 4.19.0
|
||||
version: 3.3.0
|
||||
title:
|
||||
zh-CN: 自定义状态
|
||||
en-US: Status
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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<{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?
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<{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` 不显示 ?
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -3,3 +3,5 @@ import './index.less';
|
|||
|
||||
// style dependencies
|
||||
import '../../empty/style';
|
||||
|
||||
// deps-lint-skip: form
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
cursor: pointer;
|
||||
|
||||
> .@{iconfont-css-prefix} {
|
||||
vertical-align: -0.2em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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} />);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue