From 0c520520beff214132efa3b6fc6efa9aed24ab69 Mon Sep 17 00:00:00 2001 From: Amour1688 <31695475+Amour1688@users.noreply.github.com> Date: Mon, 2 Nov 2020 15:52:02 +0800 Subject: [PATCH 1/2] chore: optimize table typescript support (#3087) * chore: optimize table typescript support * chore: update table type * fix: table type error * fix: columns shuold not have default value * fix: sortDirections and scroll should not have default value * chore: rowSelectionType as tuple --- components/table/Column.tsx | 4 +-- components/table/Table.tsx | 50 ++++++++++++++++++----------------- components/table/index.tsx | 4 +-- components/table/interface.ts | 44 +++++++++++++++++++----------- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/components/table/Column.tsx b/components/table/Column.tsx index 671a82ed7..a54fc2fcc 100644 --- a/components/table/Column.tsx +++ b/components/table/Column.tsx @@ -1,9 +1,9 @@ import { defineComponent } from 'vue'; -import { ColumnProps } from './interface'; +import { columnProps } from './interface'; export default defineComponent({ name: 'ATableColumn', - props: ColumnProps, + props: columnProps, render() { return null; }, diff --git a/components/table/Table.tsx b/components/table/Table.tsx index acc22fda3..787f46ef4 100755 --- a/components/table/Table.tsx +++ b/components/table/Table.tsx @@ -17,11 +17,11 @@ import initDefaultProps from '../_util/props-util/initDefaultProps'; import BaseMixin from '../_util/BaseMixin'; import { defaultConfigProvider } from '../config-provider'; import { - TableProps, + tableProps, TableComponents, TableState, - ITableProps, - IColumnProps, + TableProps, + ColumnProps, TableStateFilters, } from './interface'; import Pagination from '../pagination'; @@ -38,15 +38,15 @@ function stopPropagation(e) { e.stopPropagation(); } -function getRowSelection(props: ITableProps) { +function getRowSelection(props: TableProps) { return props.rowSelection || {}; } -function getColumnKey(column: IColumnProps, index?: number) { +function getColumnKey(column: ColumnProps, index?: number) { return column.key || column.dataIndex || index; } -function isSameColumn(a: IColumnProps, b: IColumnProps): boolean { +function isSameColumn(a: ColumnProps, b: ColumnProps): boolean { if (a && b && a.key && a.key === b.key) { return true; } @@ -94,16 +94,16 @@ function isTheSameComponents(components1: TableComponents = {}, components2: Tab ); } -function getFilteredValueColumns(state: TableState, columns?: IColumnProps) { +function getFilteredValueColumns(state: TableState, columns?: ColumnProps) { return flatFilter( columns || (state || {}).columns || [], - (column: IColumnProps) => typeof column.filteredValue !== 'undefined', + (column: ColumnProps) => typeof column.filteredValue !== 'undefined', ); } -function getFiltersFromColumns(state: TableState, columns: IColumnProps) { +function getFiltersFromColumns(state: TableState, columns: ColumnProps) { const filters = {}; - getFilteredValueColumns(state, columns).forEach((col: IColumnProps) => { + getFilteredValueColumns(state, columns).forEach((col: ColumnProps) => { const colKey = getColumnKey(col); filters[colKey] = col.filteredValue; }); @@ -117,26 +117,28 @@ function isFiltersChanged(state: TableState, filters: TableStateFilters[]) { return Object.keys(filters).some(columnKey => filters[columnKey] !== state.filters[columnKey]); } +export const defaultTableProps = initDefaultProps(tableProps, { + dataSource: [], + useFixedHeader: false, + // rowSelection: null, + size: 'default', + loading: false, + bordered: false, + indentSize: 20, + locale: {}, + rowKey: 'key', + showHeader: true, + sortDirections: ['ascend', 'descend'], + childrenColumnName: 'children', +}); + export default defineComponent({ name: 'Table', mixins: [BaseMixin], inheritAttrs: false, Column, ColumnGroup, - props: initDefaultProps(TableProps, { - dataSource: [], - useFixedHeader: false, - // rowSelection: null, - size: 'default', - loading: false, - bordered: false, - indentSize: 20, - locale: {}, - rowKey: 'key', - showHeader: true, - sortDirections: ['ascend', 'descend'], - childrenColumnName: 'children', - }), + props: defaultTableProps, setup() { return { diff --git a/components/table/index.tsx b/components/table/index.tsx index 720dbf88a..a096b6df4 100644 --- a/components/table/index.tsx +++ b/components/table/index.tsx @@ -1,5 +1,5 @@ import { App, defineComponent } from 'vue'; -import T from './Table'; +import T, { defaultTableProps } from './Table'; import Column from './Column'; import ColumnGroup from './ColumnGroup'; import {} from './interface'; @@ -9,7 +9,7 @@ const Table = defineComponent({ name: 'ATable', Column: T.Column, ColumnGroup: T.ColumnGroup, - props: T.props, + props: defaultTableProps, inheritAttrs: false, methods: { normalize(elements = []) { diff --git a/components/table/interface.ts b/components/table/interface.ts index f916128d3..6821e4b5b 100644 --- a/components/table/interface.ts +++ b/components/table/interface.ts @@ -1,21 +1,21 @@ +import { ExtractPropTypes, PropType } from 'vue'; import PropTypes, { withUndefined } from '../_util/vue-types'; import { PaginationProps as getPaginationProps } from '../pagination'; import { SpinProps as getSpinProps } from '../spin'; import { Store } from './createStore'; import { tuple } from '../_util/type'; -import { ExtractPropTypes } from 'vue'; const PaginationProps = getPaginationProps(); const SpinProps = getSpinProps(); -// export type CompareFn = ((a: T, b: T) => number); +export type CompareFn = (a: T, b: T, sortOrder?: SortOrder) => number; export const ColumnFilterItem = PropTypes.shape({ text: PropTypes.string, value: PropTypes.string, children: PropTypes.array, }).loose; -export const ColumnProps = { +export const columnProps = { title: PropTypes.VNodeChild, key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), dataIndex: PropTypes.string, @@ -52,7 +52,7 @@ export const ColumnProps = { // onHeaderCell?: (props: ColumnProps) => any; }; -export type IColumnProps = Partial>; +export type ColumnProps = Partial>; export interface TableComponents { table?: any; @@ -80,10 +80,10 @@ export const TableLocale = PropTypes.shape({ collapse: PropTypes.string, }).loose; -export const RowSelectionType = PropTypes.oneOf(['checkbox', 'radio']); +export const RowSelectionType = PropTypes.oneOf(tuple('checkbox', 'radio')); // export type SelectionSelectFn = (record: T, selected: boolean, selectedRows: Object[]) => any; -export const TableRowSelection = { +export const tableRowSelection = { type: RowSelectionType, selectedRowKeys: PropTypes.array, // onChange?: (selectedRowKeys: string[] | number[], selectedRows: Object[]) => any; @@ -101,10 +101,12 @@ export const TableRowSelection = { columnTitle: PropTypes.any, }; -export const TableProps = { +export type SortOrder = 'descend' | 'ascend'; + +export const tableProps = { prefixCls: PropTypes.string, dropdownPrefixCls: PropTypes.string, - rowSelection: PropTypes.oneOfType([PropTypes.shape(TableRowSelection).loose, Object]), + rowSelection: PropTypes.oneOfType([PropTypes.shape(tableRowSelection).loose, Object]), pagination: withUndefined( PropTypes.oneOfType([ PropTypes.shape({ @@ -117,7 +119,9 @@ export const TableProps = { size: PropTypes.oneOf(tuple('default', 'middle', 'small', 'large')), dataSource: PropTypes.array, components: PropTypes.object, - columns: PropTypes.array, + columns: { + type: Array as PropType, + }, rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), rowClassName: PropTypes.func, expandedRowRender: PropTypes.any, @@ -137,10 +141,18 @@ export const TableProps = { showHeader: PropTypes.looseBool, footer: PropTypes.func, title: PropTypes.func, - scroll: PropTypes.object, + scroll: { + type: Object as PropType<{ + x?: boolean | number | string; + y?: boolean | number | string; + scrollToFirstRowOnChange?: boolean; + }>, + }, childrenColumnName: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), - bodyStyle: PropTypes.any, - sortDirections: PropTypes.array, + bodyStyle: PropTypes.style, + sortDirections: { + type: Array as PropType, + }, tableLayout: PropTypes.string, getPopupContainer: PropTypes.func, expandIcon: PropTypes.func, @@ -153,9 +165,9 @@ export const TableProps = { // children?: React.ReactNode; }; -export type ITableRowSelection = Partial>; +export type TableRowSelection = Partial>; -export type ITableProps = Partial>; +export type TableProps = Partial>; export interface TableStateFilters { [key: string]: string[]; @@ -164,9 +176,9 @@ export interface TableStateFilters { export interface TableState { pagination?: Partial>; filters?: TableStateFilters; - sortColumn?: Partial> | null; + sortColumn?: ColumnProps | null; sortOrder?: string; - columns?: IColumnProps[]; + columns?: ColumnProps[]; } // export type SelectionItemSelectFn = (key: string[]) => any; From 0e78488ac36231408c9617cfb245ffb6e8677969 Mon Sep 17 00:00:00 2001 From: zkwolf Date: Mon, 2 Nov 2020 16:53:56 +0800 Subject: [PATCH 2/2] feat: update descriptions (#3046) * feat: update descriptions * Update index.tsx * chore: update descriptions type * refactor: descriptions use composition-api * fix: decsriptions.item support label slot * chore: update descriptions * test: update snapshot Co-authored-by: Amour1688 <31695475+Amour1688@users.noreply.github.com> --- components/descriptions/Cell.tsx | 55 +++ components/descriptions/Col.tsx | 82 ---- components/descriptions/Row.tsx | 109 +++++ .../__snapshots__/index.test.js.snap | 56 ++- .../descriptions/__tests__/index.test.js | 92 ++++- components/descriptions/index.tsx | 380 ++++++++---------- components/descriptions/style/index.less | 53 ++- components/style/themes/default.less | 9 + 8 files changed, 499 insertions(+), 337 deletions(-) create mode 100644 components/descriptions/Cell.tsx delete mode 100644 components/descriptions/Col.tsx create mode 100644 components/descriptions/Row.tsx diff --git a/components/descriptions/Cell.tsx b/components/descriptions/Cell.tsx new file mode 100644 index 000000000..e5a1bc50f --- /dev/null +++ b/components/descriptions/Cell.tsx @@ -0,0 +1,55 @@ +import { VNodeTypes, HTMLAttributes, FunctionalComponent } from 'vue'; + +function notEmpty(val: any) { + return val !== undefined && val !== null; +} + +interface CellProps extends HTMLAttributes { + itemPrefixCls: string; + span: number; + component: string; + bordered?: boolean; + label?: VNodeTypes; + content?: VNodeTypes; + colon?: boolean; +} + +const Cell: FunctionalComponent = props => { + const { itemPrefixCls, component, span, bordered, label, content, colon } = props; + const Component = component as any; + if (bordered) { + return ( + + {notEmpty(label) ? label : content} + + ); + } + + return ( + + {label && ( + + {label} + + )} + {content && {content}} + + ); +}; + +export default Cell; diff --git a/components/descriptions/Col.tsx b/components/descriptions/Col.tsx deleted file mode 100644 index af2fde9b5..000000000 --- a/components/descriptions/Col.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { SetupContext, VNode } from 'vue'; -import { getOptionProps } from '../_util/props-util'; - -interface ColProps { - child: VNode; - bordered: boolean; - colon: boolean; - type?: 'label' | 'content'; - layout?: 'horizontal' | 'vertical'; - colKey?: string; -} -const Col = (_props: ColProps, { attrs }: SetupContext) => { - const { - child = {} as VNode, - bordered, - colon, - type, - layout, - colKey: key, - } = (attrs as unknown) as ColProps; - const { prefixCls, span = 1 } = getOptionProps(child); - const { children = {} as any, props = {} } = child; - const label = props.label || (children.label && children.label()); - const defaultSlot = children.default && children.default(); - - const labelProps: any = { - class: [ - `${prefixCls}-item-label`, - { - [`${prefixCls}-item-colon`]: colon, - [`${prefixCls}-item-no-label`]: !label, - }, - ], - key: `${key}-label`, - }; - - if (layout === 'vertical') { - labelProps.colspan = span * 2 - 1; - } - - if (bordered) { - if (type === 'label') { - return {label}; - } - return ( - - {defaultSlot} - - ); - } - if (layout === 'vertical') { - if (type === 'content') { - return ( - - - {defaultSlot} - - - ); - } - return ( - - - {label} - - - ); - } - return ( - - {label} - - {defaultSlot} - - - ); -}; - -export default Col; diff --git a/components/descriptions/Row.tsx b/components/descriptions/Row.tsx new file mode 100644 index 000000000..ff8400023 --- /dev/null +++ b/components/descriptions/Row.tsx @@ -0,0 +1,109 @@ +import Cell from './Cell'; +import { getOptionProps, getSlot, getClass, getStyle, getComponent } from '../_util/props-util'; +import { FunctionalComponent } from 'vue'; + +interface CellConfig { + component: string | [string, string]; + type: string; + showLabel?: boolean; + showContent?: boolean; +} + +export interface RowProps { + prefixCls: string; + vertical: boolean; + row: any[]; + bordered: boolean; + colon: boolean; + index: number; +} + +const Row: FunctionalComponent = props => { + const renderCells = ( + items, + { colon, prefixCls, bordered }, + { component, type, showLabel, showContent }: CellConfig, + ) => { + return items.map((item, index) => { + const { prefixCls: itemPrefixCls = prefixCls, span = 1 } = getOptionProps(item); + const label = getComponent(item, 'label'); + + const children = getSlot(item); + const className = getClass(item); + const style = getStyle(item); + const { key } = item; + + if (typeof component === 'string') { + return ( + + ); + } + + return [ + , + , + ]; + }); + }; + + const { prefixCls, vertical, row, index, bordered } = props; + if (vertical) { + return ( + <> + + {renderCells(row, props, { component: 'th', type: 'label', showLabel: true })} + + + {renderCells(row, props, { + component: 'td', + type: 'content', + showContent: true, + })} + + + ); + } + + return ( + + {renderCells(row, props, { + component: bordered ? ['th', 'td'] : 'td', + type: 'item', + showLabel: true, + showContent: true, + })} + + ); +}; + +export default Row; diff --git a/components/descriptions/__tests__/__snapshots__/index.test.js.snap b/components/descriptions/__tests__/__snapshots__/index.test.js.snap index 23b077b59..6f3b850de 100644 --- a/components/descriptions/__tests__/__snapshots__/index.test.js.snap +++ b/components/descriptions/__tests__/__snapshots__/index.test.js.snap @@ -7,7 +7,7 @@ exports[`Descriptions Descriptions support colon 1`] = ` - +
ProductCloud DatabaseProductCloud Database
@@ -22,7 +22,9 @@ exports[`Descriptions Descriptions support style 1`] = ` - +
Cloud Database + Cloud Database +
@@ -37,7 +39,7 @@ exports[`Descriptions Descriptions.Item support className 1`] = ` - +
ProductCloud DatabaseProductCloud Database
@@ -52,12 +54,12 @@ exports[`Descriptions column is number 1`] = ` - - - + + + - +
ProductCloud DatabaseBillingPrepaidtime18:00:00ProductCloud DatabaseBillingPrepaidtime18:00:00
Amount$80.00Amount$80.00
@@ -72,20 +74,36 @@ exports[`Descriptions vertical layout 1`] = ` - - - + + + - - - + + + - + - +
ProductBillingtimeProduct + + Billing + + time + +
Cloud DatabasePrepaid18:00:00 + Cloud Database + + Prepaid + + 18:00:00 +
AmountAmount + +
$80.00 + $80.00 +
@@ -100,12 +118,12 @@ exports[`Descriptions when item is rendered conditionally 1`] = ` - - - + + + - +
ProductCloud DatabaseBillingPrepaidtime18:00:00ProductCloud DatabaseBillingPrepaidtime18:00:00
Amount$80.00Amount$80.00
diff --git a/components/descriptions/__tests__/index.test.js b/components/descriptions/__tests__/index.test.js index c80c51528..ade7754d9 100644 --- a/components/descriptions/__tests__/index.test.js +++ b/components/descriptions/__tests__/index.test.js @@ -33,7 +33,9 @@ describe('Descriptions', () => { { sync: false, attachTo: 'body' }, ); await asyncExpect(() => { - expect(wrapper.vm.$refs.descriptions.getColumn()).toBe(8); + expect( + wrapper.findAll('td').reduce((total, td) => total + parseInt(td.attributes().colspan), 0), + ).toBe(8); }, 100); wrapper.unmount(); }); @@ -74,7 +76,7 @@ describe('Descriptions', () => { }, }); expect(errorSpy).toHaveBeenCalledWith( - 'Warning: [antdv: Descriptions] Sum of column `span` in a line exceeds `column` of Descriptions.', + 'Warning: [antdv: Descriptions] Sum of column `span` in a line not match `column` of Descriptions.', ); }); @@ -120,7 +122,7 @@ describe('Descriptions', () => { render() { return ( - + Cloud Database @@ -176,7 +178,7 @@ describe('Descriptions', () => { ); await asyncExpect(() => { expect(wrapper.findAll('tr')).toHaveLength(5); - expect(wrapper.findAll('.ant-descriptions-item-no-label')).toHaveLength(1); + expect(wrapper.findAll('.ant-descriptions-item-label')).toHaveLength(4); }); wrapper.unmount(); @@ -198,8 +200,86 @@ describe('Descriptions', () => { }, { sync: false, attachTo: 'body' }, ); - await asyncExpect(() => {}); - expect(wrapper.findAll('tr')).toHaveLength(2); + await asyncExpect(() => { + expect(wrapper.findAll('tr')).toHaveLength(2); + }); wrapper.unmount(); }); + + it('columns 5 with customize', () => { + const wrapper = mount({ + render() { + return ( + + {/* 1 1 1 1 */} + bamboo + bamboo + bamboo + bamboo + {/* 2 2 */} + + bamboo + + + bamboo + + {/* 3 1 */} + + bamboo + + bamboo + + ); + }, + }); + + function matchSpan(rowIndex, spans) { + const tr = wrapper.findAll('tr')[rowIndex]; + const tds = tr.findAll('th'); + expect(tds.length).toEqual(spans.length); + tds.forEach((td, index) => { + expect(parseInt(td.attributes().colspan)).toEqual(spans[index]); + }); + } + + matchSpan(0, [1, 1, 1, 1]); + matchSpan(2, [2, 2]); + matchSpan(4, [3, 1]); + }); + + it('number value should render correct', () => { + const wrapper = mount({ + render() { + return ( + + {0} + + ); + }, + }); + + expect(wrapper.find('th').classes()).toContain('ant-descriptions-item-label'); + expect(wrapper.find('td').classes()).toContain('ant-descriptions-item-content'); + }); + + it('Descriptions support extra', async () => { + const wrapper = mount({ + render() { + return ( + + Zhou Maomao + + ); + }, + }); + + await asyncExpect(() => { + expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(true); + 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 53b445265..45fb0c473 100644 --- a/components/descriptions/index.tsx +++ b/components/descriptions/index.tsx @@ -1,12 +1,27 @@ -import { inject, cloneVNode, App, defineComponent, PropType, VNode, Plugin } from 'vue'; +import { + inject, + ref, + App, + defineComponent, + PropType, + VNode, + HTMLAttributes, + ExtractPropTypes, + onMounted, + onBeforeUnmount, +} from 'vue'; import warning from '../_util/warning'; -import ResponsiveObserve, { Breakpoint, responsiveArray } from '../_util/responsiveObserve'; +import ResponsiveObserve, { + Breakpoint, + responsiveArray, + ScreenMap, +} from '../_util/responsiveObserve'; import { defaultConfigProvider } from '../config-provider'; -import Col from './Col'; +import Row from './Row'; import PropTypes from '../_util/vue-types'; -import { getOptionProps, getComponent, isValidElement, getSlot } from '../_util/props-util'; -import BaseMixin from '../_util/BaseMixin'; -import { tuple, VueNode } from '../_util/type'; +import { tuple } from '../_util/type'; +import { cloneElement } from '../_util/vnode'; +import { filterEmpty } from '../_util/props-util'; export const DescriptionsItemProps = { prefixCls: PropTypes.string, @@ -14,16 +29,6 @@ export const DescriptionsItemProps = { span: PropTypes.number, }; -function toArray(value: any) { - let ret = value; - if (value === undefined) { - ret = []; - } else if (!Array.isArray(value)) { - ret = [value]; - } - return ret; -} - export const DescriptionsItem = { name: 'ADescriptionsItem', props: { @@ -36,7 +41,7 @@ export const DescriptionsItem = { }, }; -const defaultColumnMap = { +const DEFAULT_COLUMN_MAP: Record = { xxl: 3, xl: 3, lg: 3, @@ -45,229 +50,172 @@ const defaultColumnMap = { xs: 1, }; -export const DescriptionsProps = { +function getColumn(column: DescriptionsProps['column'], screens: ScreenMap): number { + if (typeof column === 'number') { + return column; + } + + if (typeof column === 'object') { + for (let i = 0; i < responsiveArray.length; i++) { + const breakpoint: Breakpoint = responsiveArray[i]; + if (screens[breakpoint] && column[breakpoint] !== undefined) { + return column[breakpoint] || DEFAULT_COLUMN_MAP[breakpoint]; + } + } + } + + return 3; +} + +function getFilledItem(node: VNode, span: number | undefined, rowRestCol: number): VNode { + let clone = node; + + if (span === undefined || span > rowRestCol) { + clone = cloneElement(node, { + span: rowRestCol, + }); + + warning( + span === undefined, + 'Descriptions', + 'Sum of column `span` in a line not match `column` of Descriptions.', + ); + } + + return clone; +} + +function getRows(children: VNode[], column: number) { + const childNodes = filterEmpty(children); + const rows: VNode[][] = []; + + let tmpRow: VNode[] = []; + let rowRestCol = column; + + childNodes.forEach((node, index) => { + const span: number | undefined = node.props?.span; + const mergedSpan = span || 1; + + // Additional handle last one + if (index === childNodes.length - 1) { + tmpRow.push(getFilledItem(node, span, rowRestCol)); + rows.push(tmpRow); + return; + } + + if (mergedSpan < rowRestCol) { + rowRestCol -= mergedSpan; + tmpRow.push(node); + } else { + tmpRow.push(getFilledItem(node, mergedSpan, rowRestCol)); + rows.push(tmpRow); + rowRestCol = column; + tmpRow = []; + } + }); + + return rows; +} + +const descriptionsProps = { prefixCls: PropTypes.string, bordered: PropTypes.looseBool, size: PropTypes.oneOf(tuple('default', 'middle', 'small')).def('default'), title: PropTypes.VNodeChild, + extra: PropTypes.VNodeChild, column: { type: [Number, Object] as PropType>>, - default: () => defaultColumnMap, + default: () => DEFAULT_COLUMN_MAP, }, layout: PropTypes.oneOf(tuple('horizontal', 'vertical')), colon: PropTypes.looseBool, }; -/** - * Convert children into `column` groups. - * @param children: DescriptionsItem - * @param column: number - */ -const generateChildrenRows = (children: VueNode, column: number) => { - const rows = []; - let columns = null; - let leftSpans: number; +export type DescriptionsProps = HTMLAttributes & + Partial>; - const itemNodes = toArray(children); - itemNodes.forEach((node: VNode, index: number) => { - const itemProps = getOptionProps(node); - let itemNode = node; - - if (!columns) { - leftSpans = column; - columns = []; - rows.push(columns); - } - - // Always set last span to align the end of Descriptions - const lastItem = index === itemNodes.length - 1; - let lastSpanSame = true; - if (lastItem) { - lastSpanSame = !itemProps.span || itemProps.span === leftSpans; - itemNode = cloneVNode(itemNode, { - span: leftSpans, - }); - } - - // Calculate left fill span - const { span = 1 } = itemProps; - columns.push(itemNode); - leftSpans -= span; - - if (leftSpans <= 0) { - columns = null; - - warning( - leftSpans === 0 && lastSpanSame, - 'Descriptions', - 'Sum of column `span` in a line exceeds `column` of Descriptions.', - ); - } - }); - - return rows; -}; - -const Descriptions = defineComponent({ +const Descriptions = defineComponent({ name: 'ADescriptions', Item: DescriptionsItem, - mixins: [BaseMixin], - props: DescriptionsProps, - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - data() { - return { - screens: {}, - token: undefined, - }; - }, - methods: { - getColumn() { - const { column } = this.$props; - if (typeof column === 'object') { - for (let i = 0; i < responsiveArray.length; i++) { - const breakpoint = responsiveArray[i]; - if (this.screens[breakpoint] && column[breakpoint] !== undefined) { - return column[breakpoint] || defaultColumnMap[breakpoint]; - } + setup(props, { slots }) { + const { getPrefixCls } = inject('configProvider', defaultConfigProvider); + + let token: number; + + const screens = ref({}); + + onMounted(() => { + token = ResponsiveObserve.subscribe(screen => { + if (typeof props.column !== 'object') { + return; } - } - // If the configuration is not an object, it is a number, return number - if (typeof column === 'number') { - return column; - } - // If it is an object, but no response is found, this happens only in the test. - // Maybe there are some strange environments - return 3; - }, - renderRow( - children: VNode[], - index: number, - { prefixCls }: { prefixCls: string }, - bordered: boolean, - layout: 'horizontal' | 'vertical', - colon: boolean, - ) { - const renderCol = (colItem: VNode, type: 'label' | 'content', idx: number) => { - return ( - - ); - }; - const cloneChildren = []; - const cloneContentChildren = []; - toArray(children).forEach((childrenItem: VNode, idx: number) => { - cloneChildren.push(renderCol(childrenItem, 'label', idx)); - if (layout === 'vertical') { - cloneContentChildren.push(renderCol(childrenItem, 'content', idx)); - } else if (bordered) { - cloneChildren.push(renderCol(childrenItem, 'content', idx)); - } - }); - - if (layout === 'vertical') { - return [ - - {cloneChildren} - , - - {cloneContentChildren} - , - ]; - } - - return ( - - {cloneChildren} - - ); - }, - }, - mounted() { - const { column } = this.$props; - this.token = ResponsiveObserve.subscribe(screens => { - if (typeof column !== 'object') { - return; - } - this.setState({ - screens, + screens.value = screen; }); }); - }, - beforeUnmount() { - ResponsiveObserve.unsubscribe(this.token); - }, - render() { - const { - prefixCls: customizePrefixCls, - size, - bordered = false, - layout = 'horizontal', - colon = true, - } = this.$props; - const title = getComponent(this, 'title'); - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('descriptions', customizePrefixCls); - const column = this.getColumn(); - const children = getSlot(this); - const cloneChildren = toArray(children) - .map((child: VNode) => { - if (isValidElement(child)) { - return cloneVNode(child, { + onBeforeUnmount(() => { + ResponsiveObserve.unsubscribe(token); + }); + + return () => { + const { + prefixCls: customizePrefixCls, + column, + size, + bordered = false, + layout = 'horizontal', + colon = true, + title = slots.title?.(), + extra = slots.extra?.(), + } = props; + + const prefixCls = getPrefixCls('descriptions', customizePrefixCls); + const mergeColumn = getColumn(column, screens.value); + const children = slots.default?.(); + const rows = getRows(children, mergeColumn); + + return ( +
node); - - const childrenArray = generateChildrenRows(cloneChildren, column); - return ( -
- {title &&
{title}
} -
- - - {childrenArray.map((child, index) => - this.renderRow( - child, - index, - { - prefixCls, - }, - bordered, - layout, - colon, - ), - )} - -
+ { + [`${prefixCls}-${size}`]: size !== 'default', + [`${prefixCls}-bordered`]: !!bordered, + }, + ]} + > + {(title || extra) && ( +
+
{title}
+
{extra}
+
+ )} +
+ + + {rows.map((row, index) => ( + + ))} + +
+
-
- ); + ); + }; }, }); +Descriptions.props = descriptionsProps; + Descriptions.install = function(app: App) { app.component(Descriptions.name, Descriptions); app.component(Descriptions.Item.name, Descriptions.Item); diff --git a/components/descriptions/style/index.less b/components/descriptions/style/index.less index 155468ef0..ad4bdd689 100644 --- a/components/descriptions/style/index.less +++ b/components/descriptions/style/index.less @@ -3,17 +3,28 @@ @descriptions-prefix-cls: ~'@{ant-prefix}-descriptions'; -@descriptions-default-padding: 16px 24px; -@descriptions-middle-padding: 12px 24px; -@descriptions-small-padding: 8px 16px; - .@{descriptions-prefix-cls} { + &-header { + display: flex; + align-items: center; + margin-bottom: @descriptions-title-margin-bottom; + } + &-title { - margin-bottom: 20px; + flex: auto; + overflow: hidden; color: @heading-color; font-weight: bold; font-size: @font-size-lg; line-height: @line-height-base; + white-space: nowrap; + text-overflow: ellipsis; + } + + &-extra { + margin-left: auto; + color: @descriptions-extra-color; + font-size: @font-size-base; } &-view { @@ -29,7 +40,7 @@ &-row { > th, > td { - padding-bottom: 16px; + padding-bottom: @descriptions-item-padding-bottom; } &:last-child { border-bottom: none; @@ -41,18 +52,24 @@ font-weight: normal; font-size: @font-size-base; line-height: @line-height-base; + text-align: start; &::after { + & when (@descriptions-item-trailing-colon=true) { + content: ':'; + } + & when not (@descriptions-item-trailing-colon=true) { + content: ' '; + } + position: relative; top: -0.5px; - margin: 0 8px 0 2px; - content: ' '; + margin: 0 @descriptions-item-label-colon-margin-right 0 + @descriptions-item-label-colon-margin-left; } - } - &-item-colon { - &::after { - content: ':'; + &.@{descriptions-prefix-cls}-item-no-colon::after { + content: ' '; } } @@ -65,15 +82,23 @@ &-item-content { display: table-cell; + flex: 1; color: @text-color; font-size: @font-size-base; line-height: @line-height-base; + overflow-wrap: break-word; } &-item { padding-bottom: 0; + vertical-align: top; > span { - display: inline-block; + display: inline-flex; + align-items: baseline; + } + + &-container { + display: flex; } } @@ -90,7 +115,7 @@ .@{descriptions-prefix-cls}-row { > th, > td { - padding-bottom: 8px; + padding-bottom: @padding-xs; } } } diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 37e0e0519..e77fd7d12 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -192,6 +192,15 @@ // Descriptions @descriptions-bg: #fafafa; +@descriptions-title-margin-bottom: 20px; +@descriptions-default-padding: @padding-md @padding-lg; +@descriptions-middle-padding: @padding-sm @padding-lg; +@descriptions-small-padding: @padding-xs @padding-md; +@descriptions-item-padding-bottom: @padding-md; +@descriptions-item-trailing-colon: true; +@descriptions-item-label-colon-margin-right: 8px; +@descriptions-item-label-colon-margin-left: 2px; +@descriptions-extra-color: @text-color; // Dropdown @dropdown-selected-color: @primary-color;