refactor(table): redesign interfaces to improve the experience in TypeScript (#569)

* refactor(table): redesign interfaces to improve the experience in TypeScript

* docs: upgrade to new type exports

* style: fix lint warnings
This commit is contained in:
witt
2021-06-26 19:50:19 +08:00
committed by unix
parent 144eaf332f
commit 4062d7b5a8
16 changed files with 578 additions and 305 deletions

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table should be no erros when width is too large 1`] = `
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th><div class=\\"thead-box\\">property</div></th><th><div class=\\"thead-box\\">description</div></th><th><div class=\\"thead-box\\">default</div></th></tr></thead><style>
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\">property</div></th><th class=\\"\\"><div class=\\"thead-box\\">description</div></th><th class=\\"\\"><div class=\\"thead-box\\">default</div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
@@ -46,7 +46,7 @@ exports[`Table should be no erros when width is too large 1`] = `
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"hover\\"><td><div class=\\"cell\\">type</div></td><td><div class=\\"cell\\">Content type</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">Component</div></td><td><div class=\\"cell\\">DOM element to use</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">bold</div></td><td><div class=\\"cell\\">Bold style</div></td><td><div class=\\"cell\\">true</div></td></tr><style>
</style><tbody><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td><td class=\\"\\"><div class=\\"cell\\">Content type</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td><td class=\\"\\"><div class=\\"cell\\">DOM element to use</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\">Bold style</div></td><td class=\\"\\"><div class=\\"cell\\">true</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;
@@ -86,7 +86,7 @@ exports[`Table should be no erros when width is too large 1`] = `
`;
exports[`Table should render children for table head 1`] = `
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th><div class=\\"thead-box\\"><code>property</code></div></th></tr></thead><style>
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\"><code>property</code></div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
@@ -131,7 +131,7 @@ exports[`Table should render children for table head 1`] = `
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"hover\\"><td><div class=\\"cell\\">type</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">Component</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">bold</div></td></tr><style>
</style><tbody><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;
@@ -171,7 +171,7 @@ exports[`Table should render children for table head 1`] = `
`;
exports[`Table should render correctly 1`] = `
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th><div class=\\"thead-box\\">property</div></th><th><div class=\\"thead-box\\">description</div></th><th><div class=\\"thead-box\\">default</div></th></tr></thead><style>
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\">property</div></th><th class=\\"\\"><div class=\\"thead-box\\">description</div></th><th class=\\"\\"><div class=\\"thead-box\\">default</div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
@@ -216,7 +216,7 @@ exports[`Table should render correctly 1`] = `
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"hover\\"><td><div class=\\"cell\\">type</div></td><td><div class=\\"cell\\">Content type</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">Component</div></td><td><div class=\\"cell\\">DOM element to use</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">bold</div></td><td><div class=\\"cell\\">Bold style</div></td><td><div class=\\"cell\\">true</div></td></tr><style>
</style><tbody><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td><td class=\\"\\"><div class=\\"cell\\">Content type</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td><td class=\\"\\"><div class=\\"cell\\">DOM element to use</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\">Bold style</div></td><td class=\\"\\"><div class=\\"cell\\">true</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;
@@ -256,7 +256,7 @@ exports[`Table should render correctly 1`] = `
`;
exports[`Table should set width automatically 1`] = `
"<table class=\\"\\"><colgroup><col width=\\"25\\"><col width=\\"25\\"><col width=\\"50\\"></colgroup><thead><tr><th><div class=\\"thead-box\\">property</div></th><th><div class=\\"thead-box\\">description</div></th><th><div class=\\"thead-box\\">default</div></th></tr></thead><style>
"<table class=\\"\\"><colgroup><col width=\\"25\\"><col width=\\"25\\"><col width=\\"50\\"></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\">property</div></th><th class=\\"\\"><div class=\\"thead-box\\">description</div></th><th class=\\"\\"><div class=\\"thead-box\\">default</div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
@@ -301,7 +301,92 @@ exports[`Table should set width automatically 1`] = `
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"hover\\"><td><div class=\\"cell\\">type</div></td><td><div class=\\"cell\\">Content type</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">Component</div></td><td><div class=\\"cell\\">DOM element to use</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">bold</div></td><td><div class=\\"cell\\">Bold style</div></td><td><div class=\\"cell\\">true</div></td></tr><style>
</style><tbody><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td><td class=\\"\\"><div class=\\"cell\\">Content type</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td><td class=\\"\\"><div class=\\"cell\\">DOM element to use</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\">Bold style</div></td><td class=\\"\\"><div class=\\"cell\\">true</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;
}
tr.hover:hover {
background-color: #fafafa;
}
tr :global(td) {
padding: 0 0.5em;
border-bottom: 1px solid #eaeaea;
color: #444;
font-size: calc(0.875 * var(--table-font-size));
text-align: left;
}
tr :global(.cell) {
min-height: calc(3.125 * var(--table-font-size));
display: flex;
-webkit-box-align: center;
align-items: center;
flex-flow: row wrap;
}
</style></tbody><style>
table {
border-collapse: separate;
border-spacing: 0;
--table-font-size: calc(1 * 16px);
font-size: var(--table-font-size);
width: 100%;
height: auto;
padding: 0 0 0 0;
margin: 0 0 0 0;
}
</style></table>"
`;
exports[`Table should work correctly with multiple identical props 1`] = `
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\">property3</div></th><th class=\\"\\"><div class=\\"thead-box\\">description2</div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
font-size: inherit;
}
th {
padding: 0 0.5em;
font-size: calc(0.75 * var(--table-font-size));
font-weight: normal;
text-align: left;
letter-spacing: 0;
vertical-align: middle;
min-height: calc(2.5 * var(--table-font-size));
color: #666;
background: #fafafa;
border-bottom: 1px solid #eaeaea;
border-top: 1px solid #eaeaea;
border-radius: 0;
}
th:nth-child(1) {
border-bottom: 1px solid #eaeaea;
border-left: 1px solid #eaeaea;
border-top: 1px solid #eaeaea;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
th:last-child {
border-bottom: 1px solid #eaeaea;
border-right: 1px solid #eaeaea;
border-top: 1px solid #eaeaea;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.thead-box {
display: flex;
align-items: center;
-webkit-box-align: center;
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td><td class=\\"\\"><div class=\\"cell\\">Content type</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td><td class=\\"\\"><div class=\\"cell\\">DOM element to use</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\">Bold style</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;
@@ -341,7 +426,7 @@ exports[`Table should set width automatically 1`] = `
`;
exports[`Table should work with other components 1`] = `
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th><div class=\\"thead-box\\">property</div></th><th><div class=\\"thead-box\\">description</div></th><th><div class=\\"thead-box\\">default</div></th></tr></thead><style>
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\">property</div></th><th class=\\"\\"><div class=\\"thead-box\\">description</div></th><th class=\\"\\"><div class=\\"thead-box\\">default</div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
@@ -386,7 +471,7 @@ exports[`Table should work with other components 1`] = `
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"hover\\"><td><div class=\\"cell\\">type</div></td><td><div class=\\"cell\\">Content type</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">Component</div></td><td><div class=\\"cell\\">DOM element to use</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">bold</div></td><td><div class=\\"cell\\">Bold style</div></td><td><div class=\\"cell\\">true</div></td></tr><tr class=\\"hover\\"><td><div class=\\"cell\\">bold</div></td><td><div class=\\"cell\\"><code>boolean</code></div></td><td><div class=\\"cell\\">true</div></td></tr><style>
</style><tbody><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td><td class=\\"\\"><div class=\\"cell\\">Content type</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td><td class=\\"\\"><div class=\\"cell\\">DOM element to use</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\">Bold style</div></td><td class=\\"\\"><div class=\\"cell\\">true</div></td></tr><tr class=\\"hover \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\"><code>boolean</code></div></td><td class=\\"\\"><div class=\\"cell\\">true</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;
@@ -426,7 +511,7 @@ exports[`Table should work with other components 1`] = `
`;
exports[`Table should work without hover effect 1`] = `
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th><div class=\\"thead-box\\">property</div></th><th><div class=\\"thead-box\\">description</div></th><th><div class=\\"thead-box\\">default</div></th></tr></thead><style>
"<table class=\\"\\"><colgroup></colgroup><thead><tr><th class=\\"\\"><div class=\\"thead-box\\">property</div></th><th class=\\"\\"><div class=\\"thead-box\\">description</div></th><th class=\\"\\"><div class=\\"thead-box\\">default</div></th></tr></thead><style>
thead {
border-collapse: separate;
border-spacing: 0;
@@ -471,7 +556,7 @@ exports[`Table should work without hover effect 1`] = `
min-height: calc(2.5 * var(--table-font-size));
text-transform: uppercase;
}
</style><tbody><tr class=\\"\\"><td><div class=\\"cell\\">type</div></td><td><div class=\\"cell\\">Content type</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"\\"><td><div class=\\"cell\\">Component</div></td><td><div class=\\"cell\\">DOM element to use</div></td><td><div class=\\"cell\\">-</div></td></tr><tr class=\\"\\"><td><div class=\\"cell\\">bold</div></td><td><div class=\\"cell\\">Bold style</div></td><td><div class=\\"cell\\">true</div></td></tr><style>
</style><tbody><tr class=\\" \\"><td class=\\"\\"><div class=\\"cell\\">type</div></td><td class=\\"\\"><div class=\\"cell\\">Content type</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\" \\"><td class=\\"\\"><div class=\\"cell\\">Component</div></td><td class=\\"\\"><div class=\\"cell\\">DOM element to use</div></td><td class=\\"\\"><div class=\\"cell\\">-</div></td></tr><tr class=\\" \\"><td class=\\"\\"><div class=\\"cell\\">bold</div></td><td class=\\"\\"><div class=\\"cell\\">Bold style</div></td><td class=\\"\\"><div class=\\"cell\\">true</div></td></tr><style>
tr {
transition: background-color 0.25s ease;
font-size: inherit;

View File

@@ -1,9 +1,9 @@
import React from 'react'
import { mount } from 'enzyme'
import { Table, Code } from 'components'
import { TableCellActions } from '../table-cell'
import { nativeEvent, updateWrapper } from 'tests/utils'
import { act } from 'react-dom/test-utils'
import { TableColumnRender } from 'components/table/table-types'
const data = [
{ property: 'type', description: 'Content type', default: '-' },
@@ -24,6 +24,20 @@ describe('Table', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should work correctly with multiple identical props', () => {
const wrapper = mount(
<Table data={data}>
<Table.Column prop="property" label="property" />
<Table.Column prop="description" label="description" />
<Table.Column prop="property" label="property2" />
<Table.Column prop="property" label="property3" />
<Table.Column prop="description" label="description2" />
</Table>,
)
expect(wrapper.html()).toMatchSnapshot()
expect(() => wrapper.unmount()).not.toThrow()
})
it('should re-render when data changed', async () => {
const wrapper = mount(
<Table data={data}>
@@ -99,47 +113,39 @@ describe('Table', () => {
expect(() => wrapper.unmount()).not.toThrow()
})
it('should be possible to remove the row', () => {
const operation = (actions: TableCellActions) => {
return <button onClick={() => actions.remove()}>Remove</button>
it('should be render specified elements', async () => {
type Item = {
property: string
description: string
operation: string
}
const data = [{ property: 'bold', description: 'boolean', operation }]
const wrapper = mount(
<Table data={data}>
<Table.Column prop="property" label="property" />
<Table.Column prop="description" label="description" />
<Table.Column prop="operation" label="operation" />
</Table>,
)
expect(wrapper.find('tbody').find('tr').length).toBe(1)
wrapper.find('tbody').find('button').simulate('click')
expect(wrapper.find('tbody').find('tr').length).toBe(0)
expect(() => wrapper.unmount()).not.toThrow()
})
it('should be possible to update the row', () => {
const operation = (actions: TableCellActions) => {
const renderAction: TableColumnRender<Item> = (value, rowData, index) => {
return (
<button
onClick={() =>
actions.update({ property: 'test', description: 'test', operation })
}>
Update
</button>
<div>
<button id="test-btn">Remove</button>
<div id="value">{value}</div>
<div id="row-data">{rowData.description}</div>
<div id="row-index">{index}</div>
</div>
)
}
const operation = Math.random().toString(16).slice(-10)
const data = [{ property: 'bold', description: 'boolean', operation }]
const wrapper = mount(
<Table data={data}>
<Table.Column prop="property" label="property" />
<Table.Column prop="description" label="description" />
<Table.Column prop="operation" label="operation" />
<Table<Item> data={data}>
<Table.Column<Item> prop="property" label="property" />
<Table.Column<Item> prop="description" label="description" />
<Table.Column<Item> prop="operation" label="operation" render={renderAction} />
</Table>,
)
expect(wrapper.find('tbody').find('tr').length).toBe(1)
wrapper.find('tbody').find('button').simulate('click')
expect(wrapper.find('tbody').find('tr').find('td').first().text()).toContain('test')
expect(() => wrapper.unmount()).not.toThrow()
const buttons = wrapper.find('tbody').find('#test-btn')
expect(buttons.length).not.toEqual(0)
const value = wrapper.find('tbody').find('#value').html()
expect(value).toMatch(operation)
const rowData = wrapper.find('tbody').find('#row-data').html()
expect(rowData).toMatch(`${data[0].description}`)
const rowIndex = wrapper.find('tbody').find('#row-index').html()
expect(rowIndex).toMatch(`0`)
})
it('should render emptyText when data missing', () => {

View File

@@ -1,24 +1,14 @@
import Table from './table'
import TableColumn from './table-column'
export type TableComponentType = typeof Table & {
Column: typeof TableColumn
}
;(Table as TableComponentType).Column = TableColumn
export type {
TableProps,
TableOnRow,
TableOnChange,
TableOnCell,
TableDataSource,
} from './table'
export type { TableProps } from './table'
export type { TableColumnProps } from './table-column'
export type {
TableOperation,
TableCellActions,
TableCellActionRemove,
TableCellActionUpdate,
TableCellData,
} from './table-cell'
export default Table as TableComponentType
TableOnCellClick,
TableAbstractColumn,
TableOnChange,
TableOnRowClick,
TableRowClassNameHandler,
TableDataItemBase,
TableColumnRender,
} from './table-types'
export default Table

View File

@@ -2,45 +2,54 @@ import React from 'react'
import useTheme from '../use-theme'
import TableCell from './table-cell'
import { useTableContext } from './table-context'
import {
TableDataItemBase,
TableOnCellClick,
TableOnRowClick,
TableRowClassNameHandler,
} from './table-types'
interface Props {
interface Props<TableDataItem extends TableDataItemBase> {
hover: boolean
emptyText: string
onRow: (row: any, index: number) => void
onCell: (cell: any, index: number, colunm: number) => void
data: Array<any>
onRow?: TableOnRowClick<TableDataItem>
onCell?: TableOnCellClick<TableDataItem>
data: Array<TableDataItem>
className?: string
rowClassName: TableRowClassNameHandler<TableDataItem>
}
const defaultProps = {
className: '',
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type TableBodyProps = Props & NativeAttrs
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props<any>>
export type TableBodyProps<TableDataItem> = Props<TableDataItem> & NativeAttrs
const TableBody: React.FC<TableBodyProps> = ({
const TableBody = <TableDataItem extends TableDataItemBase>({
data,
hover,
emptyText,
onRow,
onCell,
}: TableBodyProps & typeof defaultProps) => {
rowClassName,
}: TableBodyProps<TableDataItem> & typeof defaultProps) => {
const theme = useTheme()
const { columns } = useTableContext()
const rowClickHandler = (row: any, index: number) => {
onRow(row, index)
const { columns } = useTableContext<TableDataItem>()
const rowClickHandler = (row: TableDataItem, index: number) => {
onRow && onRow(row, index)
}
return (
<tbody>
{data.map((row, index) => {
const className = rowClassName(row, index)
return (
<tr
key={`tbody-row-${index}`}
className={hover ? 'hover' : ''}
className={`${hover ? 'hover' : ''} ${className}`}
onClick={() => rowClickHandler(row, index)}>
<TableCell
<TableCell<TableDataItem>
columns={columns}
row={row}
rowIndex={index}

View File

@@ -1,65 +1,47 @@
import React from 'react'
import { TableColumnItem, useTableContext } from './table-context'
import { TableDataItemBase, TableAbstractColumn, TableOnCellClick } from './table-types'
interface Props {
columns: Array<TableColumnItem>
row: any
interface Props<TableDataItem extends TableDataItemBase> {
columns: Array<TableAbstractColumn<TableDataItem>>
row: TableDataItem
rowIndex: number
emptyText: string
onCellClick: (cell: any, rowIndex: number, colunmIndex: number) => void
onCellClick?: TableOnCellClick<TableDataItem>
}
export type TableCellData = {
export type TableCellData<TableDataItem> = {
row: number
column: number
rowValue: any
rowValue: TableDataItem
}
export type TableCellActionRemove = () => void
export type TableCellActionUpdate = (data: any) => void
export type TableCellActions = {
update: TableCellActionUpdate
remove: TableCellActionRemove
}
export type TableOperation = (fn: TableCellActions, rowData: any) => any
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props<any>>
export type TableCellProps<TableDataItem extends TableDataItemBase> =
Props<TableDataItem> & NativeAttrs
const TableCell: React.FC<Props> = ({
const TableCell = <TableDataItem extends TableDataItemBase>({
columns,
row,
rowIndex,
emptyText,
onCellClick,
}) => {
const { removeRow, updateRow } = useTableContext()
const actions: TableCellActions = {
update: data => {
updateRow && updateRow(rowIndex, data)
},
remove: () => {
removeRow && removeRow(rowIndex)
},
}
}: TableCellProps<TableDataItem>) => {
/* eslint-disable react/jsx-no-useless-fragment */
return (
<>
{columns.map((column, index) => {
const data: TableCellData = {
row: rowIndex,
column: index,
rowValue: row,
}
const rowLabel = row[column.value]
const cellValue = !rowLabel
? emptyText
: typeof rowLabel === 'function'
? rowLabel(actions, data)
: rowLabel
const currentRowValue = row[column.prop]
const cellValue = currentRowValue || emptyText
const shouldBeRenderElement = column.renderHandler(currentRowValue, row, rowIndex)
return (
<td
key={`row-td-${index}-${column.value}`}
onClick={() => onCellClick(cellValue, rowIndex, index)}>
<div className="cell">{cellValue}</div>
key={`row-td-${index}-${column.prop}`}
onClick={() => onCellClick && onCellClick(currentRowValue, rowIndex, index)}
className={column.className}>
<div className="cell">
{shouldBeRenderElement ? shouldBeRenderElement : cellValue}
</div>
</td>
)
})}
@@ -68,4 +50,4 @@ const TableCell: React.FC<Props> = ({
/* eslint-enable */
}
export default React.memo(TableCell)
export default TableCell

View File

@@ -1,36 +1,52 @@
import React, { useEffect } from 'react'
import { useTableContext } from './table-context'
import useWarning from '../utils/use-warning'
import { TableColumnRender, TableDataItemBase } from './table-types'
interface Props {
prop: string
label?: string
width?: number
const defaultProps = {
className: '',
render: () => {},
}
export type TableColumnProps = Props
export type TableColumnProps<TableDataItem> = {
prop: keyof TableDataItem
label?: string
width?: number
className?: string
render?: TableColumnRender<TableDataItem>
}
const TableColumn: React.FC<React.PropsWithChildren<TableColumnProps>> = ({
children,
prop,
label,
width,
}) => {
const { updateColumn } = useTableContext()
if (!prop || prop.trim() === '') {
const TableColumn = <TableDataItem extends TableDataItemBase>(
columnProps: React.PropsWithChildren<TableColumnProps<TableDataItem>>,
) => {
const {
children,
prop,
label,
width,
className,
render: renderHandler,
} = columnProps as React.PropsWithChildren<TableColumnProps<TableDataItem>> &
typeof defaultProps
const { updateColumn } = useTableContext<TableDataItem>()
const safeProp = `${prop}`.trim()
if (!safeProp) {
useWarning('The props "prop" is required.', 'Table.Column')
}
useEffect(() => {
updateColumn &&
updateColumn({
label: children || label,
value: `${prop}`.trim(),
width,
})
}, [children, label, prop, width])
updateColumn({
label: children || label,
prop: safeProp,
width,
className,
renderHandler,
})
}, [children, label, prop, width, className, renderHandler])
return null
}
TableColumn.defaultProps = defaultProps
TableColumn.displayName = 'GiestTableColumn'
export default TableColumn

View File

@@ -1,23 +1,17 @@
import React from 'react'
import { TableAbstractColumn } from './table-types'
export type TableColumnItem = {
value: string
label: React.ReactNode | string
width?: number
}
export interface TableConfig {
columns: Array<TableColumnItem>
updateColumn?: (column: TableColumnItem) => void
removeRow?: (rowIndex: number) => void
updateRow?: (rowIndex: number, newData: any) => void
export interface TableConfig<T> {
columns: Array<TableAbstractColumn<T>>
updateColumn: (column: TableAbstractColumn<T>) => void
}
const defaultContext = {
columns: [],
updateColumn: () => {},
}
export const TableContext = React.createContext<TableConfig>(defaultContext)
export const TableContext = React.createContext<TableConfig<any>>(defaultContext)
export const useTableContext = (): TableConfig =>
React.useContext<TableConfig>(TableContext)
export const useTableContext = <T>(): TableConfig<T> =>
React.useContext<TableConfig<T>>(TableContext)

View File

@@ -1,10 +1,10 @@
import React, { useMemo } from 'react'
import useTheme from '../use-theme'
import { TableColumnItem } from './table-context'
import { TableAbstractColumn, TableDataItemBase } from './table-types'
interface Props {
interface Props<TableDataItem extends TableDataItemBase> {
width: number
columns: Array<TableColumnItem>
columns: Array<TableAbstractColumn<TableDataItem>>
className?: string
}
@@ -12,10 +12,13 @@ const defaultProps = {
className: '',
}
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type TableHeadProps = Props & NativeAttrs
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props<any>>
export type TableHeadProps<TableDataItem> = Props<TableDataItem> & NativeAttrs
const makeColgroup = (width: number, columns: Array<TableColumnItem>) => {
const makeColgroup = <TableDataItem,>(
width: number,
columns: Array<TableAbstractColumn<TableDataItem>>,
) => {
const unsetWidthCount = columns.filter(c => !c.width).length
const customWidthTotal = columns.reduce((pre, current) => {
return current.width ? pre + current.width : pre
@@ -31,11 +34,11 @@ const makeColgroup = (width: number, columns: Array<TableColumnItem>) => {
)
}
const TableHead: React.FC<TableHeadProps> = ({
columns,
width,
}: TableHeadProps & typeof defaultProps) => {
const TableHead = <TableDataItem extends TableDataItemBase>(
props: TableHeadProps<TableDataItem>,
) => {
const theme = useTheme()
const { columns, width } = props as TableHeadProps<TableDataItem> & typeof defaultProps
const isScalableWidth = useMemo(() => columns.find(item => !!item.width), [columns])
const colgroup = useMemo(() => {
if (!isScalableWidth) return <colgroup />
@@ -48,7 +51,7 @@ const TableHead: React.FC<TableHeadProps> = ({
<thead>
<tr>
{columns.map((column, index) => (
<th key={`table-th-${column.value}-${index}`}>
<th key={`table-th-${column.prop}-${index}`} className={column.className}>
<div className="thead-box">{column.label}</div>
</th>
))}

View File

@@ -0,0 +1,32 @@
import React from 'react'
export type TableDataItemBase = Record<string, any>
export type TableColumnRender<Item extends TableDataItemBase> = (
value: Item[keyof Item],
rowData: Item,
rowIndex: number,
) => JSX.Element | void
export type TableAbstractColumn<TableDataItem> = {
prop: keyof TableDataItem
label: React.ReactNode | string
className: string
width?: number
renderHandler: TableColumnRender<TableDataItem>
}
export type TableOnRowClick<TableDataItem> = (
rowData: TableDataItem,
rowIndex: number,
) => void
export type TableOnCellClick<TableDataItem> = (
cellValue: TableDataItem[keyof TableDataItem],
rowIndex: number,
colunmIndex: number,
) => void
export type TableOnChange<TableDataItem> = (data: Array<TableDataItem>) => void
export type TableRowClassNameHandler<TableDataItem> = (
rowData: TableDataItem,
rowIndex: number,
) => string

View File

@@ -3,107 +3,102 @@ import TableHead from './table-head'
import TableBody from './table-body'
import useRealShape from '../utils/use-real-shape'
import useResize from '../utils/use-resize'
import { TableContext, TableColumnItem, TableConfig } from './table-context'
import useCurrentState from '../utils/use-current-state'
import { TableOperation } from './table-cell'
import useScaleable, { withScaleable } from '../use-scaleable'
import { TableContext, TableConfig } from './table-context'
import {
TableAbstractColumn,
TableDataItemBase,
TableOnCellClick,
TableOnChange,
TableOnRowClick,
TableRowClassNameHandler,
} from './table-types'
import useScaleable, { ScaleableProps, withScaleable } from '../use-scaleable'
import TableColumn from './table-column'
export type TableOnRow = (row: any, index: number) => void
export type TableOnCell = (cell: any, index: number, colunm: number) => void
export type TableOnChange = (data: any) => void
export type TableDataSource<T extends { [key: string]: any }> = T & {
operation?: TableOperation
}
interface Props {
data?: Array<TableDataSource<any>>
interface Props<TableDataItem extends TableDataItemBase> {
data?: Array<TableDataItem>
initialData?: Array<TableDataItem>
emptyText?: string
hover?: boolean
onRow?: TableOnRow
onCell?: TableOnCell
onChange?: TableOnChange
onRow?: TableOnRowClick<TableDataItem>
onCell?: TableOnCellClick<TableDataItem>
onChange?: TableOnChange<TableDataItem>
className?: string
rowClassName?: TableRowClassNameHandler<TableDataItem>
}
const defaultProps = {
hover: true,
initialData: [],
emptyText: '',
onRow: (() => {}) as TableOnRow,
onCell: (() => {}) as TableOnCell,
onChange: (() => {}) as TableOnChange,
className: '',
rowClassName: () => '',
}
type NativeAttrs = Omit<React.TableHTMLAttributes<any>, keyof Props>
export type TableProps = Props & NativeAttrs
type NativeAttrs = Omit<React.TableHTMLAttributes<any>, keyof Props<any>>
export type TableProps<TableDataItem extends TableDataItemBase> = Props<TableDataItem> &
NativeAttrs
const TableComponent: React.FC<React.PropsWithChildren<TableProps>> = ({
children,
data,
hover,
emptyText,
onRow,
onCell,
onChange,
className,
...props
}: React.PropsWithChildren<TableProps> & typeof defaultProps) => {
function TableComponent<TableDataItem extends TableDataItemBase>(
tableProps: React.PropsWithChildren<TableProps<TableDataItem>>,
) {
/* eslint-disable @typescript-eslint/no-unused-vars */
const {
children,
data: customData,
initialData,
hover,
emptyText,
onRow,
onCell,
onChange,
className,
rowClassName,
...props
} = tableProps as React.PropsWithChildren<TableProps<TableDataItem>> &
typeof defaultProps
/* eslint-enable @typescript-eslint/no-unused-vars */
const { SCALES } = useScaleable()
const ref = useRef<HTMLTableElement>(null)
const [{ width }, updateShape] = useRealShape<HTMLTableElement>(ref)
const [columns, setColumns] = useState<Array<TableColumnItem>>([])
const [selfData, setSelfData, dataRef] = useCurrentState<Array<TableDataSource<any>>>(
[],
)
const updateColumn = (column: TableColumnItem) => {
const [columns, setColumns] = useState<Array<TableAbstractColumn<TableDataItem>>>([])
const [data, setData] = useState<Array<TableDataItem>>(initialData)
const updateColumn = (column: TableAbstractColumn<TableDataItem>) => {
setColumns(last => {
const hasColumn = last.find(item => item.value === column.value)
const hasColumn = last.find(item => item.prop === column.prop)
if (!hasColumn) return [...last, column]
return last.map(item => {
if (item.value !== column.value) return item
if (item.prop !== column.prop) return item
return column
})
})
}
const removeRow = (rowIndex: number) => {
const next = dataRef.current.filter((_, index) => index !== rowIndex)
onChange(next)
setSelfData([...next])
}
const updateRow = (rowIndex: number, newData: any) => {
const next = dataRef.current.map((data, index) =>
index === rowIndex ? { ...data, ...newData } : data,
)
onChange(next)
setSelfData([...next])
}
const initialValue = useMemo<TableConfig>(
const contextValue = useMemo<TableConfig<TableDataItem>>(
() => ({
columns,
updateColumn,
removeRow,
updateRow,
}),
[columns],
)
useEffect(() => {
if (!data) return
setSelfData(data)
}, [data])
if (typeof customData === 'undefined') return
setData(customData)
}, [customData])
useResize(() => updateShape())
return (
<TableContext.Provider value={initialValue}>
<TableContext.Provider value={contextValue}>
<table ref={ref} className={className} {...props}>
<TableHead columns={columns} width={width} />
<TableBody
data={selfData}
<TableBody<TableDataItem>
data={data}
hover={hover}
emptyText={emptyText}
onRow={onRow}
onCell={onCell}
rowClassName={rowClassName}
/>
{children}
@@ -126,5 +121,7 @@ const TableComponent: React.FC<React.PropsWithChildren<TableProps>> = ({
TableComponent.defaultProps = defaultProps
TableComponent.displayName = 'GeistTable'
const Table = withScaleable(TableComponent)
export default Table
TableComponent.Column = TableColumn
let Table = withScaleable(TableComponent) as any
Table.Column = TableColumn
export default Table as typeof TableComponent & ScaleableProps