diff --git a/packages/table/src/helpers/afterRow/addAfterRowsToData.ts b/packages/table/src/helpers/afterRow/addAfterRowsToData.ts index e4cd292a..3d597c6c 100644 --- a/packages/table/src/helpers/afterRow/addAfterRowsToData.ts +++ b/packages/table/src/helpers/afterRow/addAfterRowsToData.ts @@ -9,3 +9,18 @@ export const addAfterRowsToData = (data: TData) => { }, [] as TData); return [newData, afterRowIds]; }; + +export const addAfterRowToData = (data: TData, id: string | number) => + data.reduce( + (prev, row) => + row.id === id + ? prev.concat([row].concat([{ ...row, id: `afterRow-${id}` }])) + : prev.concat([row]), + [] as TData, + ); + +export const deleteAfterRowToDataById = (data: TData, id: string) => + data.filter((d) => d.id !== id); + +export const findDataById = (data: TData, id: string) => + data.find((d) => d.id === id); diff --git a/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.test.ts b/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.test.ts new file mode 100644 index 00000000..4010c2c7 --- /dev/null +++ b/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.test.ts @@ -0,0 +1,83 @@ +import { describe, test, expect } from 'vitest'; + +import { TRowDef } from '../../declarations'; +import { + addAfterRowToRowDefs, + getRowDef, + setHideRowDef, +} from './addAfterRowsToRowDefs'; + +describe('Add afterrow to row defs', () => { + describe('getRowDef', () => { + const cases: [string, TRowDef[], string, TRowDef][] = [ + ['empty row defs return undefined', [], '1', undefined], + ['id null return undefined', [{ id: '1' }], null, undefined], + ['rowDef null return undefined', null, '1', undefined], + ['return element rowDef', [{ id: '1' }], '1', { id: '1' }], + ['id not find in rowDef', [{ id: '1' }], '2', undefined], + ]; + + test.each(cases)('%s', (_title, rowDefs, id, expected) => { + expect(getRowDef(rowDefs, id)).toEqual(expected); + }); + }); + + describe('addAfterRowToRowDefs', () => { + const cases: [string, TRowDef[], string, TRowDef[]][] = [ + [ + 'add element with rowdefs empty', + [], + '1', + [{ id: `afterRow-1`, hide: false }], + ], + [ + 'add element with rowdefs > 0', + [{ id: `afterRow-1`, hide: false }], + '2', + [ + { id: `afterRow-1`, hide: false }, + { id: `afterRow-2`, hide: false }, + ], + ], + ['add element with rowdefs empty', null, '1', undefined], + ]; + + test.each(cases)('%s', (_title, rowDefs, id, expected) => { + expect(addAfterRowToRowDefs(rowDefs, id, () => null, 13)).toMatchObject( + expected, + ); + }); + }); + + describe('setHideRowDef', () => { + const cases: [string, TRowDef[], string, boolean, TRowDef[]][] = [ + ['empty rowdefs return empty rowdefs', [], '1', true, []], + ['rowdefs null return undefined', null, '1', true, undefined], + [ + 'changed visible to no visible', + [{ id: `afterRow-1`, hide: false }], + '1', + false, + [{ id: `afterRow-1`, hide: true }], + ], + [ + 'changed no visible to visible', + [{ id: `afterRow-1`, hide: true }], + '1', + false, + [{ id: `afterRow-1`, hide: true }], + ], + [ + 'id and isOpened null return same rowdefs', + [{ id: `afterRow-1`, hide: true }], + null, + null, + [{ id: `afterRow-1`, hide: true }], + ], + ]; + + test.each(cases)('%s', (_title, rowDefs, id, isOpened, expected) => { + expect(setHideRowDef(rowDefs, id, isOpened)).toEqual(expected); + }); + }); +}); diff --git a/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.ts b/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.ts index 0e38e544..959697e6 100644 --- a/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.ts +++ b/packages/table/src/helpers/afterRow/addAfterRowsToRowDefs.ts @@ -2,13 +2,52 @@ import * as React from 'react'; import { TCellRenderer, TRowDef } from '../../declarations'; export const addAfterRowsToRowDefs = ( + rowDefs: TRowDef[], ids: string[], - afterRowRenderer: React.FC, - afterRowHeight: number + afterRowRenderer: React.FC + | (({ value, colDef, rowIndex, row }: TCellRenderer) => React.ReactNode), + afterRowHeight: number, ) => - ids.map((id) => ({ - id, - hide: true, + ids.map((id) => { + const rowDef = getRowDef(rowDefs, id); + + const defaultRowDef = { + id, + hide: true, + cellRenderer: afterRowRenderer, + height: afterRowHeight, + }; + + return rowDef + ? { ...defaultRowDef, ...rowDef } + : { + id, + hide: true, + cellRenderer: afterRowRenderer, + height: afterRowHeight, + }; + }) as TRowDef[]; + +export const getRowDef = (rowDefs: TRowDef[], id: string) => + rowDefs?.find((r) => r.id === id); + +export const addAfterRowToRowDefs = ( + rowDefs: TRowDef[], + id, + afterRowRenderer, + afterRowHeight, +) => + rowDefs?.concat({ + id: `afterRow-${id}`, + hide: false, cellRenderer: afterRowRenderer, height: afterRowHeight, - })) as TRowDef[]; + }); + +export const setHideRowDef = (rowDefs: TRowDef[], id, isOpened) => + rowDefs?.map((rowDef: TRowDef) => { + if (rowDef.id === `afterRow-${id}`) { + rowDef.hide = !isOpened; + } + return rowDef; + }); diff --git a/packages/table/src/hooks/useAfterRow/selection.test.ts b/packages/table/src/hooks/useAfterRow/selection.test.ts new file mode 100644 index 00000000..70170752 --- /dev/null +++ b/packages/table/src/hooks/useAfterRow/selection.test.ts @@ -0,0 +1,14 @@ +import { describe, test, expect } from 'vitest'; + +import { updateSelection } from './selection'; + +describe('updateSelection', () => { + const cases: [string, string[], string, string[]][] = [ + ['return array with id', [], '1', ['1']], + ['return array without id', ['1'], '1', []], + ]; + + test.each(cases)('%s', (_title, selection, id, expected) => { + expect(updateSelection(selection)(id)).toEqual(expected); + }); +}); diff --git a/packages/table/src/hooks/useAfterRow/useAfterRow.test.ts b/packages/table/src/hooks/useAfterRow/useAfterRow.test.ts deleted file mode 100644 index 143e87b8..00000000 --- a/packages/table/src/hooks/useAfterRow/useAfterRow.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, test, expect } from 'vitest'; - -import { useAfterRow } from './useAfterRow'; -import { TRowDef } from '../declarations'; - -const data = [ - { - id: '1', - name: 'Santi', - }, - { - id: '2', - name: 'Javi', - }, - { - id: '3', - name: 'Horacio', - }, - { - id: '4', - name: 'Jose', - }, -]; - -describe('useAfterRow', () => { - const cases: [string, unknown, TRowDef[], unknown][] = [ - [ - 'basic', - data, - [], - [ - [ - { - id: '1', - name: 'Santi', - }, - { - id: "1'", - name: 'Santi', - }, - { - id: '2', - name: 'Javi', - }, - { - id: "2'", - name: 'Javi', - }, - { - id: '3', - name: 'Horacio', - }, - { - id: "3'", - name: 'Horacio', - }, - { - id: '4', - name: 'Jose', - }, - { - id: "4'", - name: 'Jose', - }, - ], - [ - { - id: "1'", - hide: true, - }, - { - id: "2'", - hide: true, - }, - { - id: "3'", - hide: true, - }, - { - id: "4'", - hide: true, - }, - ], - ], - ], - ]; - - test.each(cases)('%s', (_title, data, afterRow, expected) => { - const { addAfterRow } = useAfterRow({ data, afterRow }); - expect(addAfterRow()).toEqual(expected); - }); -}); diff --git a/packages/table/src/hooks/useAfterRow/useAfterRow.ts b/packages/table/src/hooks/useAfterRow/useAfterRow.ts index f3f86e2f..569ad6fd 100644 --- a/packages/table/src/hooks/useAfterRow/useAfterRow.ts +++ b/packages/table/src/hooks/useAfterRow/useAfterRow.ts @@ -3,18 +3,28 @@ import * as React from 'react'; import { RowGroupingRenderer } from '../../renderers'; import { TRowGroupingContext } from '../../facade'; import { updateSelection } from './selection'; -import { TColDef, TRowDef } from '../../declarations'; +import { TCellRenderer, TColDef, TData, TRowDef } from '../../declarations'; +import { + addAfterRowToData, + addAfterRowToRowDefs, + deleteAfterRowToDataById, + findDataById, + getRowDef, + setHideRowDef, +} from '../../helpers'; -export const useAfterRow = ({ +export const useRenderAfterRow = ({ rowDefs, + initialSelection, onRowDefsChange, colDefs: originalColDefs, }: { rowDefs: TRowDef[]; + initialSelection: string[]; onRowDefsChange: (rowDefs: TRowDef[]) => void; colDefs: TColDef[]; }) => { - const [selection, setSelection] = React.useState([]); + const [selection, setSelection] = React.useState(initialSelection); const colDefs = [ { @@ -45,3 +55,61 @@ export const useAfterRow = ({ colDefs, }; }; + +export const useOnDemandAfterRow = ({ + rowDefs, + onRowDefsChange, + colDefs: originalColDefs, + data, + onDataChange, + afterRowRenderer, + afterRowHeight, +}: { + rowDefs: TRowDef[]; + onRowDefsChange: (rowDefs: TRowDef[]) => void; + colDefs: TColDef[]; + data: TData[]; + onDataChange: (data: TData[]) => void; + afterRowRenderer: React.FC + | (({ value, colDef, rowIndex, row }: TCellRenderer) => React.ReactNode); + afterRowHeight: React.CSSProperties['height']; +}) => { + const [selection, setSelection] = React.useState([]); + + const colDefs = [ + { + id: 'rowGrouping', + cellRenderer: RowGroupingRenderer, + width: 46, + context: { + selection, + onClick: (rowId) => { + const nextSelection = updateSelection(selection)(rowId as string); + setSelection(nextSelection); + const isOpened = nextSelection.includes(rowId); + + onRowDefsChange( + !getRowDef(rowDefs, `afterRow-${rowId}`) + ? addAfterRowToRowDefs( + rowDefs, + rowId, + afterRowRenderer, + afterRowHeight, + ) + : setHideRowDef(rowDefs, rowId, isOpened), + ); + onDataChange( + findDataById(data, `afterRow-${rowId}`) + ? deleteAfterRowToDataById(data, `afterRow-${rowId}`) + : addAfterRowToData(data, rowId), + ); + }, + } as TRowGroupingContext, + }, + ...originalColDefs, + ]; + + return { + colDefs, + }; +}; diff --git a/packages/table/src/hooks/useTableVirtualizationRow.ts b/packages/table/src/hooks/useTableVirtualizationRow.ts index a13dad4a..9ebd67d7 100644 --- a/packages/table/src/hooks/useTableVirtualizationRow.ts +++ b/packages/table/src/hooks/useTableVirtualizationRow.ts @@ -3,7 +3,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { TableContext } from '../context/TableContext'; import { ROW_HEIGHT_MD } from '../constants'; -import { getRowDef } from '../core/Row/utils'; +import { getRowDef } from '../helpers'; type TUseVirtualizationParamsRow = { ref: React.MutableRefObject;