diff --git a/README.md b/README.md index 86c4c56..3b391fb 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,29 @@ const { }); ``` +### `useTable` Arguments + +#### `columns` + +The first argument is an array of columns of type ColumnType. Each column has the following signature: + +```typescript +type ColumnType = { + name: string; + label?: string; + hidden?: boolean; + sort?: ((a: RowType, b: RowType) => number) | undefined; + render?: ({ value, row }: { value: any; row: T }) => React.ReactNode; + headerRender?: HeaderRenderType; +}; +``` + +Only name is required, the rest are optional arguments. + +#### `rows` + +Rows is the second argument to useTable and can be an array of any _object_ type. + ### Basic example ```tsx diff --git a/examples/material-ui/src/App.tsx b/examples/material-ui/src/App.tsx index 19e24d9..ca6c376 100644 --- a/examples/material-ui/src/App.tsx +++ b/examples/material-ui/src/App.tsx @@ -45,7 +45,9 @@ const columns = [ }, ]; -const data = [ +type DataType = { first_name: string, last_name: string, date_born: string }; + +const data: DataType[] = [ { first_name: 'Frodo', last_name: 'Baggins', @@ -69,10 +71,10 @@ function App() { originalRows, toggleSort, toggleAll, - } = useTable(columns, data, { + } = useTable(columns, data, { selectable: true, filter: useCallback( - (rows: RowType[]) => { + (rows: RowType[]) => { return rows.filter(row => { return ( row.cells.filter(cell => { @@ -107,7 +109,7 @@ function App() { {headers.map(column => ( toggleSort(column.name)}> - {column.label}{' '} + {column.render()}{' '} {column.sorted.on ? ( <> {column.sorted.asc ? ( diff --git a/package.json b/package.json index 403cf52..0e38833 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.5.1", + "version": "1.1.0", "license": "MIT", "author": "Gabriel Abud", "main": "dist/index.js", @@ -52,10 +52,10 @@ }, "repository": { "type": "git", - "url": "https://github.com/Buuntu/react-final-table.git" + "url": "https://github.com/Buuntu/react-final-table" }, "bugs": { - "url": "https://github.com/Buuntu/react-final-table" + "url": "https://github.com/Buuntu/react-final-table/issues" }, "homepage": "https://github.com/Buuntu/react-final-table#readme" -} \ No newline at end of file +} diff --git a/src/hooks.tsx b/src/hooks.tsx index a10eaa2..5937be0 100644 --- a/src/hooks.tsx +++ b/src/hooks.tsx @@ -1,4 +1,4 @@ -import { useMemo, useReducer, useEffect } from 'react'; +import { useMemo, useReducer, useEffect, ReactNode } from 'react'; import { ColumnByNamesType, @@ -9,7 +9,9 @@ import { UseTableReturnType, UseTableOptionsType, RowType, - ColumnByNameType, + HeaderType, + HeaderRenderType, + ColumnStateType, } from './types'; import { byTextAscending, byTextDescending } from './utils'; @@ -173,11 +175,14 @@ export const useTable = ( data: T[], options?: UseTableOptionsType ): UseTableReturnType => { - const columnsWithSorting = useMemo( + const columnsWithSorting: ColumnStateType[] = useMemo( () => columns.map(column => { return { ...column, + label: column.label ? column.label : column.name, + hidden: column.hidden ? column.hidden : false, + sort: column.sort, sorted: { on: false, asc: true, @@ -187,10 +192,10 @@ export const useTable = ( [columns] ); const columnsByName = useMemo(() => getColumnsByName(columnsWithSorting), [ - columns, + columnsWithSorting, ]); - const tableData = useMemo(() => { + const tableData: RowType[] = useMemo(() => { const sortedData = sortDataInOrder(data, columnsWithSorting); const newData = sortedData.map((row, idx) => { @@ -205,7 +210,7 @@ export const useTable = ( hidden: columnsByName[column].hidden, field: column, value: value, - render: makeRender(value, columnsByName[column], row), + render: makeRender(value, columnsByName[column].render, row), }; }) .filter(cell => !cell.hidden), @@ -225,6 +230,18 @@ export const useTable = ( filterOn: false, }); + const headers: HeaderType[] = useMemo(() => { + return [ + ...state.columns.map(column => { + const label = column.label ? column.label : column.name; + return { + ...column, + render: makeHeaderRender(label, column.headerRender), + }; + }), + ]; + }, [state.columns]); + useEffect(() => { if (options && options.filter) { dispatch({ type: 'GLOBAL_FILTER', filter: options.filter }); @@ -234,7 +251,7 @@ export const useTable = ( }, [options?.filter]); return { - headers: state.columns.filter(column => !column.hidden), + headers: headers.filter(column => !column.hidden), rows: state.rows, originalRows: state.originalRows, selectedRows: state.selectedRows, @@ -248,10 +265,17 @@ export const useTable = ( const makeRender = ( value: any, - column: ColumnByNameType, + render: (({ value, row }: { value: any; row: T }) => ReactNode) | undefined, row: T ) => { - return column.render ? () => column.render({ row, value }) : () => value; + return render ? () => render({ row, value }) : () => value; +}; + +const makeHeaderRender = ( + label: string, + render: HeaderRenderType | undefined +) => { + return render ? () => render({ label }) : () => label; }; const sortDataInOrder = ( @@ -278,6 +302,7 @@ const getColumnsByName = ( const col: any = { label: column.label, }; + if (column.render) { col['render'] = column.render; } diff --git a/src/test/selectionGlobalFiltering.spec.tsx b/src/test/selectionGlobalFiltering.spec.tsx index 452dcae..ab5cdf1 100644 --- a/src/test/selectionGlobalFiltering.spec.tsx +++ b/src/test/selectionGlobalFiltering.spec.tsx @@ -50,7 +50,7 @@ const TableWithSelection = ({ {headers.map((header, idx) => ( - {header.label} + {header.render()} ))} @@ -135,7 +135,7 @@ const TableWithFilter = ({ {headers.map((header, idx) => ( - {header.label} + {header.render()} ))} @@ -217,7 +217,7 @@ const TableWithSelectionAndFiltering = ({ {headers.map((header, idx) => ( - {header.label} + {header.render()} ))} diff --git a/src/test/table.spec.tsx b/src/test/table.spec.tsx index 151231b..80fb803 100644 --- a/src/test/table.spec.tsx +++ b/src/test/table.spec.tsx @@ -43,7 +43,7 @@ const Table = ({ {headers.map((header, idx) => ( - {header.label} + {header.render()} ))} @@ -99,12 +99,30 @@ const columnsWithRender: ColumnType[] = [ }, ]; -test('Should see custom render HTML', () => { +test('Should see custom row render HTML', () => { const rtl = render(); expect(rtl.getAllByTestId('first-name')).toHaveLength(2); }); +const columnsWithColRender: ColumnType[] = [ + { + name: 'firstName', + label: 'First Name', + headerRender: ({ label }) =>

{label}

, + }, + { + name: 'lastName', + label: 'Last Name', + }, +]; + +test('Should see custom column render HTML', () => { + const rtl = render(
); + + expect(rtl.getAllByTestId('first-name')).toHaveLength(1); +}); + // to supress console error from test output beforeEach(() => { jest.spyOn(console, 'error').mockImplementation(() => {}); diff --git a/src/types.ts b/src/types.ts index 3e6a6b4..7a2114d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,9 +3,24 @@ export type ColumnType = { label?: string; hidden?: boolean; sort?: ((a: RowType, b: RowType) => number) | undefined; - render?: (value: any) => React.ReactNode; + render?: ({ value, row }: { value: any; row: T }) => React.ReactNode; + headerRender?: HeaderRenderType; }; +export type ColumnStateType = { + name: string; + label: string; + hidden: boolean; + sort?: ((a: RowType, b: RowType) => number) | undefined; + sorted: { + on: boolean; + asc: boolean; + }; + headerRender?: HeaderRenderType; +}; + +export type HeaderRenderType = ({ label }: { label: any }) => React.ReactNode; + // this is the type saved as state and returned export type HeaderType = { name: string; @@ -16,23 +31,23 @@ export type HeaderType = { asc: boolean; }; sort?: ((a: RowType, b: RowType) => number) | undefined; - render?: (value: any) => React.ReactNode; + render: () => React.ReactNode; }; export type DataType = { [key: string]: any }; export type ColumnByNamesType = { - [key: string]: ColumnByNameType; + [key: string]: ColumnType; }; -export type RenderFunctionType = ({ +export type RenderFunctionType = ({ value, row, -}: RenderFunctionArgsType) => React.ReactNode | undefined; +}: RenderFunctionArgsType) => React.ReactNode | undefined; -type RenderFunctionArgsType = { +type RenderFunctionArgsType = { value: any; - row: Object; + row: T; }; export type ColumnByNameType = Omit< @@ -93,7 +108,7 @@ export interface UseTableReturnType { export type TableState = { columnsByName: ColumnByNamesType; - columns: HeaderType[]; + columns: ColumnStateType[]; rows: RowType[]; originalRows: RowType[]; selectedRows: RowType[];