From 7216ea9f35244ec5ab3a73d57fbc697dea25a56f Mon Sep 17 00:00:00 2001 From: Jelly <29860491+TianlunXiong@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:14:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(table):=20=E6=96=B0=E5=A2=9E=20Table=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20(#472)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(table): 新增 Table 组件 新增 Table 组件 * fix: fix(table): 修复comment问题 * fix: 修复comment问题 * docs(table): 更新Table md文档 * chore: update common --------- Co-authored-by: github-actions[bot] --- site/mobile/mobile.config.js | 5 + site/web/site.config.js | 6 + src/_common | 2 +- src/index.ts | 1 + src/table/BaseTable.tsx | 210 ++++++++++++++++++++++++++++++++ src/table/_example/base.jsx | 61 ++++++++++ src/table/_example/bordered.jsx | 56 +++++++++ src/table/_example/index.jsx | 30 +++++ src/table/_example/scroll.jsx | 39 ++++++ src/table/_example/stripe.jsx | 46 +++++++ src/table/defaultProps.ts | 18 +++ src/table/hooks/useClassName.ts | 189 ++++++++++++++++++++++++++++ src/table/hooks/useStyle.ts | 40 ++++++ src/table/index.tsx | 9 ++ src/table/style/css.js | 1 + src/table/style/index.js | 1 + src/table/table.en-US.md | 43 +++++++ src/table/table.md | 43 +++++++ src/table/type.ts | 200 ++++++++++++++++++++++++++++++ 19 files changed, 999 insertions(+), 1 deletion(-) create mode 100644 src/table/BaseTable.tsx create mode 100644 src/table/_example/base.jsx create mode 100644 src/table/_example/bordered.jsx create mode 100644 src/table/_example/index.jsx create mode 100644 src/table/_example/scroll.jsx create mode 100644 src/table/_example/stripe.jsx create mode 100644 src/table/defaultProps.ts create mode 100644 src/table/hooks/useClassName.ts create mode 100644 src/table/hooks/useStyle.ts create mode 100644 src/table/index.tsx create mode 100644 src/table/style/css.js create mode 100644 src/table/style/index.js create mode 100644 src/table/table.en-US.md create mode 100644 src/table/table.md create mode 100644 src/table/type.ts diff --git a/site/mobile/mobile.config.js b/site/mobile/mobile.config.js index 841cf180..f46775a5 100644 --- a/site/mobile/mobile.config.js +++ b/site/mobile/mobile.config.js @@ -227,5 +227,10 @@ export default { name: 'link', component: () => import('tdesign-mobile-react/link/_example/index.tsx'), }, + { + title: 'Table 表格', + name: 'table', + component: () => import('tdesign-mobile-react/table/_example/index.jsx'), + }, ], }; diff --git a/site/web/site.config.js b/site/web/site.config.js index d5364146..30bb0e7c 100644 --- a/site/web/site.config.js +++ b/site/web/site.config.js @@ -286,6 +286,12 @@ export default { path: '/mobile-react/components/tag', component: () => import('tdesign-mobile-react/tag/tag.md'), }, + { + title: 'Table 表格', + name: 'table', + path: '/mobile-react/components/table', + component: () => import('tdesign-mobile-react/table/table.md'), + }, ], }, { diff --git a/src/_common b/src/_common index 91c9dcb3..074d44f1 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 91c9dcb35500a8ca5bd2e905db3e4672bb480750 +Subproject commit 074d44f1a1b06c4ee97759e3edf6cb3bbef2f61a diff --git a/src/index.ts b/src/index.ts index de363141..5e8bc151 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,7 @@ export * from './swiper'; export * from './swipe-cell'; export * from './tag'; export * from './result'; +export * from './table'; /** * 消息提醒(7个) diff --git a/src/table/BaseTable.tsx b/src/table/BaseTable.tsx new file mode 100644 index 00000000..6abcd5ec --- /dev/null +++ b/src/table/BaseTable.tsx @@ -0,0 +1,210 @@ +import React, { forwardRef, useRef } from 'react'; +import isFunction from 'lodash/isFunction'; +import get from 'lodash/get'; +import cx from 'classnames'; + +import { StyledProps } from '../common'; +import useClassName from './hooks/useClassName'; +import useStyle, { formatCSSUnit } from './hooks/useStyle'; +import useDefaultProps from '../hooks/useDefaultProps'; +import defaultConfig from '../_common/js/global-config/mobile/locale/zh_CN'; +import Loading from '../loading'; +import { baseTableDefaultProps } from './defaultProps'; + +import type { TdBaseTableProps, BaseTableCol, TableRowData, BaseTableCellParams } from './type'; + +export type BaseTableProps = TdBaseTableProps & StyledProps; + +export const BaseTable = forwardRef((props: BaseTableProps, ref: React.Ref) => { + const { + data, + empty, + height, + loading, + loadingProps, + columns, + bordered, + maxHeight, + tableLayout, + showHeader, + cellEmptyContent, + className, + style, + onRowClick, + onCellClick, + onScroll, + } = useDefaultProps(props, baseTableDefaultProps); + + const { tableLayoutClasses, tableHeaderClasses, tableBaseClass, tdAlignClasses, tdEllipsisClass, classPrefix } = + useClassName(); + + const { tableClasses, tableContentStyles, tableElementStyles } = useStyle(props); + + const tableElmClasses = tableLayoutClasses[tableLayout || 'fixed']; + + const theadClasses = cx(tableHeaderClasses.header, { + [tableHeaderClasses.fixed]: Boolean(maxHeight || height), + [tableBaseClass.bordered]: bordered, + }); + + const ellipsisClasses = cx([`${classPrefix}-table__ellipsis`, `${classPrefix}-text-ellipsis`]); + + const defaultColWidth = tableLayout === 'fixed' ? '80px' : undefined; + + const tableContentRef = useRef(); + + const tableElmRef = useRef(); + + const theadRef = useRef(); + + const colStyle = (colItem: BaseTableCol) => ({ + width: `${formatCSSUnit(colItem.width || defaultColWidth)}`, + minWidth: `${ + !formatCSSUnit(colItem.width || defaultColWidth) && !colItem.minWidth && tableLayout === 'fixed' + ? '80px' + : formatCSSUnit(colItem.minWidth) + }`, + }); + + const thClassName = (thItem: BaseTableCol) => { + let className = ''; + if (thItem.colKey) { + className = `${classPrefix}-table__th-${thItem.colKey}`; + } + if (thItem.ellipsisTitle || thItem.ellipsis) { + className = `${className} ${tdEllipsisClass}`; + } + if (thItem.align && thItem.align !== 'left') { + className = `${className} ${tdAlignClasses[`${thItem.align}`]}`; + } + return className; + }; + + const tdClassName = (tdItem: BaseTableCol) => { + let className = ''; + if (tdItem.ellipsis) { + className = tdEllipsisClass; + } + if (tdItem.align && tdItem.align !== 'left') { + className = `${className} ${tdAlignClasses[`${tdItem.align}`]}`; + } + return className; + }; + + const renderCell = ( + params: BaseTableCellParams, + cellEmptyContent?: TdBaseTableProps['cellEmptyContent'], + ) => { + const { col, row, rowIndex } = params; + // support serial number column + if (col.colKey === 'serial-number') { + return rowIndex + 1; + } + + if (isFunction(col.cell)) { + return col.cell(params); + } + + const r = get(row, col.colKey); + // 0 和 false 属于正常可用值,不能使用兜底逻辑 cellEmptyContent + if (![undefined, '', null].includes(r)) return r; + + // cellEmptyContent 作为空数据兜底显示,用户可自定义 + if (cellEmptyContent) { + return isFunction(cellEmptyContent) ? cellEmptyContent(params) : cellEmptyContent; + } + return r; + }; + + const renderTitle = (thItem: BaseTableCol, index: number) => { + if (isFunction(thItem?.title)) { + return thItem?.title({ col: thItem, colIndex: index }); + } + return thItem?.title; + }; + + const handleRowClick = (row: TableRowData, rowIndex: number, e: React.MouseEvent) => { + onRowClick?.({ row, index: rowIndex, e }); + }; + + const handleCellClick = (row: TableRowData, col: any, rowIndex: number, colIndex: number, e: React.MouseEvent) => { + if (col.stopPropagation) { + e.stopPropagation(); + } + onCellClick?.({ row, col, rowIndex, colIndex, e }); + }; + + const renderTableBody = () => { + const renderContentEmpty = empty || defaultConfig?.table?.empty; + + if (!data?.length && renderContentEmpty) { + return ( + + +
{renderContentEmpty}
+ + + ); + } + if (data?.length) { + return data?.map((trItem, trIdx) => ( + { + handleRowClick(trItem, trIdx, ev); + }} + > + {columns?.map((tdItem, tdIdx) => ( + { + handleCellClick(trItem, tdItem, trIdx, tdIdx, $event); + }} + > +
+ {renderCell({ row: trItem, col: tdItem, rowIndex: trIdx, colIndex: tdIdx }, cellEmptyContent)} +
+ + ))} + + )); + } + }; + + return ( +
+
{ + onScroll?.({ e }); + }} + > + + {columns?.map((col) => )} + {showHeader && ( + + + {columns?.map((thItem, idx) => ( + + ))} + + + )} + {renderTableBody()} +
+
+ {renderTitle(thItem, idx)} +
+
+ {loading && ( +
+ +
+ )} +
+
+ ); +}); diff --git a/src/table/_example/base.jsx b/src/table/_example/base.jsx new file mode 100644 index 00000000..4cfcfd6e --- /dev/null +++ b/src/table/_example/base.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Table } from 'tdesign-mobile-react'; + +const data = []; +const total = 10; +for (let i = 0; i < total; i++) { + data.push({ + index: i + 1, + applicant: ['内容', '内容', '内容'][i % 3], + status: ['内容', '内容', '内容'][i % 3], + channel: ['内容', '内容', '内容'][i % 3], + detail: { + email: ['内容', '内容', '内容内容内容'][i % 3], + }, + }); +} + +const columns = [ + { colKey: 'applicant', title: '标题', ellipsis: true, cell: 'type-slot-name' }, + { + colKey: 'status', + title: '标题', + ellipsis: true, + }, + { + colKey: 'channel', + title: '标题', + cell: ({ col, row }) => row[col.colKey], + ellipsis: true, + }, + { + colKey: 'detail.email', + title: '标题', + cell: () => '内容', + ellipsis: true, + }, +]; + +export function BaseExample() { + const handleRowClick = (e) => { + console.log('row-cliek=====', e); + }; + + const handleCellClick = (e) => { + console.log('cell-cliek=====', e); + }; + + return ( +
+
+
+ ); +} diff --git a/src/table/_example/bordered.jsx b/src/table/_example/bordered.jsx new file mode 100644 index 00000000..ab114e88 --- /dev/null +++ b/src/table/_example/bordered.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Table } from 'tdesign-mobile-react'; + +const data = []; +const total = 10; +for (let i = 0; i < total; i++) { + data.push({ + index: i + 1, + applicant: ['内容', '内容', '内容'][i % 3], + status: ['内容', '内容', '内容'][i % 3], + channel: ['内容', '内容', '内容'][i % 3], + detail: { + email: ['内容', '内容', '内容内容内容'][i % 3], + }, + }); +} + +const columns = [ + { colKey: 'applicant', title: '标题', ellipsis: true }, + { + colKey: 'status', + title: '标题', + ellipsis: true, + }, + { colKey: 'channel', title: '标题', ellipsis: true }, + { colKey: 'detail.email', title: '标题', ellipsis: true }, +]; + +export function BorderedExample() { + const handleRowClick = (e) => { + console.log('row-cliek=====', e); + }; + + const handleCellClick = (e) => { + console.log('cell-cliek=====', e); + }; + + const handleScroll = (e) => { + console.log('scroll=====', e); + }; + + return ( +
+
+
+ ); +} diff --git a/src/table/_example/index.jsx b/src/table/_example/index.jsx new file mode 100644 index 00000000..1b517013 --- /dev/null +++ b/src/table/_example/index.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import TDemoBlock from '../../../site/mobile/components/DemoBlock'; +import TDemoHeader from '../../../site/mobile/components/DemoHeader'; +import { BaseExample } from './base'; +import { ScrollExample } from './scroll'; +import { StripeExample } from './stripe'; +import { BorderedExample } from './bordered'; + +export default function Base() { + return ( +
+ + + + + + + + + + + + + +
+ ); +} diff --git a/src/table/_example/scroll.jsx b/src/table/_example/scroll.jsx new file mode 100644 index 00000000..66993417 --- /dev/null +++ b/src/table/_example/scroll.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Table } from 'tdesign-mobile-react'; + +const data = []; +const total = 5; +for (let i = 0; i < total; i++) { + data.push({ + index: i + 1, + applicant: ['横向平铺内容不省略', '内容', '内容'][i % 3], + status: ['横向平铺内容不省略', '内容', '内容'][i % 3], + channel: ['横向平铺内容不省略', '内容', '内容'][i % 3], + detail: { + email: ['横向平铺内容不省略', '内容', '内容'][i % 3], + }, + }); +} + +const columns = [ + { colKey: 'applicant', title: '标题', width: 180 }, + { + colKey: 'status', + title: '标题', + width: 180, + }, + { colKey: 'channel', title: '标题', width: 180 }, + { colKey: 'detail.email', title: '标题', width: 180 }, +]; + +export function ScrollExample() { + const handleScroll = (e) => { + console.log('scroll=====', e); + }; + + return ( +
+
+
+ ); +} diff --git a/src/table/_example/stripe.jsx b/src/table/_example/stripe.jsx new file mode 100644 index 00000000..54c1d2dd --- /dev/null +++ b/src/table/_example/stripe.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Table } from 'tdesign-mobile-react'; + +const data = []; +const total = 10; +for (let i = 0; i < total; i++) { + data.push({ + index: i + 1, + applicant: ['内容', '内容', '内容'][i % 3], + status: ['内容', '内容', '内容'][i % 3], + channel: ['内容', '内容', '内容'][i % 3], + detail: { + email: ['内容', '内容', '内容'][i % 3], + }, + }); +} + +const columns = [ + { colKey: 'applicant', title: '标题' }, + { + colKey: 'status', + title: '标题', + }, + { colKey: 'channel', title: '标题' }, + { colKey: 'detail.email', title: '标题', ellipsis: true }, +]; + +export function StripeExample() { + const handleScroll = (e) => { + console.log('scroll=====', e); + }; + + return ( +
+
+
+ ); +} diff --git a/src/table/defaultProps.ts b/src/table/defaultProps.ts new file mode 100644 index 00000000..28ec7b85 --- /dev/null +++ b/src/table/defaultProps.ts @@ -0,0 +1,18 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdBaseTableProps } from './type'; + +export const baseTableDefaultProps: TdBaseTableProps = { + bordered: false, + columns: [], + data: [], + empty: '', + loading: undefined, + rowKey: 'id', + showHeader: true, + stripe: false, + tableLayout: 'fixed', + verticalAlign: 'middle', +}; diff --git a/src/table/hooks/useClassName.ts b/src/table/hooks/useClassName.ts new file mode 100644 index 00000000..4c97a780 --- /dev/null +++ b/src/table/hooks/useClassName.ts @@ -0,0 +1,189 @@ +import useConfig from '../../_util/useConfig'; + +export default function useClassName() { + const { classPrefix } = useConfig(); + const classNames = { + classPrefix, + tableBaseClass: { + table: `${classPrefix}-table`, + columnResizableTable: `${classPrefix}-table--column-resizable`, + overflowVisible: `${classPrefix}-table--overflow-visible`, + body: `${classPrefix}-table__body`, + content: `${classPrefix}-table__content`, + topContent: `${classPrefix}-table__top-content`, + bottomContent: `${classPrefix}-table__bottom-content`, + paginationWrap: `${classPrefix}-table__pagination-wrap`, + tdLastRow: `${classPrefix}-table__td-last-row`, + tdFirstCol: `${classPrefix}-table__td-first-col`, + thCellInner: `${classPrefix}-table__th-cell-inner`, + tableRowEdit: `${classPrefix}-table--row-edit`, + cellEditable: `${classPrefix}-table__cell--editable`, + cellEditWrap: `${classPrefix}-table__cell-wrap`, + bordered: `${classPrefix}-table--bordered`, + striped: `${classPrefix}-table--striped`, + hover: `${classPrefix}-table--hoverable`, + loading: `${classPrefix}-table--loading`, + rowspanAndColspan: `${classPrefix}-table--rowspan-colspan`, + empty: `${classPrefix}-table__empty`, + emptyRow: `${classPrefix}-table__empty-row`, + headerFixed: `${classPrefix}-table--header-fixed`, + columnFixed: `${classPrefix}-table--column-fixed`, + widthOverflow: `${classPrefix}-table--width-overflow`, + multipleHeader: `${classPrefix}-table--multiple-header`, + footerAffixed: `${classPrefix}-table--footer-affixed`, + horizontalBarAffixed: `${classPrefix}-table--horizontal-bar-affixed`, + affixedHeader: `${classPrefix}-table--affixed-header`, + affixedHeaderElm: `${classPrefix}-table__affixed-header-elm`, + affixedFooterElm: `${classPrefix}-table__affixed-footer-elm`, + affixedFooterWrap: `${classPrefix}-table__affixed-footer-wrap`, + // 边框模式,固定表头,横向滚动时,右侧添加边线,分隔滚动条 + scrollbarDivider: `${classPrefix}-table__scroll-bar-divider`, + // 当用户设置 height 为固定高度,为保证行元素铺满 table,则需设置 table 元素高度为 100% + fullHeight: `${classPrefix}-table--full-height`, + // 拖拽列时的标记线 + resizeLine: `${classPrefix}-table__resize-line`, + obviousScrollbar: `${classPrefix}-table__scrollbar--obvious`, + affixedHeaderWrap: `${classPrefix}-table__affixed-header-elm-wrap`, + }, + + tdAlignClasses: { + left: `${classPrefix}-align-left`, + right: `${classPrefix}-align-right`, + center: `${classPrefix}-align-center`, + }, + + tableHeaderClasses: { + header: `${classPrefix}-table__header`, + thBordered: `${classPrefix}-table__header-th--bordered`, + fixed: `${classPrefix}-table__header--fixed`, + multipleHeader: `${classPrefix}-table__header--multiple`, + }, + + tableFooterClasses: { + footer: `${classPrefix}-table__footer`, + fixed: `${classPrefix}-table__footer--fixed`, + }, + + tableAlignClasses: { + top: `${classPrefix}-vertical-align-top`, + middle: `${classPrefix}-vertical-align-middle`, + bottom: `${classPrefix}-vertical-align-bottom`, + }, + + tableRowFixedClasses: { + top: `${classPrefix}-table__row--fixed-top`, + bottom: `${classPrefix}-table__row--fixed-bottom`, + firstBottom: `${classPrefix}-table__row--fixed-bottom-first`, + withoutBorderBottom: `${classPrefix}-table__row--without-border-bottom`, + }, + + tableColFixedClasses: { + left: `${classPrefix}-table__cell--fixed-left`, + right: `${classPrefix}-table__cell--fixed-right`, + lastLeft: `${classPrefix}-table__cell--fixed-left-last`, + firstRight: `${classPrefix}-table__cell--fixed-right-first`, + leftShadow: `${classPrefix}-table__content--scrollable-to-left`, + rightShadow: `${classPrefix}-table__content--scrollable-to-right`, + }, + + tableLayoutClasses: { + auto: `${classPrefix}-table--layout-auto`, + fixed: `${classPrefix}-table--layout-fixed`, + }, + + tdEllipsisClass: `${classPrefix}-table-td--ellipsis`, + + // 行通栏,一列铺满整行 + tableFullRowClasses: { + base: `${classPrefix}-table__row--full`, + innerFullRow: `${classPrefix}-table__row-full-inner`, + innerFullElement: `${classPrefix}-table__row-full-element`, + firstFullRow: `${classPrefix}-table__first-full-row`, + lastFullRow: `${classPrefix}-table__last-full-row`, + }, + + // 展开/收起行,全部类名 + tableExpandClasses: { + iconBox: `${classPrefix}-table__expand-box`, + iconCell: `${classPrefix}-table__expandable-icon-cell`, + row: `${classPrefix}-table__expanded-row`, + rowInner: `${classPrefix}-table__expanded-row-inner`, + expanded: `${classPrefix}-table__row--expanded`, + collapsed: `${classPrefix}-table__row--collapsed`, + }, + + // 排序功能,全部类名 + tableSortClasses: { + sortable: `${classPrefix}-table__cell--sortable`, + sortColumn: `${classPrefix}-table__sort-column`, + title: `${classPrefix}-table__cell--title`, + trigger: `${classPrefix}-table__cell--sort-trigger`, + doubleIcon: `${classPrefix}-table__double-icons`, + sortIcon: `${classPrefix}-table__sort-icon`, + iconDirection: { + asc: `${classPrefix}-table-sort-asc`, + desc: `${classPrefix}-table-sort-desc`, + }, + iconActive: `${classPrefix}-table__sort-icon--active`, + iconDefault: `${classPrefix}-icon-sort--default`, + }, + + // 行选中功能,全部类名 + tableSelectedClasses: { + selected: `${classPrefix}-table__row--selected`, + disabled: `${classPrefix}-table__row--disabled`, + checkCell: `${classPrefix}-table__cell-check`, + }, + + // 过滤功能,全部类名 + tableFilterClasses: { + filterable: `${classPrefix}-table__cell--filterable`, + popup: `${classPrefix}-table__filter-pop`, + icon: `${classPrefix}-table__filter-icon`, + popupContent: `${classPrefix}-table__filter-pop-content`, + result: `${classPrefix}-table__filter-result`, + inner: `${classPrefix}-table__row-filter-inner`, + bottomButtons: `${classPrefix}-table__filter--bottom-buttons`, + contentInner: `${classPrefix}-table__filter-pop-content-inner`, + iconWrap: `${classPrefix}-table__filter-icon-wrap`, + }, + + // 通用类名 + asyncLoadingClass: `${classPrefix}-table__async-loading`, + isFocusClass: `${classPrefix}-is-focus`, + isLoadingClass: `${classPrefix}-is-loading`, + isLoadMoreClass: `${classPrefix}-is-load-more`, + + // 树形结构类名 + tableTreeClasses: { + col: `${classPrefix}-table__tree-col`, + inlineCol: `${classPrefix}-table__tree-col--inline`, + icon: `${classPrefix}-table__tree-op-icon`, + leafNode: `${classPrefix}-table__tree-leaf-node`, + }, + + // 拖拽功能类名 + tableDraggableClasses: { + rowDraggable: `${classPrefix}-table--row-draggable`, + rowHandlerDraggable: `${classPrefix}-table--row-handler-draggable`, + colDraggable: `${classPrefix}-table--col-draggable`, + handle: `${classPrefix}-table__handle-draggable`, + ghost: `${classPrefix}-table__ele--draggable-ghost`, + chosen: `${classPrefix}-table__ele--draggable-chosen`, + dragging: `${classPrefix}-table__ele--draggable-dragging`, + dragSortTh: `${classPrefix}-table__th--drag-sort`, + }, + + virtualScrollClasses: { + cursor: `${classPrefix}-table__virtual-scroll-cursor`, + header: `${classPrefix}-table__virtual-scroll-header`, + }, + + positiveRotate90: `${classPrefix}-positive-rotate-90`, + negativeRotate180: `${classPrefix}-negative-rotate-180`, + }; + + return classNames; +} + +export type TableClassName = ReturnType; diff --git a/src/table/hooks/useStyle.ts b/src/table/hooks/useStyle.ts new file mode 100644 index 00000000..78ff36ca --- /dev/null +++ b/src/table/hooks/useStyle.ts @@ -0,0 +1,40 @@ +import cx from 'classnames'; +import useClassName from './useClassName'; + +import type { TdBaseTableProps } from '../type'; + +export function formatCSSUnit(unit: string | number | undefined) { + if (!unit) return unit; + return isNaN(Number(unit)) ? unit : `${unit}px`; +} + +export default function useStyle(props: TdBaseTableProps) { + const { bordered, stripe, verticalAlign, height, maxHeight, tableContentWidth, loading } = props; + + const { tableBaseClass, tableAlignClasses } = useClassName(); + + const tableClasses = cx([ + tableBaseClass.table, + [tableAlignClasses[verticalAlign || 'middle']], + { + [tableBaseClass.bordered]: bordered, + [tableBaseClass.striped]: stripe, + [tableBaseClass.loading]: loading, + }, + ]); + + const tableContentStyles: React.CSSProperties = { + height: formatCSSUnit(height), + maxHeight: formatCSSUnit(maxHeight), + }; + + const tableElementStyles: React.CSSProperties = { + width: formatCSSUnit(tableContentWidth), + }; + + return { + tableClasses, + tableElementStyles, + tableContentStyles, + }; +} diff --git a/src/table/index.tsx b/src/table/index.tsx new file mode 100644 index 00000000..221dae68 --- /dev/null +++ b/src/table/index.tsx @@ -0,0 +1,9 @@ +import { BaseTable, BaseTableProps } from './BaseTable'; + +import './style'; + +export type TableProps = BaseTableProps; + +export { BaseTable as Table }; + +export default BaseTable; diff --git a/src/table/style/css.js b/src/table/style/css.js new file mode 100644 index 00000000..6a9a4b13 --- /dev/null +++ b/src/table/style/css.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/src/table/style/index.js b/src/table/style/index.js new file mode 100644 index 00000000..bcee957e --- /dev/null +++ b/src/table/style/index.js @@ -0,0 +1 @@ +import '../../_common/style/mobile/components/table/_index.less'; diff --git a/src/table/table.en-US.md b/src/table/table.en-US.md new file mode 100644 index 00000000..37293f19 --- /dev/null +++ b/src/table/table.en-US.md @@ -0,0 +1,43 @@ +:: BASE_DOC :: + +## API + +### BaseTable Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N +bordered | Boolean | false | show table bordered | N +cellEmptyContent | TNode | - | Typescript:`string \| TNode>`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +columns | Array | [] | table column configs。Typescript:`Array>` | N +data | Array | [] | table data。Typescript:`Array` | N +empty | TNode | '' | empty text or empty element。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +fixedRows | Array | - | Typescript:`Array` | N +height | String / Number | - | table height | N +loading | TNode | undefined | loading state table。Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +loadingProps | Object | - | Typescript:`Partial`,[Loading API Documents](./loading?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +maxHeight | String / Number | - | table max height | N +rowKey | String | 'id' | required。unique key for each row data | Y +showHeader | Boolean | true | show table header | N +stripe | Boolean | false | show stripe style | N +tableContentWidth | String | - | \- | N +tableLayout | String | fixed | table-layout css properties, [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout). set value to be `fixed` on `resizable=true` please。options: auto/fixed | N +verticalAlign | String | middle | vertical align。options: top/middle/bottom | N +onCellClick | Function | | Typescript:`(context: BaseTableCellEventContext) => void`
trigger on cell clicked。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts)。
`interface BaseTableCellEventContext { row: T; col: BaseTableCol; rowIndex: number; colIndex: number; e: MouseEvent }`
| N +onRowClick | Function | | Typescript:`(context: RowEventContext) => void`
trigger on row click。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts)。
`interface RowEventContext { row: T; index: number; e: MouseEvent }`
| N +onScroll | Function | | Typescript:`(params: { e: WheelEvent }) => void`
trigger on table content scroll | N + +### BaseTableCol + +name | type | default | description | required +-- | -- | -- | -- | -- +align | String | left | align type。options: left/right/center | N +cell | String / Function | - | use cell to render table cell。Typescript:`string \| TNode>` `interface BaseTableCellParams { row: T; rowIndex: number; col: BaseTableCol; colIndex: number }`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +colKey | String | - | unique key for column | N +ellipsis | TNode | false | ellipsis cell content。Typescript:`boolean \| TNode> \| TooltipProps \| { props: TooltipProps; content: TNode> }`,[Tooltip API Documents](./tooltip?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +ellipsisTitle | TNode | undefined | ellipsis title content。Typescript:`boolean \| TNode> \| TooltipProps \| { props: TooltipProps; content: TNode> }` `interface BaseTableColParams { col: BaseTableCol; colIndex: number }`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +fixed | String | left | options: left/right | N +minWidth | String / Number | - | add CSS property `min-width` to HTML Element ``,Browsers with [TablesNG](https://docs.google.com/document/d/16PFD1GtMI9Zgwu0jtPaKZJ75Q2wyZ9EZnVbBacOfiNA/preview) support `minWidth` | N +title | String / Function | - | th content。Typescript:`string \| TNode \| TNode<{ col: BaseTableCol; colIndex: number }>`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +width | String / Number | - | column width | N \ No newline at end of file diff --git a/src/table/table.md b/src/table/table.md new file mode 100644 index 00000000..606f8f7c --- /dev/null +++ b/src/table/table.md @@ -0,0 +1,43 @@ +:: BASE_DOC :: + +## API + +### BaseTable Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +bordered | Boolean | false | 是否显示表格边框 | N +cellEmptyContent | TNode | - | 单元格数据为空时呈现的内容。TS 类型:`string \| TNode>`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +columns | Array | [] | 列配置,泛型 T 指表格数据类型。TS 类型:`Array>` | N +data | Array | [] | 数据源,泛型 T 指表格数据类型。TS 类型:`Array` | N +empty | TNode | '' | 空表格呈现样式,支持全局配置 `GlobalConfigProvider`。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +fixedRows | Array | - | 【开发中】固定行(冻结行),示例:[M, N],表示冻结表头 M 行和表尾 N 行。M 和 N 值为 0 时,表示不冻结行。TS 类型:`Array` | N +height | String / Number | - | 表格高度,超出后会出现滚动条。示例:100, '30%', '300'。值为数字类型,会自动加上单位 px。如果不是绝对固定表格高度,建议使用 `maxHeight` | N +loading | TNode | undefined | 加载中状态。值为 `true` 会显示默认加载中样式,可以通过 Function 和 插槽 自定义加载状态呈现内容和样式。值为 `false` 则会取消加载状态。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +loadingProps | Object | - | 透传加载组件全部属性。TS 类型:`Partial`,[Loading API Documents](./loading?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +maxHeight | String / Number | - | 表格最大高度,超出后会出现滚动条。示例:100, '30%', '300'。值为数字类型,会自动加上单位 px | N +rowKey | String | 'id' | 必需。唯一标识一行数据的字段名,来源于 `data` 中的字段。如果是字段嵌套多层,可以设置形如 `item.a.id` 的方法 | Y +showHeader | Boolean | true | 是否显示表头 | N +stripe | Boolean | false | 是否显示斑马纹 | N +tableContentWidth | String | - | 表格内容的总宽度,注意不是表格可见宽度。主要应用于 `table-layout: auto` 模式下的固定列显示。`tableContentWidth` 内容宽度的值必须大于表格可见宽度 | N +tableLayout | String | fixed | 表格布局方式。可选项:auto/fixed | N +verticalAlign | String | middle | 行内容上下方向对齐。可选项:top/middle/bottom | N +onCellClick | Function | | TS 类型:`(context: BaseTableCellEventContext) => void`
单元格点击时触发。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts)。
`interface BaseTableCellEventContext { row: T; col: BaseTableCol; rowIndex: number; colIndex: number; e: MouseEvent }`
| N +onRowClick | Function | | TS 类型:`(context: RowEventContext) => void`
行点击时触发,泛型 T 指表格数据类型。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts)。
`interface RowEventContext { row: T; index: number; e: MouseEvent }`
| N +onScroll | Function | | TS 类型:`(params: { e: WheelEvent }) => void`
表格内容滚动时触发 | N + +### BaseTableCol + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +align | String | left | 列横向对齐方式。可选项:left/right/center | N +cell | String / Function | - | 自定义单元格渲染,优先级高于 `render`。泛型 T 指表格数据类型。TS 类型:`string \| TNode>` `interface BaseTableCellParams { row: T; rowIndex: number; col: BaseTableCol; colIndex: number }`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +colKey | String | - | 渲染列所需字段,值为 `serial-number` 表示当前列为「序号」列 | N +ellipsis | TNode | false | 单元格和表头内容超出时,是否显示省略号。如果仅希望单元格超出省略,可设置 `ellipsisTitle = false`。
值为 `true`,则超出省略浮层默认显示单元格内容;
值类型为 `Function` 则自定义超出省略浮中层显示的内容;
值类型为 `Object`,则自动透传属性到 Tooltip 组件,可用于调整浮层背景色和方向等特性。
同时透传 Tooltip 属性和自定义浮层内容,请使用 `{ props: { theme: 'light' }, content: () => 'something' }`。
请注意单元格超出省略的两个基本点:1. 内容元素是内联元素或样式(自定义单元格内容时需特别注意);2. 内容超出父元素。TS 类型:`boolean \| TNode> \| TooltipProps \| { props: TooltipProps; content: TNode> }`,[Tooltip API Documents](./tooltip?tab=api)。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +ellipsisTitle | TNode | undefined | 表头内容超出时,是否显示省略号。优先级高于 `ellipsis`。
值为 `true`,则超出省略的浮层默认显示表头全部内容;
值类型为 `Function` 用于自定义超出省略浮层显示的表头内容;
值类型为 `Object`,则自动透传属性到 Tooltip 组件,则自动透传属性到 Tooltip 组件,可用于调整浮层背景色和方向等特性。
同时透传 Tooltip 属性和自定义浮层内容,请使用 `{ props: { theme: 'light' }, content: () => 'something' }`。TS 类型:`boolean \| TNode> \| TooltipProps \| { props: TooltipProps; content: TNode> }` `interface BaseTableColParams { col: BaseTableCol; colIndex: number }`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/table/type.ts) | N +fixed | String | left | 【开发中】固定列显示位置。可选项:left/right | N +minWidth | String / Number | - | 透传 CSS 属性 `min-width` 到 `` 元素。⚠️ 仅少部分浏览器支持,如:使用 [TablesNG](https://docs.google.com/document/d/16PFD1GtMI9Zgwu0jtPaKZJ75Q2wyZ9EZnVbBacOfiNA/preview) 渲染的 Chrome 浏览器支持 `minWidth` | N +title | String / Function | - | 自定义表头渲染,优先级高于 render。TS 类型:`string \| TNode \| TNode<{ col: BaseTableCol; colIndex: number }>`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +width | String / Number | - | 列宽,可以作为最小宽度使用。当列宽总和小于 `table` 元素时,浏览器根据宽度设置情况自动分配宽度;当列宽总和大于 `table` 元素,表现为定宽。可以同时调整 `table` 元素的宽度来达到自己想要的效果 | N diff --git a/src/table/type.ts b/src/table/type.ts new file mode 100644 index 00000000..1945c786 --- /dev/null +++ b/src/table/type.ts @@ -0,0 +1,200 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode, ClassName } from '../common'; +import { LoadingProps } from '../loading'; + +export interface TdBaseTableProps { + /** + * 是否显示表格边框 + * @default false + */ + bordered?: boolean; + /** + * 单元格数据为空时呈现的内容 + */ + cellEmptyContent?: string | TNode>; + /** + * 列配置,泛型 T 指表格数据类型 + * @default [] + */ + columns?: Array>; + /** + * 数据源,泛型 T 指表格数据类型 + * @default [] + */ + data?: Array; + /** + * 空表格呈现样式,支持全局配置 `GlobalConfigProvider` + * @default '' + */ + empty?: string | TNode; + /** + * 固定行(冻结行),示例:[M, N],表示冻结表头 M 行和表尾 N 行。M 和 N 值为 0 时,表示不冻结行 + */ + fixedRows?: Array; + /** + * 表格高度,超出后会出现滚动条。示例:100, '30%', '300'。值为数字类型,会自动加上单位 px。如果不是绝对固定表格高度,建议使用 `maxHeight` + */ + height?: string | number; + /** + * 加载中状态。值为 `true` 会显示默认加载中样式,可以通过 Function 和 插槽 自定义加载状态呈现内容和样式。值为 `false` 则会取消加载状态 + */ + loading?: boolean | TNode; + /** + * 透传加载组件全部属性 + */ + loadingProps?: Partial; + /** + * 表格最大高度,超出后会出现滚动条。示例:100, '30%', '300'。值为数字类型,会自动加上单位 px + */ + maxHeight?: string | number; + /** + * 唯一标识一行数据的字段名,来源于 `data` 中的字段。如果是字段嵌套多层,可以设置形如 `item.a.id` 的方法 + * @default 'id' + */ + rowKey: string; + /** + * 是否显示表头 + * @default true + */ + showHeader?: boolean; + /** + * 是否显示斑马纹 + * @default false + */ + stripe?: boolean; + /** + * 表格内容的总宽度,注意不是表格可见宽度。主要应用于 `table-layout: auto` 模式下的固定列显示。`tableContentWidth` 内容宽度的值必须大于表格可见宽度 + * @default '' + */ + tableContentWidth?: string; + /** + * 表格布局方式 + * @default fixed + */ + tableLayout?: 'auto' | 'fixed'; + /** + * 行内容上下方向对齐 + * @default middle + */ + verticalAlign?: 'top' | 'middle' | 'bottom'; + /** + * 单元格点击时触发 + */ + onCellClick?: (context: BaseTableCellEventContext) => void; + /** + * 行点击时触发,泛型 T 指表格数据类型 + */ + onRowClick?: (context: RowEventContext) => void; + /** + * 表格内容滚动时触发 + */ + onScroll?: (params: { e: React.UIEvent }) => void; +} + +/** 组件实例方法 */ +export interface BaseTableInstanceFunctions { + /** + * 全部重新渲染表格 + */ + refreshTable: () => void; +} + +export interface BaseTableCol { + /** + * 列横向对齐方式 + * @default left + */ + align?: 'left' | 'right' | 'center'; + /** + * 自定义单元格渲染。值类型为 Function 表示以函数形式渲染单元格。值类型为 string 表示使用插槽渲染,插槽名称为 cell 的值。默认使用 colKey 作为插槽名称。优先级高于 render。泛型 T 指表格数据类型 + */ + cell?: string | TNode>; + /** + * 渲染列所需字段,值为 `serial-number` 表示当前列为「序号」列 + * @default '' + */ + colKey?: string; + /** + * 单元格和表头内容超出时,是否显示省略号。如果仅希望单元格超出省略,可设置 `ellipsisTitle = false`。
值为 `true`,则超出省略浮层默认显示单元格内容;
值类型为 `Function` 则自定义超出省略浮中层显示的内容;
值类型为 `Object`,则自动透传属性到 Tooltip 组件,可用于调整浮层背景色和方向等特性。
同时透传 Tooltip 属性和自定义浮层内容,请使用 `{ props: { theme: 'light' }, content: () => 'something' }` + * @default false + */ + ellipsis?: boolean | TNode>; + /** + * 表头内容超出时,是否显示省略号。优先级高于 `ellipsis`。
值为 `true`,则超出省略的浮层默认显示表头全部内容;
值类型为 `Function` 用于自定义超出省略浮层显示的表头内容;
值类型为 `Object`,则自动透传属性到 Tooltip 组件,则自动透传属性到 Tooltip 组件,可用于调整浮层背景色和方向等特性。
同时透传 Tooltip 属性和自定义浮层内容,请使用 `{ props: { theme: 'light' }, content: () => 'something' }` + */ + ellipsisTitle?: boolean | TNode>; + /** + * 固定列显示位置 + * @default left + */ + fixed?: 'left' | 'right'; + /** + * 透传 CSS 属性 `min-width` 到 `` 元素。⚠️ 仅少部分浏览器支持,如:使用 [TablesNG](https://docs.google.com/document/d/16PFD1GtMI9Zgwu0jtPaKZJ75Q2wyZ9EZnVbBacOfiNA/preview) 渲染的 Chrome 浏览器支持 `minWidth` + */ + minWidth?: string | number; + /** + * 自定义表头渲染。值类型为 Function 表示以函数形式渲染表头。值类型为 string 表示使用插槽渲染,插槽名称为 title 的值。优先级高于 render + */ + title?: string | TNode<{ col: BaseTableCol; colIndex: number }>; + /** + * 列宽,可以作为最小宽度使用。当列宽总和小于 `table` 元素时,浏览器根据宽度设置情况自动分配宽度;当列宽总和大于 `table` 元素,表现为定宽。可以同时调整 `table` 元素的宽度来达到自己想要的效果 + */ + width?: string | number; +} + +export interface BaseTableCellEventContext { + row: T; + col: BaseTableCol; + rowIndex: number; + colIndex: number; + e: React.MouseEvent; +} + +export interface RowEventContext { + row: T; + index: number; + e: React.MouseEvent; +} + +export interface TableRowData { + [key: string]: any; + children?: TableRowData[]; +} + +export interface BaseTableCellParams { + row: T; + rowIndex: number; + col: BaseTableCol; + colIndex: number; +} + +export interface BaseTableColParams { + col: BaseTableCol; + colIndex: number; +} + +export interface RowClassNameParams { + row: T; + rowIndex: number; + type?: 'body' | 'foot'; +} + +export type TableColumnClassName = ClassName | ((context: CellData) => ClassName); + +export interface CellData extends BaseTableCellParams { + type: 'th' | 'td'; +} + +export interface BaseTableCellParams { + row: T; + rowIndex: number; + col: BaseTableCol; + colIndex: number; +} + +export type DataType = TableRowData;