diff --git a/docs/examples/className.tsx b/docs/examples/className.tsx index 82beacdcf..8deffd2b1 100644 --- a/docs/examples/className.tsx +++ b/docs/examples/className.tsx @@ -52,6 +52,20 @@ const Demo = () => ( expandedRowClassName={(record, i) => `ex-row-${i}`} data={data} className="table" + title={() => title} + footer={() => footer} + /> +

scroll

+ `row-${i}`} + expandedRowRender={record =>

extra: {record.a}

} + expandedRowClassName={(record, i) => `ex-row-${i}`} + data={Array(5).fill(data)} + className="table" + scroll={{ x: 'calc(700px + 50%)', y: 47 * 5 }} + title={() => title} + footer={() => footer} /> ); diff --git a/docs/examples/components.tsx b/docs/examples/components.tsx index b7647f52d..6883c6ae3 100644 --- a/docs/examples/components.tsx +++ b/docs/examples/components.tsx @@ -24,6 +24,18 @@ const Demo = () => { return (
{ record: RecordType; @@ -14,6 +15,8 @@ export interface BodyRowProps { renderIndex: number; className?: string; style?: React.CSSProperties; + classNames: TableProps['classNames']['body']; + styles: TableProps['styles']['body']; rowComponent: CustomizeComponent; cellComponent: CustomizeComponent; scopeCellComponent: CustomizeComponent; @@ -94,6 +97,8 @@ function BodyRow( const { className, style, + classNames, + styles, record, index, renderIndex, @@ -103,6 +108,7 @@ function BodyRow( cellComponent, scopeCellComponent, } = props; + const rowInfo = useRowInfo(record, rowKey, index, indent); const { prefixCls, @@ -133,16 +139,21 @@ function BodyRow( = 1, }, )} - style={{ ...style, ...rowProps?.style }} + style={{ + ...style, + ...rowProps?.style, + ...styles.row, + }} > {flattenColumns.map((column: ColumnType, colIndex) => { const { render, dataIndex, className: columnClassName } = column; @@ -157,7 +168,8 @@ function BodyRow( return ( - className={columnClassName} + className={cls(columnClassName, classNames.cell)} + style={styles.cell} ellipsis={column.ellipsis} align={column.align} scope={column.rowScope} @@ -187,7 +199,7 @@ function BodyRow( expandRowNode = ( { data: readonly RecordType[]; @@ -31,6 +32,8 @@ function Body(props: BodyProps) { expandedKeys, childrenColumnName, emptyNode, + classNames, + styles, } = useContext(TableContext, [ 'prefixCls', 'getComponent', @@ -40,7 +43,11 @@ function Body(props: BodyProps) { 'expandedKeys', 'childrenColumnName', 'emptyNode', + 'classNames', + 'styles', ]); + const { body: bodyCls = {} } = classNames || {}; + const { body: bodyStyles = {} } = styles || {}; const flattenData: { record: RecordType; indent: number; index: number }[] = useFlattenRecords(data, childrenColumnName, expandedKeys, getRowKey); @@ -65,6 +72,8 @@ function Body(props: BodyProps) { return ( (props: BodyProps) { return ( - + {/* Measure body column width with additional hidden col */} {measureColumnWidth && ( { prefixCls?: string; className?: string; + style?: React.CSSProperties; record?: RecordType; /** `column` index is the real show rowIndex */ index?: number; @@ -88,6 +89,7 @@ function Cell(props: CellProps) { // Style prefixCls, className, + style, align, // Value @@ -122,6 +124,7 @@ function Cell(props: CellProps) { } = props; const cellPrefixCls = `${prefixCls}-cell`; + const { allColumnsFixedLeft, rowHoverable } = useContext(TableContext, [ 'allColumnsFixedLeft', 'rowHoverable', @@ -212,7 +215,7 @@ function Cell(props: CellProps) { }); // >>>>> ClassName - const mergedClassName = classNames( + const mergedClassName = cls( cellPrefixCls, className, { @@ -249,6 +252,7 @@ function Cell(props: CellProps) { ...fixedStyle, ...alignStyle, ...additionalProps.style, + ...style, }; // >>>>> Children Node diff --git a/src/FixedHolder/index.tsx b/src/FixedHolder/index.tsx index f2a4a6331..4e07f162c 100644 --- a/src/FixedHolder/index.tsx +++ b/src/FixedHolder/index.tsx @@ -26,6 +26,7 @@ function useColumnWidth(colWidths: readonly number[], columCount: number) { export interface FixedHeaderProps extends HeaderProps { className: string; + style?: React.CSSProperties; noData: boolean; maxContentScroll: boolean; colWidths: readonly number[]; @@ -46,6 +47,7 @@ const FixedHolder = React.forwardRef>((pro const { className, + style, noData, columns, flattenColumns, @@ -159,6 +161,7 @@ const FixedHolder = React.forwardRef>((pro style={{ overflow: 'hidden', ...(isSticky ? { top: stickyTopOffset, bottom: stickyBottomOffset } : {}), + ...style, }} ref={setScrollRef} className={classNames(className, { diff --git a/src/Header/Header.tsx b/src/Header/Header.tsx index f21b817b2..d426b91f3 100644 --- a/src/Header/Header.tsx +++ b/src/Header/Header.tsx @@ -11,9 +11,13 @@ import type { StickyOffsets, } from '../interface'; import HeaderRow from './HeaderRow'; +import cls from 'classnames'; +import { TableProps } from '..'; function parseHeaderRows( rootColumns: ColumnsType, + classNames: TableProps['classNames']['header'], + styles: TableProps['styles']['header'], ): CellType[][] { const rows: CellType[][] = []; @@ -29,7 +33,8 @@ function parseHeaderRows( const colSpans: number[] = columns.filter(Boolean).map(column => { const cell: CellType = { key: column.key, - className: column.className || '', + className: cls(column.className, classNames.cell) || '', + style: styles.cell, children: column.title, column, colStart: currentColIndex, @@ -97,18 +102,33 @@ const Header = (props: HeaderProps) => { const { stickyOffsets, columns, flattenColumns, onHeaderRow } = props; - const { prefixCls, getComponent } = useContext(TableContext, ['prefixCls', 'getComponent']); - const rows = React.useMemo[][]>(() => parseHeaderRows(columns), [columns]); + const { prefixCls, getComponent, classNames, styles } = useContext(TableContext, [ + 'prefixCls', + 'getComponent', + 'classNames', + 'styles', + ]); + const { header: headerCls = {} } = classNames || {}; + const { header: headerStyles = {} } = styles || {}; + const rows = React.useMemo[][]>( + () => parseHeaderRows(columns, headerCls, headerStyles), + [columns, headerCls, headerStyles], + ); const WrapperComponent = getComponent(['header', 'wrapper'], 'thead'); const trComponent = getComponent(['header', 'row'], 'tr'); const thComponent = getComponent(['header', 'cell'], 'th'); return ( - + {rows.map((row, rowIndex) => { const rowNode = ( { cells: readonly CellType[]; @@ -20,6 +21,8 @@ export interface RowProps { cellComponent: CustomizeComponent; onHeaderRow: GetComponentProps[]>; index: number; + classNames: TableProps['classNames']['header']; + styles: TableProps['styles']['header']; } const HeaderRow = (props: RowProps) => { @@ -31,6 +34,8 @@ const HeaderRow = (props: RowProps) => { cellComponent: CellComponent, onHeaderRow, index, + classNames, + styles, } = props; const { prefixCls } = useContext(TableContext, ['prefixCls']); let rowProps: React.HTMLAttributes; @@ -44,7 +49,7 @@ const HeaderRow = (props: RowProps) => { const columnsKey = getColumnsKey(cells.map(cell => cell.column)); return ( - + {cells.map((cell: CellType, cellIndex) => { const { column } = cell; const fixedInfo = getCellFixedInfo( diff --git a/src/Panel/index.tsx b/src/Panel/index.tsx index 606bf65a7..3cd57e21e 100644 --- a/src/Panel/index.tsx +++ b/src/Panel/index.tsx @@ -3,10 +3,11 @@ import * as React from 'react'; export interface TitleProps { className: string; children: React.ReactNode; + style: React.CSSProperties; } -function Panel({ className, children }: TitleProps) { - return
{children}
; +function Panel({ className, style, children }: TitleProps) { + return
{children}
; } export default Panel; diff --git a/src/Table.tsx b/src/Table.tsx index c617ba494..d4062b3fe 100644 --- a/src/Table.tsx +++ b/src/Table.tsx @@ -25,7 +25,7 @@ */ import type { CompareProps } from '@rc-component/context/lib/Immutable'; -import classNames from 'classnames'; +import cls from 'classnames'; import ResizeObserver from '@rc-component/resize-observer'; import isVisible from '@rc-component/util/lib/Dom/isVisible'; import { getTargetScrollBarSize } from '@rc-component/util/lib/getScrollBarSize'; @@ -85,11 +85,21 @@ const EMPTY_DATA = []; // Used for customize scroll const EMPTY_SCROLL_TARGET = {}; +export type SemanticName = 'section' | 'title' | 'footer' | 'content'; +export type ComponentsSemantic = 'wrapper' | 'cell' | 'row'; export interface TableProps extends Omit, 'showExpandColumn'> { prefixCls?: string; className?: string; style?: React.CSSProperties; + classNames?: Partial> & { + body?: Partial>; + header?: Partial>; + }; + styles?: Partial> & { + body?: Partial>; + header?: Partial>; + }; children?: React.ReactNode; data?: readonly RecordType[]; columns?: ColumnsType; @@ -190,6 +200,8 @@ function Table( className, rowClassName, style, + classNames, + styles, data, rowKey, scroll, @@ -658,7 +670,7 @@ function Table( }} onScroll={onBodyScroll} ref={scrollBodyRef} - className={classNames(`${prefixCls}-body`)} + className={`${prefixCls}-body`} > ( style={{ ...scrollXStyle, ...scrollYStyle, + ...styles?.content, }} - className={classNames(`${prefixCls}-content`)} + className={cls(`${prefixCls}-content`, classNames?.content)} onScroll={onInternalScroll} ref={scrollBodyRef} > @@ -773,7 +786,7 @@ function Table( let fullTable = (
( ref={fullTableRef} {...dataProps} > - {title && {title(mergedData)}} -
+ {title && ( + + {title(mergedData)} + + )} +
{groupTableNode}
- {footer && {footer(mergedData)}} + {footer && ( + + {footer(mergedData)} + + )}
); @@ -812,6 +837,9 @@ function Table( scrollX: mergedScrollX, scrollInfo, + classNames, + styles, + // Table prefixCls, getComponent, diff --git a/src/context/TableContext.tsx b/src/context/TableContext.tsx index cce41f90b..b9cae4ddc 100644 --- a/src/context/TableContext.tsx +++ b/src/context/TableContext.tsx @@ -14,6 +14,7 @@ import type { TriggerEventHandler, } from '../interface'; import type { FixedInfo } from '../utils/fixUtil'; +import { TableProps } from '../Table'; const { makeImmutable, responseImmutable, useImmutableMark } = createImmutable(); export { makeImmutable, responseImmutable, useImmutableMark }; @@ -23,6 +24,8 @@ export type ScrollInfoType = [scrollLeft: number, scrollRange: number]; export interface TableContextProps { // Scroll scrollX: number | string | true; + classNames?: TableProps['classNames']; + styles?: TableProps['styles']; // Table prefixCls: string; diff --git a/tests/semantic.spec.tsx b/tests/semantic.spec.tsx new file mode 100644 index 000000000..878a621f7 --- /dev/null +++ b/tests/semantic.spec.tsx @@ -0,0 +1,120 @@ +import { render, fireEvent } from '@testing-library/react'; +import React from 'react'; +import Table, { TableProps } from '../src'; +describe('support classNames and styles', () => { + const columns: TableProps['columns'] = [ + { + title: 'title1', + dataIndex: 'a', + className: 'a', + key: 'a', + width: 100, + }, + { + title: 'title2', + dataIndex: 'b', + className: 'b', + key: 'b', + width: 100, + }, + { + title: 'title3', + dataIndex: 'c', + className: 'c', + key: 'c', + width: 200, + }, + { + title: 'Operations', + dataIndex: '', + className: 'd', + key: 'd', + render() { + return Operations; + }, + }, + ]; + + const data = [ + { a: '123', key: '1' }, + { a: 'cdd', b: 'edd', key: '2' }, + { a: '1333', c: 'eee', d: 2, key: '3' }, + ]; + const commonTableProps = { + columns: columns, + rowClassName: (record, i) => `row-${i}`, + expandedRowRender: record =>

extra: {record.a}

, + expandedRowClassName: (record, i) => `ex-row-${i}`, + className: 'table', + title: () => title, + footer: () => footer, + }; + it('should support className and style', () => { + const testClassNames = { + section: 'test-section', + title: 'test-title', + footer: 'test-footer', + content: 'test-content', + body: { + wrapper: 'test-body-wrapper', + cell: 'test-body-cell', + row: 'test-body-row', + }, + header: { + wrapper: 'test-header-wrapper', + cell: 'test-header-cell', + row: 'test-header-row', + }, + }; + const testStyles = { + section: { background: 'red' }, + title: { background: 'green' }, + footer: { background: 'pink' }, + content: { background: 'purple' }, + body: { + wrapper: { background: 'cyan' }, + cell: { background: 'lime' }, + row: { background: 'teal' }, + }, + header: { + wrapper: { background: 'magenta' }, + cell: { background: 'gold' }, + row: { background: 'silver' }, + }, + }; + const { container } = render( +
, + ); + const section = container.querySelector('.rc-table-container'); + const title = container.querySelector('.rc-table-title'); + const footer = container.querySelector('.rc-table-footer'); + const content = container.querySelector('.rc-table-content'); + const headerWrapper = container.querySelector('.rc-table-thead'); + const headerCell = container.querySelector('.rc-table-cell'); + const headerRow = container.querySelector('tr'); + const bodyWrapper = container.querySelector('.rc-table-tbody'); + const bodyCell = container.querySelector('.rc-table-tbody .rc-table-cell'); + const bodyRow = container.querySelector('.rc-table-row'); + expect(section).toHaveClass(testClassNames.section); + expect(section).toHaveStyle(testStyles.section); + expect(title).toHaveClass(testClassNames.title); + expect(title).toHaveStyle(testStyles.title); + expect(footer).toHaveClass(testClassNames.footer); + expect(footer).toHaveStyle(testStyles.footer); + expect(content).toHaveClass(testClassNames.content); + expect(content).toHaveStyle(testStyles.content); + + expect(headerWrapper).toHaveClass(testClassNames.header.wrapper); + expect(headerWrapper).toHaveStyle(testStyles.header.wrapper); + expect(headerCell).toHaveClass(testClassNames.header.cell); + expect(headerCell).toHaveStyle({ background: testStyles.header.cell.background }); + expect(headerRow).toHaveClass(testClassNames.header.row); + expect(headerRow).toHaveStyle(testStyles.header.row); + expect(bodyWrapper).toHaveClass(testClassNames.body.wrapper); + expect(bodyWrapper).toHaveStyle(testStyles.body.wrapper); + expect(bodyCell).toHaveClass(testClassNames.body.cell); + expect(bodyCell).toHaveStyle(testStyles.body.cell); + expect(bodyRow).toHaveClass(testClassNames.body.row); + expect(bodyRow).toHaveStyle(testStyles.body.row); + }); +});