mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
feat(components): [form-item]: add label-position
prop (#17111)
* feat(components): [form-item]: add `label-position` prop * docs(components): form-item label-position docs * test(components): form-item closed form-item label-position test * Update docs/en-US/component/form.md Co-authored-by: btea <2356281422@qq.com> * Update docs/en-US/component/form.md Co-authored-by: btea <2356281422@qq.com> * Update docs/en-US/component/form.md Co-authored-by: btea <2356281422@qq.com> * fix(components): form-item line-height is overridden by the form style fix(components): form-item label-position style is invalid when label-width is auto * docs(components): update form and form-item alignment examples * Update docs/en-US/component/form.md Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com> * Update docs/en-US/component/form.md Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com> --------- Co-authored-by: btea <2356281422@qq.com> Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>
This commit is contained in:
parent
e63d906d44
commit
91ee8606fb
@ -45,7 +45,7 @@ form/inline-form
|
||||
|
||||
## Alignment
|
||||
|
||||
Depending on your design, there are several different ways to align your label element.
|
||||
Depending on your design, there are several different ways to align your label element. You can set the position of `el-form-item` separately using `label-position` ^(2.7.7). If the value is not set, the `label-position` of `el-form` is used.
|
||||
|
||||
:::demo The `label-position` attribute decides how labels align, it can be `top` or `left`. When set to `top`, labels will be placed at the top of the form field.
|
||||
|
||||
@ -171,19 +171,20 @@ form/accessibility
|
||||
|
||||
### FormItem Attributes
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ------- |
|
||||
| prop | A key of `model`. It could be a path of the property (e.g `a.b.0` or `['a', 'b', '0']`). In the use of `validate` and `resetFields` method, the attribute is required. | ^[string] / ^[string[]] | — |
|
||||
| label | Label text. | ^[string] | — |
|
||||
| label-width | Width of label, e.g. `'50px'`. `'auto'` is supported. | ^[string] / ^[number] | '' |
|
||||
| required | Whether the field is required or not, will be determined by validation rules if omitted. | ^[boolean] | — |
|
||||
| rules | Validation rules of form, see the [following table](#formitemrule), more advanced usage at [async-validator](https://github.com/yiminghe/async-validator). | ^[object]`Arrayable<FormItemRule>` | — |
|
||||
| error | Field error message, set its value and the field will validate error and show this message immediately. | ^[string] | — |
|
||||
| show-message | Whether to show the error message. | ^[boolean] | true |
|
||||
| inline-message | Inline style validate message. | ^[string] / ^[boolean] | '' |
|
||||
| size | Control the size of components in this form-item. | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
|
||||
| for | Same as for in native label. | ^[string] | — |
|
||||
| validate-status | Validation state of formItem. | ^[enum]`'' \| 'error' \| 'validating' \| 'success'` | — |
|
||||
| Name | Description | Type | Default |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ------- |
|
||||
| prop | A key of `model`. It could be a path of the property (e.g `a.b.0` or `['a', 'b', '0']`). In the use of `validate` and `resetFields` method, the attribute is required. | ^[string] / ^[string[]] | — |
|
||||
| label | Label text. | ^[string] | — |
|
||||
| label-position ^(2.7.7) | Position of item label. If set to `'left'` or `'right'`, `label-width` prop is also required. Default extend `label-postion` of `form`. | ^[enum]`'left' \| 'right' \| 'top'` | '' |
|
||||
| label-width | Width of label, e.g. `'50px'`. `'auto'` is supported. | ^[string] / ^[number] | '' |
|
||||
| required | Whether the field is required or not, will be determined by validation rules if omitted. | ^[boolean] | — |
|
||||
| rules | Validation rules of form, see the [following table](#formitemrule), more advanced usage at [async-validator](https://github.com/yiminghe/async-validator). | ^[object]`Arrayable<FormItemRule>` | — |
|
||||
| error | Field error message, set its value and the field will validate error and show this message immediately. | ^[string] | — |
|
||||
| show-message | Whether to show the error message. | ^[boolean] | true |
|
||||
| inline-message | Inline style validate message. | ^[string] / ^[boolean] | '' |
|
||||
| size | Control the size of components in this form-item. | ^[enum]`'' \| 'large' \| 'default' \| 'small'` | — |
|
||||
| for | Same as for in native label. | ^[string] | — |
|
||||
| validate-status | Validation state of formItem. | ^[enum]`'' \| 'error' \| 'validating' \| 'success'` | — |
|
||||
|
||||
#### FormItemRule
|
||||
|
||||
|
@ -1,9 +1,23 @@
|
||||
<template>
|
||||
<el-radio-group v-model="labelPosition" aria-label="label position">
|
||||
<el-radio-button value="left">Left</el-radio-button>
|
||||
<el-radio-button value="right">Right</el-radio-button>
|
||||
<el-radio-button value="top">Top</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div style="margin: 20px">
|
||||
<span>Form Align</span>
|
||||
<el-radio-group v-model="labelPosition" aria-label="label position">
|
||||
<el-radio-button value="left">Left</el-radio-button>
|
||||
<el-radio-button value="right">Right</el-radio-button>
|
||||
<el-radio-button value="top">Top</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div>
|
||||
<span>First Form Item Align</span>
|
||||
<el-radio-group
|
||||
v-model="itemLabelPosition"
|
||||
aria-label="form item label position"
|
||||
>
|
||||
<el-radio-button value="left">Left</el-radio-button>
|
||||
<el-radio-button value="right">Right</el-radio-button>
|
||||
<el-radio-button value="top">Top</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div style="margin: 20px" />
|
||||
<el-form
|
||||
:label-position="labelPosition"
|
||||
@ -11,7 +25,7 @@
|
||||
:model="formLabelAlign"
|
||||
style="max-width: 600px"
|
||||
>
|
||||
<el-form-item label="Name">
|
||||
<el-form-item label="Name" :label-position="itemLabelPosition">
|
||||
<el-input v-model="formLabelAlign.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Activity zone">
|
||||
@ -28,7 +42,7 @@ import { reactive, ref } from 'vue'
|
||||
import type { FormProps } from 'element-plus'
|
||||
|
||||
const labelPosition = ref<FormProps['labelPosition']>('right')
|
||||
|
||||
const itemLabelPosition = ref<FormProps['labelPosition']>('right')
|
||||
const formLabelAlign = reactive({
|
||||
name: '',
|
||||
region: '',
|
||||
|
@ -132,4 +132,40 @@ describe('ElFormItem', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('form-item label position', () => {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
const form = reactive({
|
||||
name: '',
|
||||
nickName: '',
|
||||
address: '',
|
||||
})
|
||||
return () => (
|
||||
<div>
|
||||
<Form model={form}>
|
||||
<FormItem labelPosition="right" ref="labelRight">
|
||||
<Input v-model={form.name} />
|
||||
</FormItem>
|
||||
<FormItem labelPosition="left" ref="labelLeft">
|
||||
<Input v-model={form.nickName} />
|
||||
</FormItem>
|
||||
<FormItem labelPosition="top" ref="labelTop">
|
||||
<Input v-model={form.address} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
expect(wrapper.findComponent({ ref: 'labelTop' }).classes()).toContain(
|
||||
'el-form-item--label-top'
|
||||
)
|
||||
expect(wrapper.findComponent({ ref: 'labelLeft' }).classes()).toContain(
|
||||
'el-form-item--label-left'
|
||||
)
|
||||
expect(wrapper.findComponent({ ref: 'labelRight' }).classes()).toContain(
|
||||
'el-form-item--label-right'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -27,6 +27,14 @@ export const formItemProps = buildProps({
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description Position of label. If set to `'left'` or `'right'`, `label-width` prop is also required. The default is extend from `form label-position`.
|
||||
*/
|
||||
labelPosition: {
|
||||
type: String,
|
||||
values: ['left', 'right', 'top', ''],
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* @description A key of `model`. It could be an array of property paths (e.g `['a', 'b', '0']`). In the use of `validate` and `resetFields` method, the attribute is required.
|
||||
*/
|
||||
|
@ -100,8 +100,12 @@ const formItemRef = ref<HTMLDivElement>()
|
||||
let initialValue: any = undefined
|
||||
let isResettingField = false
|
||||
|
||||
const labelPosition = computed(
|
||||
() => props.labelPosition || formContext?.labelPosition
|
||||
)
|
||||
|
||||
const labelStyle = computed<CSSProperties>(() => {
|
||||
if (formContext?.labelPosition === 'top') {
|
||||
if (labelPosition.value === 'top') {
|
||||
return {}
|
||||
}
|
||||
|
||||
@ -111,7 +115,7 @@ const labelStyle = computed<CSSProperties>(() => {
|
||||
})
|
||||
|
||||
const contentStyle = computed<CSSProperties>(() => {
|
||||
if (formContext?.labelPosition === 'top' || formContext?.inline) {
|
||||
if (labelPosition.value === 'top' || formContext?.inline) {
|
||||
return {}
|
||||
}
|
||||
if (!props.label && !props.labelWidth && isNested) {
|
||||
@ -135,7 +139,10 @@ const formItemClasses = computed(() => [
|
||||
formContext?.requireAsteriskPosition === 'right'
|
||||
? 'asterisk-right'
|
||||
: 'asterisk-left',
|
||||
{ [ns.m('feedback')]: formContext?.statusIcon },
|
||||
{
|
||||
[ns.m('feedback')]: formContext?.statusIcon,
|
||||
[ns.m(`label-${labelPosition.value}`)]: labelPosition.value,
|
||||
},
|
||||
])
|
||||
|
||||
const _inlineMessage = computed(() =>
|
||||
|
@ -95,8 +95,12 @@ export default defineComponent({
|
||||
0,
|
||||
Number.parseInt(autoLabelWidth, 10) - computedWidth.value
|
||||
)
|
||||
const labelPosition =
|
||||
formItemContext.labelPosition || formContext.labelPosition
|
||||
|
||||
const marginPosition =
|
||||
formContext.labelPosition === 'left' ? 'marginRight' : 'marginLeft'
|
||||
labelPosition === 'left' ? 'marginRight' : 'marginLeft'
|
||||
|
||||
if (marginWidth) {
|
||||
style[marginPosition] = `${marginWidth}px`
|
||||
}
|
||||
|
@ -58,24 +58,6 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
@include b(form) {
|
||||
@include set-component-css-var('form', $form);
|
||||
|
||||
@include m(label-left) {
|
||||
.#{$namespace}-form-item__label {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
@include m(label-top) {
|
||||
.#{$namespace}-form-item {
|
||||
display: block;
|
||||
|
||||
.#{$namespace}-form-item__label {
|
||||
display: block;
|
||||
height: auto;
|
||||
text-align: left;
|
||||
margin-bottom: #{map.get($form-item-label-top-margin-bottom, 'default')};
|
||||
line-height: #{map.get($form-item-label-top-line-height, 'default')};
|
||||
}
|
||||
}
|
||||
}
|
||||
@include m(inline) {
|
||||
.#{$namespace}-form-item {
|
||||
display: inline-flex;
|
||||
@ -92,19 +74,6 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $size in (large, default, small) {
|
||||
@include m($size) {
|
||||
&.#{$namespace}-form--label-top {
|
||||
.#{$namespace}-form-item {
|
||||
.#{$namespace}-form-item__label {
|
||||
margin-bottom: #{map.get($form-item-label-top-margin-bottom, $size)};
|
||||
line-height: #{map.get($form-item-label-top-line-height, $size)};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include b(form-item) {
|
||||
@ -131,15 +100,35 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
height: #{map.get($form-item-line-height, $size)};
|
||||
line-height: #{map.get($form-item-line-height, $size)};
|
||||
}
|
||||
|
||||
@include e(content) {
|
||||
line-height: #{map.get($form-item-line-height, $size)};
|
||||
}
|
||||
|
||||
@include e(error) {
|
||||
padding-top: #{map.get($form-item-error-padding-top, $size)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include m(label-left) {
|
||||
.#{$namespace}-form-item__label {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@include m(label-top) {
|
||||
display: block;
|
||||
|
||||
.#{$namespace}-form-item__label {
|
||||
display: block;
|
||||
height: auto;
|
||||
text-align: left;
|
||||
margin-bottom: #{map.get($form-item-label-top-margin-bottom, 'default')};
|
||||
line-height: #{map.get($form-item-label-top-line-height, 'default')};
|
||||
}
|
||||
}
|
||||
|
||||
@include e(label-wrap) {
|
||||
display: flex;
|
||||
}
|
||||
@ -159,6 +148,7 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
padding: 0 12px 0 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@include e(content) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -173,6 +163,7 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(error) {
|
||||
color: getCssVar('color-danger');
|
||||
font-size: 12px;
|
||||
@ -202,6 +193,7 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.asterisk-right {
|
||||
> .#{$namespace}-form-item__label:after,
|
||||
> .#{$namespace}-form-item__label-wrap
|
||||
@ -232,6 +224,7 @@ $form-item-label-top-margin-bottom: map.merge(
|
||||
box-shadow: 0 0 0 1px transparent inset;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$namespace}-input__validateIcon {
|
||||
color: getCssVar('color-danger');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user