feat(descriptions): add new feature for customized style (#2358)

* feat(descriptions): add new feature for customized style

add props: width, min-width, align, label-align, class-name, label-class-name

re #1976 #2353

* feat: update docs

* feat: update docs

* feat: update

* feat: separate type
This commit is contained in:
kooriookami
2021-07-03 15:01:55 +08:00
committed by GitHub
parent dae1d88e51
commit f2e3dcb68e
14 changed files with 253 additions and 27 deletions

View File

@@ -37,6 +37,50 @@ describe('Descriptions.vue', () => {
expect(wrapper.find('table').classes()).toContain('is-bordered')
})
test('should render align props', () => {
const wrapper = _mount(`
<el-descriptions border>
<el-descriptions-item v-for="item in 3" :label="item" align="right" label-align="center">{{ item }}</el-descriptions-item>
</el-descriptions>
`)
expect(wrapper.find('.el-descriptions__label').classes()).toContain('is-center')
expect(wrapper.find('.el-descriptions__content').classes()).toContain('is-right')
})
test('should render width props', () => {
const wrapper = _mount(`
<el-descriptions border>
<el-descriptions-item v-for="item in 3" :label="item" width="50px" min-width="60px">{{ item }}</el-descriptions-item>
</el-descriptions>
`)
expect(wrapper.find('.el-descriptions__label').attributes('style')).toContain('width: 50px; min-width: 60px;')
expect(wrapper.find('.el-descriptions__content').attributes('style')).toContain('width: 50px; min-width: 60px;')
})
test('should render class props', () => {
const wrapper = _mount(`
<el-descriptions border>
<el-descriptions-item v-for="item in 3" :label="item" class-name="class-name" label-class-name="label-class-name">{{ item }}</el-descriptions-item>
</el-descriptions>
`)
expect(wrapper.find('.el-descriptions__label').classes()).toContain('label-class-name')
expect(wrapper.find('.el-descriptions__content').classes()).toContain('class-name')
})
test('should render width props', () => {
const wrapper = _mount(`
<el-descriptions border>
<el-descriptions-item v-for="item in 3" :label="item" width="50px" min-width="60px">{{ item }}</el-descriptions-item>
</el-descriptions>
`)
expect(wrapper.find('.el-descriptions__label').attributes('style')).toContain('width: 50px; min-width: 60px;')
expect(wrapper.find('.el-descriptions__content').attributes('style')).toContain('width: 50px; min-width: 60px;')
})
test('should render column props', async () => {
const wrapper = _mount(`
<el-descriptions :column="5" :border="border">
@@ -110,7 +154,7 @@ describe('Descriptions.vue', () => {
remarks: ['school', 'hospital'],
}
}, {
onClick () {
onClick() {
this.remarks[0] = CHANGE_VALUE
},
})

View File

@@ -2,4 +2,38 @@ import { defineComponent } from 'vue'
export default defineComponent({
name: 'ElDescriptionsItem',
props: {
label: {
type: String,
default: '1',
},
span: {
type: Number,
default: 1,
},
width: {
type: [String, Number],
default: '',
},
minWidth: {
type: [String, Number],
default: '',
},
align: {
type: String,
default: 'left',
},
labelAlign: {
type: String,
default: '',
},
className: {
type: String,
default: '',
},
labelClassName: {
type: String,
default: '',
},
},
})

View File

@@ -1,5 +1,10 @@
import { computed, defineComponent, h, inject } from 'vue'
import { elDescriptionsKey, IDescriptionsInject } from './descriptions.type'
import { defineComponent, h, inject } from 'vue'
import { addUnit } from '@element-plus/utils/util'
import { getNormalizedProps } from '@element-plus/utils/vnode'
import { elDescriptionsKey } from './descriptions.type'
import type { VNode } from 'vue'
import type { IDescriptionsInject, IDescriptionsItemInject } from './descriptions.type'
export default defineComponent({
name: 'ElDescriptionsCell',
@@ -14,42 +19,53 @@ export default defineComponent({
type: String,
},
},
setup(props) {
setup() {
const descriptions = inject(elDescriptionsKey, {} as IDescriptionsInject)
const label = computed(() => props.cell?.children?.label?.() || props.cell?.props?.label)
const content = computed(() => props.cell?.children?.default?.())
const span = computed(() => props.cell?.props?.span || 1)
return {
descriptions,
label: label,
content: content,
span: span,
}
},
render() {
const item = getNormalizedProps(this.cell as VNode) as IDescriptionsItemInject
const label = this.cell?.children?.label?.() || item.label
const content = this.cell?.children?.default?.()
const span = item.span
const align = item.align ? `is-${item.align}` : ''
const labelAlign = item.labelAlign ? `is-${item.labelAlign}` : '' || align
const className = item.className
const labelClassName = item.labelClassName
const style = {
width: addUnit(item.width),
minWidth: addUnit(item.minWidth),
}
switch (this.type) {
case 'label':
return h(this.tag, {
class: ['el-descriptions__label', { 'is-bordered-label': this.descriptions.border }],
colSpan: this.descriptions.direction === 'vertical' ? this.span : 1,
}, this.label)
style: style,
class: ['el-descriptions__label', { 'is-bordered-label': this.descriptions.border }, labelAlign, labelClassName],
colSpan: this.descriptions.direction === 'vertical' ? span : 1,
}, label)
case 'content':
return h(this.tag, {
class: 'el-descriptions__content',
colSpan: this.descriptions.direction === 'vertical' ? this.span : this.span * 2 - 1,
}, this.content)
style: style,
class: ['el-descriptions__content', align, className],
colSpan: this.descriptions.direction === 'vertical' ? span : span * 2 - 1,
}, content)
default:
return h('td', {
colSpan: this.span,
style: style,
class: [align],
colSpan: span,
}, [
h('span', {
class: ['el-descriptions__label', { 'is-bordered-label': this.descriptions.border }],
}, this.label),
class: ['el-descriptions__label', labelClassName],
}, label),
h('span', {
class: 'el-descriptions__content',
}, this.content)])
class: ['el-descriptions__content', className],
}, content)])
}
},
})

View File

@@ -30,7 +30,8 @@
<script lang="ts">
import { defineComponent, inject } from 'vue'
import DescriptionsCell from './descriptions-cell'
import { elDescriptionsKey, IDescriptionsInject } from './descriptions.type'
import { elDescriptionsKey } from './descriptions.type'
import type { IDescriptionsInject } from './descriptions.type'
export default defineComponent({
name: 'ElDescriptionsRow',

View File

@@ -11,7 +11,22 @@ interface IDescriptionsInject {
const elDescriptionsKey: InjectionKey<IDescriptionsInject> = 'elDescriptions' as any
interface IDescriptionsItemInject {
label: string
span: number
width: string | number
minWidth: string | number
align: string
labelAlign: string
className: string
labelClassName: string
}
const elDescriptionsItemKey: InjectionKey<IDescriptionsItemInject> = 'elDescriptionsItem' as any
export {
IDescriptionsInject,
elDescriptionsKey,
IDescriptionsItemInject,
elDescriptionsItemKey,
}

View File

@@ -22,17 +22,17 @@
</template>
<script lang="ts">
import { computed, defineComponent, PropType, provide } from 'vue'
import { computed, defineComponent, provide } from 'vue'
import { isValidComponentSize } from '@element-plus/utils/validators'
import DescriptionsItem from '@element-plus/descriptions-item'
import DescriptionsRow from './descriptions-row.vue'
import { useGlobalConfig } from '@element-plus/utils/util'
import { elDescriptionsKey } from './descriptions.type'
import type { PropType } from 'vue'
export default defineComponent({
name: 'ElDescriptions',
components: {
[DescriptionsItem.name]: DescriptionsItem,
[DescriptionsRow.name]: DescriptionsRow,
},
props: {

View File

@@ -28,9 +28,22 @@
width: 100%;
th, td {
box-sizing: border-box;
text-align: left;
font-weight: normal;
line-height: 1.5;
@include when(left) {
text-align: left;
}
@include when(center) {
text-align: center;
}
@include when(right) {
text-align: right;
}
}
}
}

View File

@@ -1,11 +1,15 @@
import { Fragment, Text, Comment, createBlock, openBlock, createCommentVNode } from 'vue'
import { Fragment, Text, Comment, createBlock, openBlock, createCommentVNode, isVNode, camelize } from 'vue'
import type { VNode, VNodeTypes, VNodeChild } from 'vue'
import { hasOwn } from '@vue/shared'
import { warn } from './error'
type Children = VNodeTypes[] | VNodeTypes
const TEMPLATE = 'template'
export const SCOPE = 'VNode'
export enum PatchFlags {
TEXT = 1,
CLASS = 2,
@@ -87,3 +91,30 @@ export function renderBlock(
) {
return (openBlock(), createBlock(node, props, children, patchFlag, patchProps))
}
/**
* todo
* get normalized props from VNode
* @param node
*/
export const getNormalizedProps = (node: VNode) => {
if (!isVNode(node)) {
warn(SCOPE, 'value must be a VNode')
return
}
const raw = node.props || {}
const type = node.type?.props || {}
const props = {}
Object.keys(type).forEach(key => {
if (hasOwn(type[key], 'default')) {
props[key] = type[key].default
}
})
Object.keys(raw).forEach(key => {
props[camelize(key)] = raw[key]
})
return props
}

View File

@@ -2,4 +2,12 @@
.margin-top {
margin-top: 20px;
}
.my-label {
background: #E1F3D8;
}
.my-content {
background: #FDE2E2;
}
}

View File

@@ -126,6 +126,23 @@ Display multiple fields in list form.
```
:::
### Customized Style
:::demo
```html
<el-descriptions title="Customized style list" :column="3" border>
<el-descriptions-item label="Username" label-align="right" align="center" label-class-name="my-label" class-name="my-content" width="150px">kooriookami</el-descriptions-item>
<el-descriptions-item label="Telephone" label-align="right" align="center">18100000000</el-descriptions-item>
<el-descriptions-item label="Place" label-align="right" align="center">Suzhou</el-descriptions-item>
<el-descriptions-item label="Remarks" label-align="right" align="center">
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item label="Address" label-align="right" align="center">No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province</el-descriptions-item>
</el-descriptions>
```
:::
### Descriptions Attributes
| Attribute | Description | Type | Accepted Values | Default |
|------------- |---------------- |---------------- |---------------------- |-------- |
@@ -148,6 +165,12 @@ Display multiple fields in list form.
|------------- |---------------- |---------------- |---------------------- |-------- |
| label | label text | string | — | — |
| span | colspan of column | number | — | 1 |
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
| align | column content alignment | string | left / center / right | left |
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
| class-name | column content custom class name | string | — | — |
| label-class-name | column label custom class name | string | — | — |
### Descriptions Item Slots

View File

@@ -148,6 +148,12 @@ Display multiple fields in list form.
|------------- |---------------- |---------------- |---------------------- |-------- |
| label | label text | string | — | — |
| span | colspan of column | number | — | 1 |
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
| align | column content alignment | string | left / center / right | left |
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
| class-name | column content custom class name | string | — | — |
| label-class-name | column label custom class name | string | — | — |
### Descriptions Item Slots

View File

@@ -148,6 +148,12 @@ Display multiple fields in list form.
|------------- |---------------- |---------------- |---------------------- |-------- |
| label | label text | string | — | — |
| span | colspan of column | number | — | 1 |
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
| align | column content alignment | string | left / center / right | left |
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
| class-name | column content custom class name | string | — | — |
| label-class-name | column label custom class name | string | — | — |
### Descriptions Item Slots

View File

@@ -148,6 +148,12 @@ Display multiple fields in list form.
|------------- |---------------- |---------------- |---------------------- |-------- |
| label | label text | string | — | — |
| span | colspan of column | number | — | 1 |
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
| align | column content alignment | string | left / center / right | left |
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
| class-name | column content custom class name | string | — | — |
| label-class-name | column label custom class name | string | — | — |
### Descriptions Item Slots

View File

@@ -126,6 +126,23 @@
```
:::
### 自定义样式
:::demo
```html
<el-descriptions title="自定义样式列表" :column="3" border>
<el-descriptions-item label="用户名" label-align="right" align="center" label-class-name="my-label" class-name="my-content" width="150px">kooriookami</el-descriptions-item>
<el-descriptions-item label="手机号" label-align="right" align="center">18100000000</el-descriptions-item>
<el-descriptions-item label="居住地" label-align="right" align="center">苏州市</el-descriptions-item>
<el-descriptions-item label="备注" label-align="right" align="center">
<el-tag size="small">学校</el-tag>
</el-descriptions-item>
<el-descriptions-item label="联系地址" label-align="right" align="center">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item>
</el-descriptions>
```
:::
### Descriptions Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|------------- |---------------- |---------------- |---------------------- |-------- |
@@ -148,6 +165,12 @@
|------------- |---------------- |---------------- |---------------------- |-------- |
| label | 标签文本 | string | — | — |
| span | 列的数量 | number | — | 1 |
| width | 列的宽度,不同行相同列的宽度按最大值设定 | string / number | — | — |
| min-width | 列的最小宽度,与 `width` 的区别是 `width` 是固定的,`min-width` 会把剩余宽度按比例分配给设置了 `min-width` 的列 | string / number | — | — |
| align | 列的内容对齐方式 | string | left / center / right | left |
| label-align | 列的标签对齐方式,若不设置该项,则使用内容的对齐方式 | string | left / center / right | — |
| class-name | 列的内容自定义类名 | string | — | — |
| label-class-name | 列的标签自定义类名 | string | — | — |
### Descriptions Item Slots