diff --git a/components/descriptions/Cell.tsx b/components/descriptions/Cell.tsx index e5a1bc50f..5040a8a3e 100644 --- a/components/descriptions/Cell.tsx +++ b/components/descriptions/Cell.tsx @@ -1,4 +1,4 @@ -import { VNodeTypes, HTMLAttributes, FunctionalComponent } from 'vue'; +import { VNodeTypes, HTMLAttributes, FunctionalComponent, CSSProperties } from 'vue'; function notEmpty(val: any) { return val !== undefined && val !== null; @@ -8,6 +8,8 @@ interface CellProps extends HTMLAttributes { itemPrefixCls: string; span: number; component: string; + labelStyle?: CSSProperties; + contentStyle?: CSSProperties; bordered?: boolean; label?: VNodeTypes; content?: VNodeTypes; @@ -15,7 +17,17 @@ interface CellProps extends HTMLAttributes { } const Cell: FunctionalComponent = props => { - const { itemPrefixCls, component, span, bordered, label, content, colon } = props; + const { + itemPrefixCls, + component, + span, + labelStyle, + contentStyle, + bordered, + label, + content, + colon, + } = props; const Component = component as any; if (bordered) { return ( @@ -28,26 +40,34 @@ const Cell: FunctionalComponent = props => { ]} colSpan={span} > - {notEmpty(label) ? label : content} + {notEmpty(label) && {label}} + {notEmpty(content) && {content}} ); } return ( - {label && ( - - {label} - - )} - {content && {content}} +
+ {label && ( + + {label} + + )} + {content && ( + + {content} + + )} +
); }; diff --git a/components/descriptions/Row.tsx b/components/descriptions/Row.tsx index 31af6d391..f6f39e341 100644 --- a/components/descriptions/Row.tsx +++ b/components/descriptions/Row.tsx @@ -1,6 +1,7 @@ import Cell from './Cell'; import { getOptionProps, getSlot, getClass, getStyle, getComponent } from '../_util/props-util'; -import { FunctionalComponent, VNode } from 'vue'; +import { FunctionalComponent, VNode, inject } from 'vue'; +import { descriptionsContext, DescriptionsContextProp } from './index'; interface CellConfig { component: string | [string, string]; @@ -22,10 +23,22 @@ const Row: FunctionalComponent = props => { const renderCells = ( items: VNode[], { colon, prefixCls, bordered }, - { component, type, showLabel, showContent }: CellConfig, + { + component, + type, + showLabel, + showContent, + labelStyle: rootLabelStyle, + contentStyle: rootContentStyle, + }: CellConfig & DescriptionsContextProp, ) => { return items.map((item, index) => { - const { prefixCls: itemPrefixCls = prefixCls, span = 1 } = getOptionProps(item); + const { + prefixCls: itemPrefixCls = prefixCls, + span = 1, + labelStyle, + contentStyle, + } = getOptionProps(item); const label = getComponent(item, 'label'); const children = getSlot(item); @@ -39,6 +52,8 @@ const Row: FunctionalComponent = props => { key={`${type}-${key || index}`} class={className} style={style} + labelStyle={{ ...rootLabelStyle, ...labelStyle }} + contentStyle={{ ...rootContentStyle, ...contentStyle }} span={span} colon={colon} component={component} @@ -54,7 +69,7 @@ const Row: FunctionalComponent = props => { = props => { = props => { }; const { prefixCls, vertical, row, index, bordered } = props; + const { labelStyle, contentStyle } = inject(descriptionsContext, { + labelStyle: undefined, + contentStyle: undefined, + }); if (vertical) { return ( <> - {renderCells(row, props, { component: 'th', type: 'label', showLabel: true })} + {renderCells(row, props, { + component: 'th', + type: 'label', + showLabel: true, + labelStyle: labelStyle.value, + contentStyle: contentStyle.value, + })} {renderCells(row, props, { component: 'td', type: 'content', showContent: true, + labelStyle: labelStyle.value, + contentStyle: contentStyle.value, })} @@ -101,6 +128,8 @@ const Row: FunctionalComponent = props => { type: 'item', showLabel: true, showContent: true, + labelStyle: labelStyle.value, + contentStyle: contentStyle.value, })} ); diff --git a/components/descriptions/__tests__/__snapshots__/index.test.js.snap b/components/descriptions/__tests__/__snapshots__/index.test.js.snap index 6f3b850de..8a82f0059 100644 --- a/components/descriptions/__tests__/__snapshots__/index.test.js.snap +++ b/components/descriptions/__tests__/__snapshots__/index.test.js.snap @@ -7,7 +7,9 @@ exports[`Descriptions Descriptions support colon 1`] = ` - +
ProductCloud Database +
ProductCloud Database
+
@@ -23,7 +25,9 @@ exports[`Descriptions Descriptions support style 1`] = ` - Cloud Database +
+ Cloud Database +
@@ -39,7 +43,9 @@ exports[`Descriptions Descriptions.Item support className 1`] = ` - +
ProductCloud Database +
ProductCloud Database
+
@@ -54,12 +60,20 @@ exports[`Descriptions column is number 1`] = ` - - - + + + - +
ProductCloud DatabaseBillingPrepaidtime18:00:00 +
ProductCloud Database
+
+
BillingPrepaid
+
+
time18:00:00
+
Amount$80.00 +
Amount$80.00
+
@@ -74,35 +88,51 @@ exports[`Descriptions vertical layout 1`] = ` - - - - @@ -118,12 +148,20 @@ exports[`Descriptions when item is rendered conditionally 1`] = `
Product - + +
Product + +
Billing - + +
Billing + +
time - + +
time + +
- Cloud Database +
+ Cloud Database +
- Prepaid +
+ Prepaid +
- 18:00:00 +
+ 18:00:00 +
Amount - + +
Amount + +
- $80.00 +
+ $80.00 +
- - - + + + - +
ProductCloud DatabaseBillingPrepaidtime18:00:00 +
ProductCloud Database
+
+
BillingPrepaid
+
+
time18:00:00
+
Amount$80.00 +
Amount$80.00
+
diff --git a/components/descriptions/__tests__/index.test.js b/components/descriptions/__tests__/index.test.js index ade7754d9..84ad00912 100644 --- a/components/descriptions/__tests__/index.test.js +++ b/components/descriptions/__tests__/index.test.js @@ -1,4 +1,5 @@ import { mount } from '@vue/test-utils'; +import { h } from 'vue'; import MockDate from 'mockdate'; import Descriptions from '..'; import { resetWarned } from '../../_util/warning'; @@ -263,21 +264,27 @@ describe('Descriptions', () => { }); it('Descriptions support extra', async () => { - const wrapper = mount({ - render() { - return ( - - Zhou Maomao - - ); + const wrapper = mount(Descriptions, { + props: { + extra: 'Edit', + }, + slots: { + default: h( + Descriptions.Item, + { + label: 'UserName', + }, + 'Zhou Maomao', + ), }, }); await asyncExpect(() => { expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(true); - wrapper.setProps({ extra: undefined }); }); + wrapper.setProps({ extra: undefined }); + await asyncExpect(() => { expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(false); }); diff --git a/components/descriptions/index.tsx b/components/descriptions/index.tsx index 64fd72765..a462363f9 100644 --- a/components/descriptions/index.tsx +++ b/components/descriptions/index.tsx @@ -1,6 +1,6 @@ import { - inject, ref, + Ref, App, defineComponent, PropType, @@ -10,6 +10,9 @@ import { onMounted, onBeforeUnmount, Plugin, + CSSProperties, + provide, + toRef, } from 'vue'; import warning from '../_util/warning'; import ResponsiveObserve, { @@ -17,12 +20,12 @@ import ResponsiveObserve, { responsiveArray, ScreenMap, } from '../_util/responsiveObserve'; -import { defaultConfigProvider } from '../config-provider'; import Row from './Row'; import PropTypes from '../_util/vue-types'; import { tuple } from '../_util/type'; import { cloneElement } from '../_util/vnode'; import { filterEmpty } from '../_util/props-util'; +import useConfigInject from '../_util/hooks/useConfigInject'; export const DescriptionsItemProps = { prefixCls: PropTypes.string, @@ -30,15 +33,21 @@ export const DescriptionsItemProps = { span: PropTypes.number, }; +const descriptionsItemProp = { + prefixCls: PropTypes.string, + label: PropTypes.VNodeChild, + labelStyle: PropTypes.style, + contentStyle: PropTypes.style, + span: PropTypes.number.def(1), +}; + +export type DescriptionsItemProp = Partial>; + export const DescriptionsItem = defineComponent({ name: 'ADescriptionsItem', - props: { - prefixCls: PropTypes.string, - label: PropTypes.VNodeChild, - span: PropTypes.number.def(1), - }, - render() { - return null; + props: descriptionsItemProp, + setup() { + return () => null; }, }); @@ -130,17 +139,26 @@ const descriptionsProps = { }, layout: PropTypes.oneOf(tuple('horizontal', 'vertical')), colon: PropTypes.looseBool, + labelStyle: PropTypes.style, + contentStyle: PropTypes.style, }; export type DescriptionsProps = HTMLAttributes & Partial>; +export interface DescriptionsContextProp { + labelStyle?: Ref; + contentStyle?: Ref; +} + +export const descriptionsContext = Symbol('descriptionsContext'); + const Descriptions = defineComponent({ name: 'ADescriptions', props: descriptionsProps, Item: DescriptionsItem, setup(props, { slots }) { - const { getPrefixCls } = inject('configProvider', defaultConfigProvider); + const { prefixCls, direction } = useConfigInject('descriptions', props); let token: number; @@ -160,9 +178,13 @@ const Descriptions = defineComponent({ ResponsiveObserve.unsubscribe(token); }); + provide(descriptionsContext, { + labelStyle: toRef(props, 'labelStyle'), + contentStyle: toRef(props, 'contentStyle'), + }); + return () => { const { - prefixCls: customizePrefixCls, column, size, bordered = false, @@ -172,7 +194,6 @@ const Descriptions = defineComponent({ extra = slots.extra?.(), } = props; - const prefixCls = getPrefixCls('descriptions', customizePrefixCls); const mergeColumn = getColumn(column, screens.value); const children = slots.default?.(); const rows = getRows(children, mergeColumn); @@ -180,20 +201,21 @@ const Descriptions = defineComponent({ return (
{(title || extra) && ( -
-
{title}
-
{extra}
+
+ {title &&
{title}
} + {extra &&
{extra}
}
)} -
+
{rows.map((row, index) => ( @@ -201,7 +223,7 @@ const Descriptions = defineComponent({ key={index} index={index} colon={colon} - prefixCls={prefixCls} + prefixCls={prefixCls.value} vertical={layout === 'vertical'} bordered={bordered} row={row} diff --git a/components/descriptions/style/index.less b/components/descriptions/style/index.less index ad4bdd689..683420ead 100644 --- a/components/descriptions/style/index.less +++ b/components/descriptions/style/index.less @@ -86,19 +86,22 @@ color: @text-color; font-size: @font-size-base; line-height: @line-height-base; + word-break: break-word; overflow-wrap: break-word; } &-item { padding-bottom: 0; vertical-align: top; - > span { - display: inline-flex; - align-items: baseline; - } &-container { display: flex; + + .@{descriptions-prefix-cls}-item-label, + .@{descriptions-prefix-cls}-item-content { + display: inline-flex; + align-items: baseline; + } } } @@ -167,3 +170,5 @@ } } } + +@import './rtl'; diff --git a/components/descriptions/style/rtl.less b/components/descriptions/style/rtl.less new file mode 100644 index 000000000..7aca94f13 --- /dev/null +++ b/components/descriptions/style/rtl.less @@ -0,0 +1,33 @@ +@import '../../style/themes/default'; +@import '../../style/mixins/index'; + +@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions'; + +.@{descriptions-prefix-cls} { + &-rtl { + direction: rtl; + } + + &-item-label { + &::after { + .@{descriptions-prefix-cls}-rtl & { + margin: 0 @descriptions-item-label-colon-margin-left 0 + @descriptions-item-label-colon-margin-right; + } + } + } + + &-bordered { + .@{descriptions-prefix-cls}-item-label, + .@{descriptions-prefix-cls}-item-content { + .@{descriptions-prefix-cls}-rtl& { + border-right: none; + border-left: 1px solid @border-color-split; + + &:last-child { + border-left: none; + } + } + } + } +}