diff --git a/app/client/src/widgets/TableWidgetV2/component/Constants.ts b/app/client/src/widgets/TableWidgetV2/component/Constants.ts index 182eb402073a..88ad92b6cf11 100644 --- a/app/client/src/widgets/TableWidgetV2/component/Constants.ts +++ b/app/client/src/widgets/TableWidgetV2/component/Constants.ts @@ -54,6 +54,7 @@ export enum ImageSizes { LARGE = "128px", } +export const SCROLL_BAR_OFFSET = 2; export const TABLE_SIZES: { [key: string]: TableSizes } = { [CompactModeTypes.DEFAULT]: { COLUMN_HEADER_HEIGHT: 32, diff --git a/app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx b/app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx deleted file mode 100644 index a759641e6218..000000000000 --- a/app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from "react"; -import type { - TableBodyPropGetter, - TableBodyProps, - Row as ReactTableRowType, -} from "react-table"; -import type { ReactElementType } from "react-window"; -import SimpleBar from "simplebar-react"; -import "simplebar-react/dist/simplebar.min.css"; -import type { ReactTableColumnProps, TableSizes } from "./Constants"; -import { MULTISELECT_CHECKBOX_WIDTH, TABLE_SCROLLBAR_WIDTH } from "./Constants"; -import type { TableColumnHeaderProps } from "./header/TableColumnHeader"; -import TableColumnHeader from "./header/TableColumnHeader"; -import { TableBody } from "./TableBody"; - -type StaticTableProps = TableColumnHeaderProps & { - getTableBodyProps( - propGetter?: TableBodyPropGetter> | undefined, - ): TableBodyProps; - pageSize: number; - height: number; - width?: number; - tableSizes: TableSizes; - innerElementType?: ReactElementType; - accentColor: string; - borderRadius: string; - multiRowSelection?: boolean; - prepareRow?(row: ReactTableRowType>): void; - selectTableRow?: (row: { - original: Record; - index: number; - }) => void; - selectedRowIndex: number; - selectedRowIndices: number[]; - columns: ReactTableColumnProps[]; - primaryColumnId?: string; - isAddRowInProgress: boolean; - headerProps?: TableColumnHeaderProps | Record; - totalColumnsWidth?: number; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - scrollContainerStyles: any; - useVirtual: boolean; - tableBodyRef?: React.MutableRefObject; - isLoading: boolean; - loadMoreFromEvaluations: () => void; -}; - -const StaticTable = (props: StaticTableProps, ref: React.Ref) => { - return ( - - - - - ); -}; - -export default React.forwardRef(StaticTable); diff --git a/app/client/src/widgets/TableWidgetV2/component/StaticTable/StaticTableBody/index.tsx b/app/client/src/widgets/TableWidgetV2/component/StaticTable/StaticTableBody/index.tsx new file mode 100644 index 000000000000..9e864a4a44b3 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/StaticTable/StaticTableBody/index.tsx @@ -0,0 +1,21 @@ +import { EmptyRows } from "../../TableBodyCoreComponents/Row"; + +import { Row } from "../../TableBodyCoreComponents/Row"; + +import React from "react"; +import { useAppsmithTable } from "../../TableContext"; + +export const StaticTableBodyComponent = () => { + const { getTableBodyProps, pageSize, subPage: rows } = useAppsmithTable(); + + return ( +
+ {rows.map((row, index) => { + return ; + })} + {pageSize > rows.length && ( + + )} +
+ ); +}; diff --git a/app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTable.test.tsx b/app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTable.test.tsx new file mode 100644 index 000000000000..fe61381da434 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTable.test.tsx @@ -0,0 +1,105 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import StaticTable from "../index"; +import { useAppsmithTable } from "../../TableContext"; +import type SimpleBar from "simplebar-react"; + +// Mock SimpleBar +jest.mock("simplebar-react", () => { + return { + __esModule: true, + default: React.forwardRef< + HTMLDivElement, + { children: React.ReactNode; style?: React.CSSProperties } + >((props, ref) => ( +
+ {props.children} +
+ )), + }; +}); + +// Mock the required dependencies +jest.mock("../../TableContext", () => ({ + useAppsmithTable: jest.fn(), +})); + +jest.mock("../../header/TableColumnHeader", () => ({ + __esModule: true, + default: () =>
Table Header
, +})); + +jest.mock("../StaticTableBody", () => ({ + StaticTableBodyComponent: () => ( +
Table Body
+ ), +})); + +describe("StaticTable", () => { + const mockScrollContainerStyles = { + height: 400, + width: 800, + }; + + beforeEach(() => { + (useAppsmithTable as jest.Mock).mockReturnValue({ + scrollContainerStyles: mockScrollContainerStyles, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("renders the table with header and body components", () => { + const ref = React.createRef(); + + render(); + + expect(screen.getByTestId("mock-table-header")).toBeInTheDocument(); + expect(screen.getByTestId("mock-table-body")).toBeInTheDocument(); + }); + + it("applies correct scroll container styles from context", () => { + const ref = React.createRef(); + const { container } = render(); + + const simpleBarElement = container.querySelector( + ".simplebar-content-wrapper", + ); + + expect(simpleBarElement).toHaveStyle({ + height: `${mockScrollContainerStyles.height}px`, + width: `${mockScrollContainerStyles.width}px`, + }); + }); + + it("forwards ref correctly", () => { + const ref = React.createRef(); + + render(); + + expect(ref.current).toBeTruthy(); + // Instead of instanceof check, verify the ref points to the correct element + expect(ref.current).toHaveClass("simplebar-content-wrapper"); + }); + + it("throws error when used outside TableContext", () => { + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + (useAppsmithTable as jest.Mock).mockImplementation(() => { + throw new Error("useTable must be used within a TableProvider"); + }); + + const ref = React.createRef(); + + expect(() => render()).toThrow( + "useTable must be used within a TableProvider", + ); + + consoleErrorSpy.mockRestore(); + }); +}); diff --git a/app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTableBody.test.tsx b/app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTableBody.test.tsx new file mode 100644 index 000000000000..98ff9e107ddc --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTableBody.test.tsx @@ -0,0 +1,256 @@ +import { render } from "@testing-library/react"; +import React from "react"; +import { EmptyRows, Row } from "../../TableBodyCoreComponents/Row"; +import * as TableContext from "../../TableContext"; +import { useAppsmithTable } from "../../TableContext"; +import { StaticTableBodyComponent } from "../StaticTableBody"; +import type { Row as ReactTableRowType } from "react-table"; +import { TABLE_SIZES, CompactModeTypes } from "../../Constants"; +import type { TableContextState } from "../../TableContext"; + +// Mock the required dependencies +jest.mock("../../TableContext", () => ({ + useAppsmithTable: jest.fn(), +})); + +jest.mock("../../TableBodyCoreComponents/Row", () => ({ + Row: jest.fn(({ index }) => ( +
+ Row {index} +
+ )), + EmptyRows: jest.fn(() =>
), +})); + +describe("StaticTableBodyComponent", () => { + const mockGetTableBodyProps = jest.fn(() => ({ + "data-testid": "table-body", + })); + + const createMockRow = (id: number, index: number) => + ({ + index, + original: { id }, + id: String(id), + getRowProps: () => ({ key: `row-${id}` }), + }) as unknown as ReactTableRowType>; + + const mockTableContextState = { + scrollContainerStyles: { height: 400, width: 800 }, + tableSizes: TABLE_SIZES[CompactModeTypes.DEFAULT], + getTableBodyProps: mockGetTableBodyProps, + pageSize: 0, + subPage: [], + } as unknown as TableContextState; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("throws error when used outside TableContext", () => { + const consoleErrorSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + (useAppsmithTable as jest.Mock).mockImplementation(() => { + throw new Error("useTable must be used within a TableProvider"); + }); + + expect(() => render()).toThrow( + "useTable must be used within a TableProvider", + ); + + consoleErrorSpy.mockRestore(); + }); + + describe("Rows", () => { + it("renders rows correctly(EmptyRows not required)", () => { + const mockRows = [createMockRow(1, 0), createMockRow(2, 1)]; + + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 2, + subPage: mockRows, + } as unknown as TableContextState); + + const { container, getAllByTestId } = render( + , + ); + + expect( + container.querySelector('[data-testid="table-body"]'), + ).toBeTruthy(); + expect(getAllByTestId("mocked-row")).toHaveLength(2); + expect(Row).toHaveBeenCalledTimes(2); + expect(EmptyRows).not.toHaveBeenCalled(); + }); + + it("passes correct props to Row component", () => { + const mockRows = [createMockRow(1, 0)]; + + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 1, + subPage: mockRows, + } as unknown as TableContextState); + + render(); + + expect(Row).toHaveBeenCalledWith( + { + index: 0, + row: mockRows[0], + }, + expect.anything(), + ); + }); + + it("should not render Rows or EmptyRows when pageSize is 0", () => { + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 0, + subPage: [], + } as unknown as TableContextState); + + const { container, queryByTestId } = render(); + + expect(container.querySelector(".tbody")).toBeTruthy(); + expect(queryByTestId("mocked-row")).not.toBeTruthy(); + expect(queryByTestId("mocked-empty-rows")).not.toBeTruthy(); + }); + + it("[Performance] should maintain rendering performance with large data sets", () => { + const largeDataSet = Array.from({ length: 1000 }, (_, index) => + createMockRow(index, index), + ); + + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 1000, + subPage: largeDataSet, + } as unknown as TableContextState); + + const { getAllByTestId } = render(); + + expect(getAllByTestId("mocked-row")).toHaveLength(1000); + }); + + it("should preserve row order during re-renders", () => { + const mockRows = [1, 2, 3].map((id, index) => createMockRow(id, index)); + + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 3, + subPage: mockRows, + } as unknown as TableContextState); + + const { getAllByTestId, rerender } = render(); + + // Initial render check + let renderedRows = getAllByTestId("mocked-row"); + + expect(renderedRows).toHaveLength(3); + expect(renderedRows[0].getAttribute("data-index")).toBe("0"); + expect(renderedRows[1].getAttribute("data-index")).toBe("1"); + expect(renderedRows[2].getAttribute("data-index")).toBe("2"); + + // Re-render with the same rows + rerender(); + + // Check if the order is preserved + renderedRows = getAllByTestId("mocked-row"); + expect(renderedRows).toHaveLength(3); + expect(renderedRows[0].getAttribute("data-index")).toBe("0"); + expect(renderedRows[1].getAttribute("data-index")).toBe("1"); + expect(renderedRows[2].getAttribute("data-index")).toBe("2"); + }); + + it("should update the number of Row components when rows data changes", () => { + const initialRows = [1, 2].map((id, index) => createMockRow(id, index)); + const updatedRows = [1, 2, 3, 4].map((id, index) => + createMockRow(id, index), + ); + const mockUseAppsmithTable = jest.spyOn(TableContext, "useAppsmithTable"); + + // Initial render with initialRows + mockUseAppsmithTable.mockReturnValue({ + ...mockTableContextState, + pageSize: 4, + subPage: initialRows, + } as unknown as TableContextState); + + const { getAllByTestId, rerender } = render(); + + expect(getAllByTestId("mocked-row")).toHaveLength(2); + + // Rerender with updatedRows + mockUseAppsmithTable.mockReturnValue({ + ...mockTableContextState, + pageSize: 4, + subPage: updatedRows, + } as unknown as TableContextState); + + rerender(); + expect(getAllByTestId("mocked-row")).toHaveLength(4); + }); + }); + + describe("EmptyRows", () => { + it("should render EmptyRows when rows array is empty and pageSize is greater than zero", () => { + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 5, + subPage: [], + } as unknown as TableContextState); + + const { container, getByTestId } = render(); + + expect(container.querySelector(".tbody")).toBeTruthy(); + expect(getByTestId("mocked-empty-rows")).toBeTruthy(); + expect(EmptyRows).toHaveBeenCalledWith( + { rowCount: 5 }, + expect.anything(), + ); + }); + + it("renders empty rows when pageSize is greater than rows length", () => { + const mockRows = [createMockRow(1, 0)]; + + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 3, + subPage: mockRows, + } as unknown as TableContextState); + + const { container, getByTestId } = render(); + + expect( + container.querySelector('[data-testid="table-body"]'), + ).toBeTruthy(); + expect(getByTestId("mocked-empty-rows")).toBeTruthy(); + expect(Row).toHaveBeenCalledTimes(1); + expect(EmptyRows).toHaveBeenCalledWith( + { rowCount: 2 }, + expect.anything(), + ); + }); + + it("does not render empty rows when pageSize equals rows length", () => { + const mockRows = [createMockRow(1, 0), createMockRow(2, 1)]; + + (useAppsmithTable as jest.Mock).mockReturnValue({ + ...mockTableContextState, + pageSize: 2, + subPage: mockRows, + } as unknown as TableContextState); + + const { container, queryByTestId } = render(); + + expect( + container.querySelector('[data-testid="table-body"]'), + ).toBeTruthy(); + expect(queryByTestId("mocked-empty-rows")).toBeNull(); + expect(EmptyRows).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx b/app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx new file mode 100644 index 000000000000..1f27189efdc6 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import SimpleBar from "simplebar-react"; +import "simplebar-react/dist/simplebar.min.css"; +import TableColumnHeader from "../header/TableColumnHeader"; +import { useAppsmithTable } from "../TableContext"; +import { StaticTableBodyComponent } from "./StaticTableBody"; + +interface StaticTableProps {} +const StaticTable = (_: StaticTableProps, ref: React.Ref) => { + const { scrollContainerStyles } = useAppsmithTable(); + + return ( + + + + + ); +}; + +export default React.forwardRef(StaticTable); diff --git a/app/client/src/widgets/TableWidgetV2/component/Table.tsx b/app/client/src/widgets/TableWidgetV2/component/Table.tsx index 5045d277d58f..dc1a9ed02296 100644 --- a/app/client/src/widgets/TableWidgetV2/component/Table.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/Table.tsx @@ -1,52 +1,34 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; +import { Classes } from "@blueprintjs/core"; +import { Classes as PopOver2Classes } from "@blueprintjs/popover2"; +import { Colors } from "constants/Colors"; +import { CONNECT_BUTTON_TEXT, createMessage } from "ee/constants/messages"; +import fastdom from "fastdom"; import { reduce } from "lodash"; +import React, { useCallback, useEffect, useMemo, useRef } from "react"; import type { Row as ReactTableRowType } from "react-table"; import { - useTable, - usePagination, useBlockLayout, + usePagination, useResizeColumns, useRowSelect, + useTable, } from "react-table"; import { useSticky } from "react-table-sticky"; -import { - TableWrapper, - TableHeaderWrapper, - TableHeaderInnerWrapper, -} from "./TableStyledWrappers"; -import TableHeader from "./header"; -import { Classes } from "@blueprintjs/core"; -import type { - ReactTableColumnProps, - ReactTableFilter, - CompactMode, - AddNewRowActions, - StickyType, -} from "./Constants"; -import { - TABLE_SIZES, - CompactModeTypes, - TABLE_SCROLLBAR_HEIGHT, -} from "./Constants"; -import { Colors } from "constants/Colors"; -import type { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { - ColumnTypes, - type EditableCell, - type TableVariant, -} from "../constants"; -import SimpleBar from "simplebar-react"; +import type SimpleBar from "simplebar-react"; import "simplebar-react/dist/simplebar.min.css"; import { createGlobalStyle } from "styled-components"; -import { Classes as PopOver2Classes } from "@blueprintjs/popover2"; -import StaticTable from "./StaticTable"; -import VirtualTable from "./VirtualTable"; -import fastdom from "fastdom"; import { ConnectDataOverlay } from "widgets/ConnectDataOverlay"; +import { ColumnTypes } from "../constants"; import { TABLE_CONNECT_OVERLAY_TEXT } from "../constants/messages"; -import { createMessage, CONNECT_BUTTON_TEXT } from "ee/constants/messages"; +import type { ReactTableColumnProps, StickyType } from "./Constants"; +import { CompactModeTypes, TABLE_SIZES } from "./Constants"; +import TableHeader from "./header"; +import StaticTable from "./StaticTable"; +import { TableProvider } from "./TableContext"; +import { TableWrapper } from "./TableStyledWrappers"; +import type { TableProps } from "./types"; +import VirtualTable from "./VirtualTable"; -const SCROLL_BAR_OFFSET = 2; const HEADER_MENU_PORTAL_CLASS = ".header-menu-portal"; const PopoverStyles = createGlobalStyle<{ @@ -65,78 +47,6 @@ const PopoverStyles = createGlobalStyle<{ } `; -export interface TableProps { - width: number; - height: number; - pageSize: number; - widgetId: string; - widgetName: string; - searchKey: string; - isLoading: boolean; - columnWidthMap?: { [key: string]: number }; - columns: ReactTableColumnProps[]; - data: Array>; - totalRecordsCount?: number; - editMode: boolean; - editableCell: EditableCell; - sortTableColumn: (columnIndex: number, asc: boolean) => void; - handleResizeColumn: (columnWidthMap: { [key: string]: number }) => void; - handleReorderColumn: (columnOrder: string[]) => void; - selectTableRow: (row: { - original: Record; - index: number; - }) => void; - pageNo: number; - updatePageNo: (pageNo: number, event?: EventType) => void; - multiRowSelection?: boolean; - isSortable?: boolean; - nextPageClick: () => void; - prevPageClick: () => void; - serverSidePaginationEnabled: boolean; - selectedRowIndex: number; - selectedRowIndices: number[]; - disableDrag: () => void; - enableDrag: () => void; - toggleAllRowSelect: ( - isSelect: boolean, - pageData: ReactTableRowType>[], - ) => void; - triggerRowSelection: boolean; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - searchTableData: (searchKey: any) => void; - filters?: ReactTableFilter[]; - applyFilter: (filters: ReactTableFilter[]) => void; - compactMode?: CompactMode; - isVisibleDownload?: boolean; - isVisibleFilters?: boolean; - isVisiblePagination?: boolean; - isVisibleSearch?: boolean; - delimiter: string; - accentColor: string; - borderRadius: string; - boxShadow: string; - borderWidth?: number; - borderColor?: string; - onBulkEditDiscard: () => void; - onBulkEditSave: () => void; - variant?: TableVariant; - primaryColumnId?: string; - isAddRowInProgress: boolean; - allowAddNewRow: boolean; - onAddNewRow: () => void; - onAddNewRowAction: ( - type: AddNewRowActions, - onActionComplete: () => void, - ) => void; - disabledAddNewRowSave: boolean; - handleColumnFreeze?: (columnName: string, sticky?: StickyType) => void; - canFreezeColumn?: boolean; - showConnectDataOverlay: boolean; - onConnectData: () => void; - isInfiniteScrollEnabled: boolean; -} - const defaultColumn = { minWidth: 30, width: 150, @@ -207,19 +117,24 @@ export function Table(props: TableProps) { toggleAllRowSelect, } = props; - const tableHeadercolumns = React.useMemo( + const pageCount = useMemo( () => - columns.filter((column: ReactTableColumnProps) => { - return column.alias !== "actions"; - }), - [columns], + props.serverSidePaginationEnabled && props.totalRecordsCount + ? Math.ceil(props.totalRecordsCount / props.pageSize) + : Math.ceil(props.data.length / props.pageSize), + [ + props.serverSidePaginationEnabled, + props.totalRecordsCount, + props.pageSize, + props.data.length, + ], + ); + + const currentPageIndex = useMemo( + () => (props.pageNo < pageCount ? props.pageNo : 0), + [props.pageNo, pageCount], ); - const pageCount = - props.serverSidePaginationEnabled && props.totalRecordsCount - ? Math.ceil(props.totalRecordsCount / props.pageSize) - : Math.ceil(props.data.length / props.pageSize); - const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0; const { getTableBodyProps, getTableProps, @@ -280,7 +195,6 @@ export function Table(props: TableProps) { const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT]; const tableWrapperRef = useRef(null); const scrollBarRef = useRef(null); - const tableHeaderWrapperRef = React.createRef(); const rowSelectionState = React.useMemo(() => { // return : 0; no row selected | 1; all row selected | 2: some rows selected if (!multiRowSelection) return null; @@ -297,6 +211,7 @@ export function Table(props: TableProps) { return result; }, [multiRowSelection, page, selectedRowIndices]); + const handleAllRowSelectClick = useCallback( (e: React.MouseEvent) => { // if all / some rows are selected we remove selection on click @@ -307,27 +222,28 @@ export function Table(props: TableProps) { }, [page, rowSelectionState, toggleAllRowSelect], ); - const isHeaderVisible = - props.isVisibleSearch || + const isHeaderVisible = useMemo( + () => + props.isVisibleSearch || + props.isVisibleFilters || + props.isVisibleDownload || + props.isVisiblePagination || + props.allowAddNewRow, + [ + props.isVisibleSearch, + props.isVisibleFilters, + props.isVisibleDownload, + props.isVisiblePagination, + props.allowAddNewRow, + ], + ); + + props.isVisibleSearch || props.isVisibleFilters || props.isVisibleDownload || props.isVisiblePagination || props.allowAddNewRow; - const scrollContainerStyles = useMemo(() => { - return { - height: isHeaderVisible - ? props.height - tableSizes.TABLE_HEADER_HEIGHT - TABLE_SCROLLBAR_HEIGHT - : props.height - TABLE_SCROLLBAR_HEIGHT - SCROLL_BAR_OFFSET, - width: props.width, - }; - }, [ - isHeaderVisible, - props.height, - tableSizes.TABLE_HEADER_HEIGHT, - props.width, - ]); - /** * What this really translates is to fixed height rows: * shouldUseVirtual: false -> fixed height row, irrespective of content small or big @@ -335,14 +251,22 @@ export function Table(props: TableProps) { * Right now all HTML content is dynamic height in nature hence * for server paginated tables it needs this extra handling. */ - const shouldUseVirtual = - props.isInfiniteScrollEnabled || - (props.serverSidePaginationEnabled && - !props.columns.some( - (column) => - !!column.columnProperties.allowCellWrapping || - column.metaProperties?.type === ColumnTypes.HTML, - )); + //TODO: REMOVE THIS + const shouldUseVirtual = useMemo( + () => + props.isInfiniteScrollEnabled || + (props.serverSidePaginationEnabled && + !props.columns.some( + (column) => + !!column.columnProperties.allowCellWrapping || + column.metaProperties?.type === ColumnTypes.HTML, + )), + [ + props.isInfiniteScrollEnabled, + props.serverSidePaginationEnabled, + props.columns, + ], + ); useEffect(() => { if (props.isAddRowInProgress) { @@ -355,7 +279,21 @@ export function Table(props: TableProps) { }, [props.isAddRowInProgress]); return ( - <> + {showConnectDataOverlay && ( - {isHeaderVisible && ( - - - - - - - - )} + {isHeaderVisible && }
- {!shouldUseVirtual && ( - - )} - - {shouldUseVirtual && ( - - )} + {shouldUseVirtual ? : }
- +
); } diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx b/app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx deleted file mode 100644 index 3a5f4b63a209..000000000000 --- a/app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import type { Ref } from "react"; -import React from "react"; -import type { - Row as ReactTableRowType, - TableBodyPropGetter, - TableBodyProps, -} from "react-table"; -import { type ReactElementType } from "react-window"; -import type SimpleBar from "simplebar-react"; -import type { ReactTableColumnProps, TableSizes } from "../Constants"; -import type { HeaderComponentProps } from "../Table"; -import InfiniteScrollBody from "./InifiniteScrollBody"; -import { EmptyRows, Row } from "./Row"; -import { FixedVirtualList } from "./VirtualList"; - -export type BodyContextType = { - accentColor: string; - borderRadius: string; - multiRowSelection: boolean; - prepareRow?(row: ReactTableRowType>): void; - selectTableRow?: (row: { - original: Record; - index: number; - }) => void; - selectedRowIndex: number; - selectedRowIndices: number[]; - columns: ReactTableColumnProps[]; - width: number; - rows: ReactTableRowType>[]; - primaryColumnId?: string; - isAddRowInProgress: boolean; - getTableBodyProps?( - propGetter?: TableBodyPropGetter> | undefined, - ): TableBodyProps; - totalColumnsWidth?: number; -} & Partial; - -export const BodyContext = React.createContext({ - accentColor: "", - borderRadius: "", - multiRowSelection: false, - selectedRowIndex: -1, - selectedRowIndices: [], - columns: [], - width: 0, - rows: [], - primaryColumnId: "", - isAddRowInProgress: false, - totalColumnsWidth: 0, -}); - -interface BodyPropsType { - getTableBodyProps( - propGetter?: TableBodyPropGetter> | undefined, - ): TableBodyProps; - pageSize: number; - rows: ReactTableRowType>[]; - height: number; - width?: number; - tableSizes: TableSizes; - innerElementType?: ReactElementType; - isInfiniteScrollEnabled?: boolean; - isLoading: boolean; - loadMoreFromEvaluations: () => void; -} - -const TableVirtualBodyComponent = React.forwardRef( - (props: BodyPropsType, ref: Ref) => { - return ( -
- -
- ); - }, -); - -const TableBodyComponent = (props: BodyPropsType) => { - return ( -
- {props.rows.map((row, index) => { - return ; - })} - {props.pageSize > props.rows.length && ( - - )} -
- ); -}; - -export const TableBody = React.forwardRef( - ( - props: BodyPropsType & BodyContextType & { useVirtual: boolean }, - ref: Ref, - ) => { - const { - accentColor, - borderRadius, - canFreezeColumn, - columns, - disableDrag, - editMode, - enableDrag, - handleAllRowSelectClick, - handleColumnFreeze, - handleReorderColumn, - headerGroups, - isAddRowInProgress, - isInfiniteScrollEnabled, - isResizingColumn, - isSortable, - multiRowSelection, - prepareRow, - primaryColumnId, - rows, - rowSelectionState, - selectedRowIndex, - selectedRowIndices, - selectTableRow, - sortTableColumn, - subPage, - useVirtual, - widgetId, - width, - ...restOfProps - } = props; - - return ( - - {isInfiniteScrollEnabled ? ( - - ) : useVirtual ? ( - - ) : ( - - )} - - ); - }, -); diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx similarity index 76% rename from app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx rename to app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx index 03dec597f564..c5c055e3c9a3 100644 --- a/app/client/src/widgets/TableWidgetV2/component/TableBody/Row.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx @@ -1,11 +1,11 @@ import type { CSSProperties, Key } from "react"; -import React, { useContext } from "react"; +import React from "react"; import type { Row as ReactTableRowType } from "react-table"; import type { ListChildComponentProps } from "react-window"; -import { BodyContext } from "."; -import { renderEmptyRows } from "../cellComponents/EmptyCell"; +import { RenderEmptyRows } from "../cellComponents/EmptyCell"; import { renderBodyCheckBoxCell } from "../cellComponents/SelectionCheckboxCell"; import { MULTISELECT_CHECKBOX_WIDTH, StickyType } from "../Constants"; +import { useAppsmithTable } from "../TableContext"; interface RowType { className?: string; @@ -26,7 +26,7 @@ export function Row(props: RowType) { selectedRowIndex, selectedRowIndices, selectTableRow, - } = useContext(BodyContext); + } = useAppsmithTable(); prepareRow?.(props.row); const rowProps = { @@ -105,53 +105,9 @@ export const EmptyRows = (props: { style?: CSSProperties; rowCount: number; }) => { - const { - accentColor, - borderRadius, - columns, - multiRowSelection, - prepareRow, - rows, - width, - } = useContext(BodyContext); - - return ( - <> - {renderEmptyRows( - props.rowCount, - columns, - width, - rows, - multiRowSelection, - accentColor, - borderRadius, - props.style, - prepareRow, - )} - - ); + return <>{RenderEmptyRows(props.rowCount, props.style)}; }; export const EmptyRow = (props: { style?: CSSProperties }) => { - const { - accentColor, - borderRadius, - columns, - multiRowSelection, - prepareRow, - rows, - width, - } = useContext(BodyContext); - - return renderEmptyRows( - 1, - columns, - width, - rows, - multiRowSelection, - accentColor, - borderRadius, - props.style, - prepareRow, - )?.[0]; + return RenderEmptyRows(1, props.style)?.[0]; }; diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx similarity index 97% rename from app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx rename to app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx index c4d79bc97d5c..0d9c7a9e5558 100644 --- a/app/client/src/widgets/TableWidgetV2/component/TableBody/VirtualList.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx @@ -1,6 +1,6 @@ import type { ListOnItemsRenderedProps, ReactElementType } from "react-window"; import { FixedSizeList, areEqual } from "react-window"; -import React from "react"; +import React, { type Ref } from "react"; import type { ListChildComponentProps } from "react-window"; import type { Row as ReactTableRowType } from "react-table"; import { WIDGET_PADDING } from "constants/WidgetConstants"; @@ -34,7 +34,7 @@ interface BaseVirtualListProps { rows: ReactTableRowType>[]; pageSize: number; innerElementType?: ReactElementType; - outerRef?: React.Ref; + outerRef: Ref; onItemsRendered?: (props: ListOnItemsRenderedProps) => void; infiniteLoaderListRef?: React.Ref; } diff --git a/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx b/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx new file mode 100644 index 000000000000..c4bc4c37eb4f --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx @@ -0,0 +1,309 @@ +import React, { useContext } from "react"; +import { render, screen } from "@testing-library/react"; +import TestRenderer from "react-test-renderer"; +import { + TableProvider, + TableContext, + useAppsmithTable, + type TableContextState, +} from "./TableContext"; +import { CompactModeTypes, TABLE_SIZES } from "./Constants"; + +// Mock data and props +const mockTableProviderProps = { + width: 800, + height: 400, + pageSize: 10, + isHeaderVisible: true, + compactMode: CompactModeTypes.DEFAULT, + currentPageIndex: 0, + pageCount: 5, + pageOptions: [0, 1, 2, 3, 4], + headerGroups: [], + totalColumnsWidth: 800, + isResizingColumn: { current: false }, + prepareRow: jest.fn(), + rowSelectionState: null, + subPage: [], + handleAllRowSelectClick: jest.fn(), + getTableBodyProps: jest.fn(), + children: null, + // Additional required props + widgetId: "table-widget-1", + widgetName: "Table1", + searchKey: "", + isLoading: false, + columns: [], + data: [], + editMode: false, + editableCell: { + column: "column1", + row: 0, + index: 0, + value: "", + initialValue: "", + inputValue: "", + __originalIndex__: 0, + }, + sortTableColumn: jest.fn(), + handleResizeColumn: jest.fn(), + handleReorderColumn: jest.fn(), + selectTableRow: jest.fn(), + pageNo: 0, + updatePageNo: jest.fn(), + nextPageClick: jest.fn(), + prevPageClick: jest.fn(), + serverSidePaginationEnabled: false, + selectedRowIndex: -1, + selectedRowIndices: [], + disableDrag: jest.fn(), + enableDrag: jest.fn(), + toggleAllRowSelect: jest.fn(), + triggerRowSelection: false, + searchTableData: jest.fn(), + filters: [], + applyFilter: jest.fn(), + delimiter: ",", + accentColor: "#000000", + isSortable: true, + multiRowSelection: false, + columnWidthMap: {}, + // Additional required props from latest error + borderRadius: "0px", + boxShadow: "none", + onBulkEditDiscard: jest.fn(), + onBulkEditSave: jest.fn(), + primaryColumns: {}, + derivedColumns: {}, + sortOrder: { column: "", order: null }, + transientTableData: {}, + isEditableCellsValid: {}, + selectColumnFilterText: {}, + isAddRowInProgress: false, + newRow: {}, + firstEditableColumnIdByOrder: "", + enableServerSideFiltering: false, + onTableFilterUpdate: "", + customIsLoading: false, + customIsLoadingValue: false, + infiniteScrollEnabled: false, + // Final set of required props + allowAddNewRow: false, + onAddNewRow: jest.fn(), + onAddNewRowAction: jest.fn(), + disabledAddNewRowSave: false, + addNewRowValidation: {}, + onAddNewRowSave: jest.fn(), + onAddNewRowDiscard: jest.fn(), + // Last set of required props + showConnectDataOverlay: false, + onConnectData: jest.fn(), + isInfiniteScrollEnabled: false, +}; + +// Test components +interface TestChildProps { + tableContext: TableContextState | undefined; +} + +const TestChild = (props: TestChildProps) => { + return ( +
{Object.keys(props.tableContext as TableContextState).join(",")}
+ ); +}; + +const TestParent = () => { + const tableContext = useContext(TableContext); + + return ; +}; + +describe("TableContext", () => { + describe("context values and usage", () => { + it("provides correct context values to children", async () => { + const testRenderer = TestRenderer.create( + + + , + ); + const testInstance = testRenderer.root; + + const expectedKeys = [ + "width", + "height", + "pageSize", + "isHeaderVisible", + "compactMode", + "currentPageIndex", + "pageCount", + "pageOptions", + "headerGroups", + "totalColumnsWidth", + "isResizingColumn", + "prepareRow", + "rowSelectionState", + "subPage", + "handleAllRowSelectClick", + "getTableBodyProps", + "scrollContainerStyles", + "tableSizes", + "widgetId", + "widgetName", + "searchKey", + "isLoading", + "columns", + "data", + "editMode", + "editableCell", + "sortTableColumn", + "handleResizeColumn", + "handleReorderColumn", + "selectTableRow", + "pageNo", + "updatePageNo", + "nextPageClick", + "prevPageClick", + "serverSidePaginationEnabled", + "selectedRowIndex", + "selectedRowIndices", + "disableDrag", + "enableDrag", + "toggleAllRowSelect", + "triggerRowSelection", + "searchTableData", + "filters", + "applyFilter", + "delimiter", + "accentColor", + "isSortable", + "multiRowSelection", + "columnWidthMap", + "borderRadius", + "boxShadow", + "onBulkEditDiscard", + "onBulkEditSave", + "primaryColumns", + "derivedColumns", + "sortOrder", + "transientTableData", + "isEditableCellsValid", + "selectColumnFilterText", + "isAddRowInProgress", + "newRow", + "firstEditableColumnIdByOrder", + "enableServerSideFiltering", + "onTableFilterUpdate", + "customIsLoading", + "customIsLoadingValue", + "infiniteScrollEnabled", + "allowAddNewRow", + "onAddNewRow", + "onAddNewRowAction", + "disabledAddNewRowSave", + "addNewRowValidation", + "onAddNewRowSave", + "onAddNewRowDiscard", + "showConnectDataOverlay", + "onConnectData", + "isInfiniteScrollEnabled", + ].sort(); + + const result = Object.keys( + (await testInstance.findByType(TestChild)).props.tableContext, + ).sort(); + + expect(result).toEqual(expectedKeys); + }); + + it("throws error when useAppsmithTable is used outside TableProvider", () => { + const TestComponent = () => { + useAppsmithTable(); + + return null; + }; + + const consoleError = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + expect(() => render()).toThrow( + "useTable must be used within a TableProvider", + ); + consoleError.mockRestore(); + }); + }); + + describe("scrollContainerStyles", () => { + it("calculates correct scrollContainerStyles when header is visible", async () => { + const testRenderer = TestRenderer.create( + + + , + ); + const testInstance = testRenderer.root; + const context = (await testInstance.findByType(TestChild)).props + .tableContext; + + expect(context.scrollContainerStyles).toEqual({ + height: 352, // 400 - 40 - 8 (height - TABLE_HEADER_HEIGHT - TABLE_SCROLLBAR_HEIGHT) + width: 800, + }); + }); + + it("calculates correct scrollContainerStyles when header is not visible", async () => { + const testRenderer = TestRenderer.create( + + + , + ); + const testInstance = testRenderer.root; + const context = (await testInstance.findByType(TestChild)).props + .tableContext; + + expect(context.scrollContainerStyles).toEqual({ + height: 390, // 400 - 8 - 2 (height - TABLE_SCROLLBAR_HEIGHT - SCROLL_BAR_OFFSET) + width: 800, + }); + }); + }); + + it("provides correct tableSizes based on compactMode", async () => { + const testRenderer = TestRenderer.create( + + + , + ); + const testInstance = testRenderer.root; + const context = (await testInstance.findByType(TestChild)).props + .tableContext; + + expect(context.tableSizes).toEqual( + TABLE_SIZES[mockTableProviderProps.compactMode], + ); + }); + + it("memoizes context value and scrollContainerStyles", () => { + const { rerender } = render( + + + , + ); + + const firstRender = screen.getByText(/.+/); + const firstText = firstRender.textContent; + + // Rerender with same props + rerender( + + + , + ); + + const secondRender = screen.getByText(/.+/); + const secondText = secondRender.textContent; + + expect(firstText).toBe(secondText); + }); +}); diff --git a/app/client/src/widgets/TableWidgetV2/component/TableContext.tsx b/app/client/src/widgets/TableWidgetV2/component/TableContext.tsx new file mode 100644 index 000000000000..e36bc9802586 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/TableContext.tsx @@ -0,0 +1,84 @@ +import React, { + createContext, + useContext, + useMemo, + type ReactNode, +} from "react"; +import type { TableProps } from "./types"; +import { + SCROLL_BAR_OFFSET, + TABLE_SCROLLBAR_HEIGHT, + TABLE_SIZES, + type TableSizes, +} from "./Constants"; +import type { + HeaderGroup, + Row, + TableBodyPropGetter, + TableBodyProps, +} from "react-table"; + +export interface TableProviderProps extends TableProps { + children: ReactNode; + currentPageIndex: number; + pageCount: number; + pageOptions: number[]; + isHeaderVisible: boolean; + handleAllRowSelectClick: ( + e: React.MouseEvent, + ) => void; + headerGroups: HeaderGroup[]; + totalColumnsWidth: number; + isResizingColumn: React.MutableRefObject; + prepareRow: (row: Row>) => void; + rowSelectionState: 0 | 1 | 2 | null; + subPage: Row>[]; + getTableBodyProps( + propGetter?: TableBodyPropGetter> | undefined, + ): TableBodyProps; +} + +export interface TableContextState + extends Omit { + scrollContainerStyles: { + height: number; + width: number; + }; + tableSizes: TableSizes; +} + +export const TableContext = createContext( + undefined, +); + +export const TableProvider = ({ children, ...state }: TableProviderProps) => { + const value = useMemo(() => state, [state]); + const tableSizes = TABLE_SIZES[state.compactMode]; + + const scrollContainerStyles = useMemo(() => { + return { + height: state.isHeaderVisible + ? state.height - tableSizes.TABLE_HEADER_HEIGHT - TABLE_SCROLLBAR_HEIGHT + : state.height - TABLE_SCROLLBAR_HEIGHT - SCROLL_BAR_OFFSET, + width: state.width, + }; + }, [state.isHeaderVisible, state.height, state.compactMode, state.width]); + + return ( + + {children} + + ); +}; + +export const useAppsmithTable = () => { + const context = useContext(TableContext); + + if (context === undefined) { + throw new Error("useTable must be used within a TableProvider"); + } + + return context; +}; diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx deleted file mode 100644 index 25a55a0f045e..000000000000 --- a/app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from "react"; -import type { - TableBodyPropGetter, - TableBodyProps, - Row as ReactTableRowType, -} from "react-table"; -import SimpleBar from "simplebar-react"; -import "simplebar-react/dist/simplebar.min.css"; -import type { ReactTableColumnProps, TableSizes } from "./Constants"; -import type { TableColumnHeaderProps } from "./header/TableColumnHeader"; -import VirtualTableInnerElement from "./header/VirtualTableInnerElement"; -import { TableBody } from "./TableBody"; - -type VirtualTableProps = TableColumnHeaderProps & { - getTableBodyProps( - propGetter?: TableBodyPropGetter> | undefined, - ): TableBodyProps; - pageSize: number; - height: number; - width?: number; - tableSizes: TableSizes; - accentColor: string; - borderRadius: string; - multiRowSelection?: boolean; - prepareRow?(row: ReactTableRowType>): void; - selectTableRow?: (row: { - original: Record; - index: number; - }) => void; - selectedRowIndex: number; - selectedRowIndices: number[]; - columns: ReactTableColumnProps[]; - primaryColumnId?: string; - isAddRowInProgress: boolean; - totalColumnsWidth?: number; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - scrollContainerStyles: any; - useVirtual: boolean; - isInfiniteScrollEnabled: boolean; - isLoading: boolean; - loadMoreFromEvaluations: () => void; -}; - -const VirtualTable = (props: VirtualTableProps, ref: React.Ref) => { - return ( - - {({ scrollableNodeRef }) => ( - - )} - - ); -}; - -export default React.forwardRef(VirtualTable); diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/index.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx similarity index 63% rename from app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/index.tsx rename to app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx index f2cb83e1cba0..485060b9f985 100644 --- a/app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx @@ -1,33 +1,31 @@ import React, { type Ref } from "react"; -import type { Row as ReactTableRowType } from "react-table"; import { type ReactElementType } from "react-window"; import InfiniteLoader from "react-window-infinite-loader"; import type SimpleBar from "simplebar-react"; -import type { TableSizes } from "../../Constants"; +import { FixedInfiniteVirtualList } from "../../TableBodyCoreComponents/VirtualList"; +import { useAppsmithTable } from "../../TableContext"; import { useInfiniteVirtualization } from "./useInfiniteVirtualization"; -import { FixedInfiniteVirtualList } from "../VirtualList"; interface InfiniteScrollBodyProps { - rows: ReactTableRowType>[]; - height: number; - tableSizes: TableSizes; - innerElementType?: ReactElementType; - isLoading: boolean; - totalRecordsCount?: number; - itemCount: number; - loadMoreFromEvaluations: () => void; - pageSize: number; + innerElementType: ReactElementType; } const InfiniteScrollBody = React.forwardRef( (props: InfiniteScrollBodyProps, ref: Ref) => { - const { isLoading, loadMoreFromEvaluations, pageSize, rows } = props; + const { + height, + isLoading, + nextPageClick, + pageSize, + subPage: rows, + tableSizes, + } = useAppsmithTable(); const { isItemLoaded, itemCount, loadMoreItems } = useInfiniteVirtualization({ rows, totalRecordsCount: rows.length, isLoading, - loadMore: loadMoreFromEvaluations, + loadMore: nextPageClick, pageSize, }); @@ -40,14 +38,14 @@ const InfiniteScrollBody = React.forwardRef( > {({ onItemsRendered, ref: infiniteLoaderRef }) => ( )} diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/useInfiniteVirtualization.test.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteVirtualization.test.tsx similarity index 100% rename from app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/useInfiniteVirtualization.test.tsx rename to app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteVirtualization.test.tsx diff --git a/app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/useInfiniteVirtualization.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteVirtualization.tsx similarity index 100% rename from app/client/src/widgets/TableWidgetV2/component/TableBody/InifiniteScrollBody/useInfiniteVirtualization.tsx rename to app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteVirtualization.tsx diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableBodyComponent.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableBodyComponent.tsx new file mode 100644 index 000000000000..15802a013afb --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableBodyComponent.tsx @@ -0,0 +1,30 @@ +import React, { type Ref } from "react"; +import type { ReactElementType } from "react-window"; +import { FixedVirtualList } from "../TableBodyCoreComponents/VirtualList"; +import { useAppsmithTable } from "../TableContext"; +import type SimpleBar from "simplebar-react"; + +interface VirtualTableBodyComponentProps { + innerElementType: ReactElementType; +} + +const VirtualTableBodyComponent = React.forwardRef( + (props: VirtualTableBodyComponentProps, ref: Ref) => { + const { height, pageSize, subPage: rows, tableSizes } = useAppsmithTable(); + + return ( +
+ +
+ ); + }, +); + +export default VirtualTableBodyComponent; diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableInnerElement.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableInnerElement.tsx new file mode 100644 index 000000000000..80f94f78c6f4 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableInnerElement.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import styled from "styled-components"; +import { MULTISELECT_CHECKBOX_WIDTH } from "../Constants"; +import TableColumnHeader from "../header/TableColumnHeader"; +import { useAppsmithTable } from "../TableContext"; + +const StyledTableBodyWrapper = styled.div<{ + multiRowSelection?: boolean; + totalColumnsWidth: number; +}>` + width: ${(props) => + props.multiRowSelection + ? MULTISELECT_CHECKBOX_WIDTH + props.totalColumnsWidth + : props.totalColumnsWidth}px !important; +`; + +const VirtualTableInnerElement = ({ + children, + outerRef, + style, + ...rest // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any +}: any) => { + const { getTableBodyProps, multiRowSelection, totalColumnsWidth } = + useAppsmithTable(); + + return ( + <> + + +
+ {children} +
+
+ + ); +}; + +export default VirtualTableInnerElement; diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/index.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/index.tsx new file mode 100644 index 000000000000..b88661a7ea2f --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/index.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import SimpleBar from "simplebar-react"; +import "simplebar-react/dist/simplebar.min.css"; +import { useAppsmithTable } from "../TableContext"; +import InfiniteScrollBody from "./InifiniteScrollBody"; +import VirtualTableBodyComponent from "./VirtualTableBodyComponent"; +import VirtualTableInnerElement from "./VirtualTableInnerElement"; + +interface VirtualTableProps {} +const VirtualTable = (_: VirtualTableProps, ref: React.Ref) => { + const { isInfiniteScrollEnabled, scrollContainerStyles } = useAppsmithTable(); + + return ( + + {({ scrollableNodeRef }) => + isInfiniteScrollEnabled ? ( + + ) : ( + + ) + } + + ); +}; + +export default React.forwardRef(VirtualTable); diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx index 2c161a5dcddf..cfa59a9f766f 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx @@ -1,9 +1,10 @@ import { pickBy, sum } from "lodash"; import type { CSSProperties } from "react"; import React from "react"; -import type { Cell, Row } from "react-table"; +import type { Cell } from "react-table"; import type { ReactTableColumnProps } from "../Constants"; import { MULTISELECT_CHECKBOX_WIDTH, StickyType } from "../Constants"; +import { useAppsmithTable } from "../TableContext"; import { EmptyCell, EmptyRow } from "../TableStyledWrappers"; import { renderBodyCheckBoxCell } from "./SelectionCheckboxCell"; @@ -19,17 +20,17 @@ const addStickyModifierClass = ( : ""; }; -export const renderEmptyRows = ( - rowCount: number, - columns: ReactTableColumnProps[], - tableWidth: number, - page: Row>[], - multiRowSelection = false, - accentColor: string, - borderRadius: string, - style?: CSSProperties, - prepareRow?: (row: Row>) => void, -) => { +export const RenderEmptyRows = (rowCount: number, style?: CSSProperties) => { + const { + accentColor, + borderRadius, + columns, + multiRowSelection = false, + prepareRow, + subPage: page, + width: tableWidth, + } = useAppsmithTable(); + const rows: string[] = new Array(rowCount).fill(""); if (page.length) { diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx index 2c2641d209e6..e302dce660b6 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx @@ -25,6 +25,7 @@ import { Popover2 } from "@blueprintjs/popover2"; import { MenuDivider } from "@design-system/widgets-old"; import { importRemixIcon, importSvg } from "@design-system/widgets-old"; import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants"; +import { useAppsmithTable } from "../TableContext"; const Check = importRemixIcon( async () => import("remixicon-react/CheckFillIcon"), @@ -130,25 +131,15 @@ function Title(props: TitleProps) { const ICON_SIZE = 16; interface HeaderProps { - canFreezeColumn?: boolean; columnName: string; columnIndex: number; isHidden: boolean; isAscOrder?: boolean; - handleColumnFreeze?: (columnName: string, sticky?: StickyType) => void; - handleReorderColumn: (columnOrder: string[]) => void; columnOrder?: string[]; - sortTableColumn: (columnIndex: number, asc: boolean) => void; - isResizingColumn: boolean; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any column: any; - editMode?: boolean; - isSortable?: boolean; - width?: number; - widgetId: string; stickyRightModifier: string; - multiRowSelection?: boolean; onDrag: (e: React.DragEvent) => void; onDragEnter: ( e: React.DragEvent, @@ -165,20 +156,30 @@ interface HeaderProps { } const HeaderCellComponent = (props: HeaderProps) => { - const { column, editMode, isSortable } = props; + const { + canFreezeColumn, + editMode, + handleColumnFreeze, + isResizingColumn, + isSortable, + multiRowSelection, + sortTableColumn, + widgetId, + width, + } = useAppsmithTable(); const [isMenuOpen, setIsMenuOpen] = useState(false); - const headerProps = { ...column.getHeaderProps() }; + const headerProps = { ...props.column.getHeaderProps() }; headerProps["style"] = { ...headerProps.style, left: - column.sticky === StickyType.LEFT && props.multiRowSelection - ? MULTISELECT_CHECKBOX_WIDTH + column.totalLeft + props.column.sticky === StickyType.LEFT && multiRowSelection + ? MULTISELECT_CHECKBOX_WIDTH + props.column.totalLeft : headerProps.style.left, }; const handleSortColumn = () => { - if (props.isResizingColumn) return; + if (isResizingColumn.current) return; let columnIndex = props.columnIndex; @@ -189,19 +190,19 @@ const HeaderCellComponent = (props: HeaderProps) => { const sortOrder = props.isAscOrder === undefined ? false : !props.isAscOrder; - props.sortTableColumn(columnIndex, sortOrder); + sortTableColumn(columnIndex, sortOrder); }; const disableSort = editMode === false && isSortable === false; const isColumnEditable = - column.columnProperties.isCellEditable && - column.columnProperties.isEditable && - isColumnTypeEditable(column.columnProperties.columnType); + props.column.columnProperties.isCellEditable && + props.column.columnProperties.isEditable && + isColumnTypeEditable(props.column.columnProperties.columnType); const toggleColumnFreeze = (value: StickyType) => { - props.handleColumnFreeze && - props.handleColumnFreeze( + handleColumnFreeze && + handleColumnFreeze( props.column.id, props.column.sticky !== value ? value : StickyType.NONE, ); @@ -270,17 +271,19 @@ const HeaderCellComponent = (props: HeaderProps) => { onDrop={onDrop} > {isColumnEditable && } - + <Title width={width}> {props.columnName.replace(/\s/g, "\u00a0")}
{ disabled={disableSort} labelElement={props.isAscOrder === true ? : undefined} onClick={() => { - props.sortTableColumn(props.columnIndex, true); + sortTableColumn(props.columnIndex, true); }} text={POPOVER_ITEMS_TEXT_MAP.SORT_ASC} /> @@ -300,7 +303,7 @@ const HeaderCellComponent = (props: HeaderProps) => { props.isAscOrder === false ? : undefined } onClick={() => { - props.sortTableColumn(props.columnIndex, false); + sortTableColumn(props.columnIndex, false); }} text={POPOVER_ITEMS_TEXT_MAP.SORT_DSC} /> @@ -311,9 +314,11 @@ const HeaderCellComponent = (props: HeaderProps) => { }} /> : undefined + props.column.sticky === StickyType.LEFT ? ( + + ) : undefined } onClick={() => { toggleColumnFreeze(StickyType.LEFT); @@ -321,9 +326,11 @@ const HeaderCellComponent = (props: HeaderProps) => { text={POPOVER_ITEMS_TEXT_MAP.FREEZE_LEFT} /> : undefined + props.column.sticky === StickyType.RIGHT ? ( + + ) : undefined } onClick={() => { toggleColumnFreeze(StickyType.RIGHT); @@ -337,7 +344,7 @@ const HeaderCellComponent = (props: HeaderProps) => { minimal onInteraction={setIsMenuOpen} placement="bottom-end" - portalClassName={`${HEADER_MENU_PORTAL_CLASS}-${props.widgetId}`} + portalClassName={`${HEADER_MENU_PORTAL_CLASS}-${widgetId}`} portalContainer={ document.getElementById(CANVAS_ART_BOARD) || undefined } @@ -355,8 +362,8 @@ const HeaderCellComponent = (props: HeaderProps) => {
) : null}
) => { e.preventDefault(); e.stopPropagation(); diff --git a/app/client/src/widgets/TableWidgetV2/component/header/BannerNActions.tsx b/app/client/src/widgets/TableWidgetV2/component/header/BannerNActions.tsx new file mode 100644 index 000000000000..16c730a500a0 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/header/BannerNActions.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import type { ActionsPropsType } from "./actions"; +import Actions from "./actions"; +import type { BannerPropType } from "./banner"; +import { Banner } from "./banner"; + +function BannerNActions(props: ActionsPropsType & BannerPropType) { + const { + accentColor, + borderRadius, + boxShadow, + disabledAddNewRowSave, + isAddRowInProgress, + onAddNewRowAction, + ...ActionProps + } = props; + + return isAddRowInProgress ? ( + + ) : ( + + ); +} + +export default BannerNActions; diff --git a/app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx b/app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx index dd06e175e314..4d0dc49cf9c4 100644 --- a/app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx @@ -1,42 +1,11 @@ import React, { memo } from "react"; +import styled from "styled-components"; import { getDragHandlers } from "widgets/TableWidgetV2/widget/utilities"; +import { RenderEmptyRows } from "../cellComponents/EmptyCell"; import { HeaderCell } from "../cellComponents/HeaderCell"; -import type { ReactTableColumnProps } from "../Constants"; -import { StickyType } from "../Constants"; -import type { Row as ReactTableRowType } from "react-table"; import { renderHeaderCheckBoxCell } from "../cellComponents/SelectionCheckboxCell"; -import { renderEmptyRows } from "../cellComponents/EmptyCell"; -import styled from "styled-components"; - -export interface TableColumnHeaderProps { - enableDrag: () => void; - disableDrag: () => void; - multiRowSelection?: boolean; - handleAllRowSelectClick: ( - e: React.MouseEvent, - ) => void; - handleReorderColumn: (columnOrder: string[]) => void; - accentColor: string; - borderRadius: string; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - headerGroups: any; - canFreezeColumn?: boolean; - editMode: boolean; - handleColumnFreeze?: (columnName: string, sticky?: StickyType) => void; - isResizingColumn: React.MutableRefObject; - isSortable?: boolean; - sortTableColumn: (columnIndex: number, asc: boolean) => void; - columns: ReactTableColumnProps[]; - width: number; - subPage: ReactTableRowType>[]; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - prepareRow: any; - headerWidth?: number; - rowSelectionState: 0 | 1 | 2 | null; - widgetId: string; -} +import { MULTISELECT_CHECKBOX_WIDTH, StickyType } from "../Constants"; +import { useAppsmithTable } from "../TableContext"; const StyledHeaderGroup = styled.div<{ headerWidth: number; @@ -44,9 +13,30 @@ const StyledHeaderGroup = styled.div<{ display: flex; width: ${(props) => props.headerWidth}px !important; `; -const TableColumnHeader = (props: TableColumnHeaderProps) => { +const TableColumnHeader = () => { + const { + accentColor, + borderRadius, + columns, + disableDrag, + enableDrag, + handleAllRowSelectClick, + handleReorderColumn, + headerGroups, + multiRowSelection, + rowSelectionState, + totalColumnsWidth, + } = useAppsmithTable(); + const headerWidth = React.useMemo( + () => + multiRowSelection && totalColumnsWidth + ? MULTISELECT_CHECKBOX_WIDTH + totalColumnsWidth + : totalColumnsWidth, + [multiRowSelection, totalColumnsWidth], + ); + const currentDraggedColumn = React.useRef(""); - const columnOrder = props.columns.map((col) => col.alias); + const columnOrder = columns.map((col) => col.alias); const { onDrag, onDragEnd, @@ -56,21 +46,17 @@ const TableColumnHeader = (props: TableColumnHeaderProps) => { onDragStart, onDrop, } = getDragHandlers( - props.columns, + columns, currentDraggedColumn, - props.handleReorderColumn, + handleReorderColumn, columnOrder, ); return ( -
+
{/* TODO: Fix this the next time the file is edited */} {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - {props.headerGroups.map((headerGroup: any, index: number) => { + {headerGroups.map((headerGroup: any, index: number) => { const headerRowProps = { ...headerGroup.getHeaderGroupProps(), }; @@ -79,15 +65,15 @@ const TableColumnHeader = (props: TableColumnHeaderProps) => { - {props.multiRowSelection && + {multiRowSelection && renderHeaderCheckBoxCell( - props.handleAllRowSelectClick, - props.rowSelectionState, - props.accentColor, - props.borderRadius, + handleAllRowSelectClick, + rowSelectionState, + accentColor, + borderRadius, )} {/* TODO: Fix this the next time the file is edited */} @@ -95,28 +81,21 @@ const TableColumnHeader = (props: TableColumnHeaderProps) => { {headerGroup.headers.map((column: any, columnIndex: number) => { const stickyRightModifier = !column.isHidden ? columnIndex !== 0 && - props.columns[columnIndex - 1].sticky === StickyType.RIGHT && - props.columns[columnIndex - 1].isHidden + columns[columnIndex - 1].sticky === StickyType.RIGHT && + columns[columnIndex - 1].isHidden ? "sticky-right-modifier" : "" : ""; return ( { onDragOver={onDragOver} onDragStart={onDragStart} onDrop={onDrop} - sortTableColumn={props.sortTableColumn} stickyRightModifier={stickyRightModifier} - widgetId={props.widgetId} - width={column.width} /> ); })} ); })} - {props.headerGroups.length === 0 && - renderEmptyRows( - 1, - props.columns, - props.width, - props.subPage, - props.multiRowSelection, - props.accentColor, - props.borderRadius, - {}, - props.prepareRow, - )} + {headerGroups.length === 0 && RenderEmptyRows(1, {})}
); }; diff --git a/app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx b/app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx deleted file mode 100644 index 31d2f81576bf..000000000000 --- a/app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from "react"; -import { useContext } from "react"; -import styled from "styled-components"; -import { MULTISELECT_CHECKBOX_WIDTH } from "../Constants"; -import { BodyContext } from "../TableBody"; -import TableColumnHeader from "./TableColumnHeader"; - -const StyledTableBodyWrapper = styled.div<{ - multiRowSelection?: boolean; - totalColumnsWidth: number; -}>` - width: ${(props) => - props.multiRowSelection - ? MULTISELECT_CHECKBOX_WIDTH + props.totalColumnsWidth - : props.totalColumnsWidth}px !important; -`; - -const VirtualTableInnerElement = ({ - children, - outerRef, - style, - ...rest // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any -}: any) => { - const { - accentColor, - borderRadius, - canFreezeColumn, - columns, - disableDrag, - editMode, - enableDrag, - getTableBodyProps, - handleAllRowSelectClick, - handleColumnFreeze, - handleReorderColumn, - headerGroups, - isResizingColumn, - isSortable, - multiRowSelection, - prepareRow, - rows, - rowSelectionState, - sortTableColumn, - totalColumnsWidth, - widgetId, - width, - } = // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - useContext(BodyContext) as any; - - return ( - <> - - -
- {children} -
-
- - ); -}; - -export default VirtualTableInnerElement; diff --git a/app/client/src/widgets/TableWidgetV2/component/header/index.tsx b/app/client/src/widgets/TableWidgetV2/component/header/index.tsx index 5b8f14d0a884..858bf658fbc2 100644 --- a/app/client/src/widgets/TableWidgetV2/component/header/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/header/index.tsx @@ -1,37 +1,117 @@ +import { Colors } from "constants/Colors"; import React from "react"; -import type { ActionsPropsType } from "./actions"; -import Actions from "./actions"; -import type { BannerPropType } from "./banner"; -import { Banner } from "./banner"; +import { type ReactTableColumnProps } from "../Constants"; +import { useAppsmithTable } from "../TableContext"; +import { + TableHeaderInnerWrapper, + TableHeaderWrapper, +} from "../TableStyledWrappers"; +import BannerNActions from "./BannerNActions"; +import SimpleBar from "simplebar-react"; +import "simplebar-react/dist/simplebar.min.css"; -function TableHeader(props: ActionsPropsType & BannerPropType) { +export default function TableHeader() { const { accentColor, + allowAddNewRow, + applyFilter, borderRadius, boxShadow, + columns, + currentPageIndex, + data, + delimiter, disabledAddNewRowSave, + editableCell, + filters, isAddRowInProgress, + isVisibleDownload, + isVisibleFilters, + isVisiblePagination, + isVisibleSearch, + nextPageClick, + onAddNewRow, onAddNewRowAction, - ...ActionProps - } = props; + pageCount, + pageNo, + pageOptions, + prevPageClick, + searchKey, + searchTableData, + serverSidePaginationEnabled, + tableSizes, + totalRecordsCount, + updatePageNo, + variant, + widgetId, + widgetName, + width, + } = useAppsmithTable(); - return isAddRowInProgress ? ( - - ) : ( - + const tableHeaderColumns = React.useMemo( + () => + columns.filter((column: ReactTableColumnProps) => { + return column.alias !== "actions"; + }), + [columns], ); -} -export default TableHeader; + return ( + + + + + + + + ); +} diff --git a/app/client/src/widgets/TableWidgetV2/component/index.tsx b/app/client/src/widgets/TableWidgetV2/component/index.tsx index fb0c93798653..72575792bf76 100644 --- a/app/client/src/widgets/TableWidgetV2/component/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/index.tsx @@ -1,19 +1,20 @@ import React from "react"; -import Table from "./Table"; -import type { - AddNewRowActions, - CompactMode, - ReactTableColumnProps, - ReactTableFilter, - StickyType, -} from "./Constants"; import type { Row } from "react-table"; +import { + CompactModeTypes, + type AddNewRowActions, + type CompactMode, + type ReactTableColumnProps, + type ReactTableFilter, + type StickyType, +} from "./Constants"; +import Table from "./Table"; import type { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import equal from "fast-deep-equal/es6"; +import { useCallback } from "react"; import type { EditableCell, TableVariant } from "../constants"; import { ColumnTypes } from "../constants"; -import { useCallback } from "react"; export interface ColumnMenuOptionProps { content: string | JSX.Element; @@ -234,7 +235,7 @@ function ReactTableComponent(props: ReactTableComponentProps) { canFreezeColumn={canFreezeColumn} columnWidthMap={columnWidthMap} columns={columns} - compactMode={compactMode} + compactMode={compactMode || CompactModeTypes.DEFAULT} data={tableData} delimiter={delimiter} disableDrag={memoziedDisableDrag} diff --git a/app/client/src/widgets/TableWidgetV2/component/types.ts b/app/client/src/widgets/TableWidgetV2/component/types.ts new file mode 100644 index 000000000000..e127093c6f7b --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/types.ts @@ -0,0 +1,88 @@ +import type { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import type { ReactNode } from "react"; +import type { Row as ReactTableRowType } from "react-table"; +import type { EditableCell, TableVariant } from "../constants"; +import type { + AddNewRowActions, + CompactMode, + ReactTableColumnProps, + ReactTableFilter, + StickyType, +} from "./Constants"; + +export interface TableProps { + width: number; + height: number; + pageSize: number; + widgetId: string; + widgetName: string; + searchKey: string; + isLoading: boolean; + columnWidthMap?: { [key: string]: number }; + columns: ReactTableColumnProps[]; + data: Array>; + totalRecordsCount?: number; + editMode: boolean; + editableCell: EditableCell; + sortTableColumn: (columnIndex: number, asc: boolean) => void; + handleResizeColumn: (columnWidthMap: { [key: string]: number }) => void; + handleReorderColumn: (columnOrder: string[]) => void; + selectTableRow: (row: { + original: Record; + index: number; + }) => void; + pageNo: number; + updatePageNo: (pageNo: number, event?: EventType) => void; + multiRowSelection?: boolean; + isSortable?: boolean; + nextPageClick: () => void; + prevPageClick: () => void; + serverSidePaginationEnabled: boolean; + selectedRowIndex: number; + selectedRowIndices: number[]; + disableDrag: () => void; + enableDrag: () => void; + toggleAllRowSelect: ( + isSelect: boolean, + pageData: ReactTableRowType>[], + ) => void; + triggerRowSelection: boolean; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + searchTableData: (searchKey: any) => void; + filters?: ReactTableFilter[]; + applyFilter: (filters: ReactTableFilter[]) => void; + compactMode: CompactMode; + isVisibleDownload?: boolean; + isVisibleFilters?: boolean; + isVisiblePagination?: boolean; + isVisibleSearch?: boolean; + delimiter: string; + accentColor: string; + borderRadius: string; + boxShadow: string; + borderWidth?: number; + borderColor?: string; + onBulkEditDiscard: () => void; + onBulkEditSave: () => void; + variant?: TableVariant; + primaryColumnId?: string; + isAddRowInProgress: boolean; + allowAddNewRow: boolean; + onAddNewRow: () => void; + onAddNewRowAction: ( + type: AddNewRowActions, + onActionComplete: () => void, + ) => void; + disabledAddNewRowSave: boolean; + handleColumnFreeze?: (columnName: string, sticky?: StickyType) => void; + canFreezeColumn?: boolean; + showConnectDataOverlay: boolean; + onConnectData: () => void; + isInfiniteScrollEnabled: boolean; +} + +export interface TableProviderProps extends TableProps { + children: ReactNode; + currentPageIndex: number; +}