UNPKG

@deepbag/react-grid-x

Version:

ReactGridX is a customizable and flexible React table component that supports pagination, dynamic rendering of table data, and customizable column rendering. It provides an easy-to-use interface for displaying tabular data with configurable columns, pagin

1 lines 85.1 kB
{"version":3,"sources":["../src/components/ReactGridX/index.tsx","../src/components/SVGIcons/index.tsx","../src/components/Paginations/RGXArrowPagination/index.tsx","../src/components/Paginations/RGXTablePagination/index.tsx","../src/components/Tooltip/index.tsx","../src/components/Loader/index.tsx","../src/components/Popover/index.tsx","../src/utils/merge-classes.ts"],"sourcesContent":["import React, { JSX, useEffect, useMemo, useState } from \"react\";\r\nimport RGXArrowPagination from \"../Paginations/RGXArrowPagination\";\r\nimport RGXTablePagination from \"../Paginations/RGXTablePagination\";\r\nimport RGXTooltip from \"../Tooltip\";\r\nimport RGXLoader from \"../Loader\";\r\nimport { ReactGridXProps } from \"../../types/react-grid-x-props\";\r\nimport RGXPopover from \"../Popover\";\r\nimport SvgIcon from \"../SVGIcons\";\r\nimport { mergeClasses } from \"../../utils/merge-classes\";\r\n\r\nconst ReactGridX: React.FC<ReactGridXProps> = ({\r\n columns,\r\n data,\r\n theme = \"rgx-theme\",\r\n rowsPerPageOptions = [5, 10, 15],\r\n paginationType = \"rgx-table-pagination\",\r\n paginationStyle = {},\r\n tableStyle = {},\r\n loaderStyle = {},\r\n popupStyle = {},\r\n tooltipStyle = {},\r\n serverSidePagination = false,\r\n onPaginationAndRowSizeChange,\r\n totalRows,\r\n serverSideSorting = false,\r\n onSorting,\r\n onRowClick,\r\n expandedComponent,\r\n loading = false,\r\n loaderComponent = ({ style }) => <RGXLoader style={style} />,\r\n multiColumnSort = false,\r\n selectionCheckbox = false,\r\n onSelectionCheck,\r\n rowPerPage = 10,\r\n page = 1,\r\n mode = \"light\",\r\n}) => {\r\n const darkMode = mode === \"dark\";\r\n\r\n // State to manage the current page of the table. Tracks the active page number for pagination purposes.\r\n const [currentPage, setCurrentPage] = useState<number>(page);\r\n\r\n // State to store the current dataset to display in the table. This can be updated when data is filtered, sorted, or paginated.\r\n const [currentData, setCurrentData] = useState<any[]>(data);\r\n\r\n // State to manage the sorting configuration for multiple columns. Allows multi-column sorting with a key and direction.\r\n const [sortConfig, setSortConfig] = useState<\r\n { key: string; direction: \"asc\" | \"desc\" }[]\r\n >([]);\r\n\r\n // State to manage the number of rows per page for pagination. Default value is taken from rowsPerPageOptions.\r\n const [rowsPerPage, setRowsPerPage] = useState<number>(rowsPerPageOptions[0]);\r\n\r\n // State to track which row is currently expanded. Holds the index of the expanded row, or null if no row is expanded.\r\n const [expandedRow, setExpandedRow] = useState<number | null>(null);\r\n\r\n // State to manage the popover state for each column. Tracks which column's dot menu is active.\r\n const [isDotPopover, setIsDotPopover] = useState<string | null>(null);\r\n\r\n // State to manage selection info: `selectedRows` for selected row IDs and `selectAllChecked` for the \"select all\" checkbox state\r\n const [_selectionInfo, _setSelectionInfo] = useState<{\r\n selectedRows: any[];\r\n selectAllChecked: boolean;\r\n }>({\r\n selectedRows: [],\r\n selectAllChecked: false,\r\n });\r\n\r\n /**\r\n * Handle page change event for pagination.\r\n * This function updates the current page and triggers the callback to inform the parent component\r\n * of the new page number and the number of rows per page.\r\n *\r\n * @param page - The new page number to navigate to.\r\n */\r\n const onPageChange = (page: number) => {\r\n setCurrentPage(page); // Update the current page state with the new page number\r\n setExpandedRow(null);\r\n onPaginationAndRowSizeChange &&\r\n onPaginationAndRowSizeChange(page, rowsPerPage); // Trigger the callback with the updated page and rows per page, if provided\r\n };\r\n\r\n /**\r\n * Handle rows per page change event for pagination.\r\n * This function updates the number of rows per page, resets the current page to 1,\r\n * and triggers the callback to inform the parent component about the new rows per page and the reset page number.\r\n *\r\n * @param rows - The new number of rows per page to display.\r\n */\r\n const onRowsPerPageChange = (rows: number) => {\r\n setRowsPerPage(rows); // Update the rows per page state with the new number of rows\r\n setCurrentPage(1); // Reset to the first page as the rows per page changed\r\n onPaginationAndRowSizeChange && onPaginationAndRowSizeChange(1, rows); // Trigger the callback with page 1 and the updated rows per page\r\n };\r\n\r\n /**\r\n * Handles the \"select all\" checkbox functionality in the table header.\r\n *\r\n * This function toggles the state of the \"select all\" checkbox and updates\r\n * the `selectedRows` state to select or deselect all rows. It calls the\r\n * `onSelectionCheck` callback with the updated selection data.\r\n *\r\n * @note Triggered when the user interacts with the \"select all\" checkbox in the header.\r\n */\r\n const onHeaderCheckboxChange = () => {\r\n const _newSelectAllChecked = !_selectionInfo.selectAllChecked;\r\n const _newSelectedRows = _newSelectAllChecked\r\n ? data.map((row) => row.id)\r\n : [];\r\n\r\n _setSelectionInfo({\r\n selectedRows: _newSelectedRows,\r\n selectAllChecked: _newSelectAllChecked,\r\n });\r\n\r\n onSelectionCheck &&\r\n onSelectionCheck(_newSelectedRows, _newSelectAllChecked);\r\n };\r\n\r\n /**\r\n * Handles the row-level checkbox functionality.\r\n *\r\n * This function updates the selection state of an individual row when its\r\n * checkbox is toggled. It updates the `selectedRows` state accordingly and\r\n * also checks if the \"select all\" checkbox needs to be updated based on the\r\n * current selection. The `onSelectionCheck` callback is called with the updated selection data.\r\n *\r\n * @param rowId - The unique ID of the row that was selected or deselected.\r\n * @note Triggered when the user interacts with an individual row checkbox.\r\n */\r\n const onRowCheckboxChange = (rowId: string | number) => {\r\n const _newSelectedRows = _selectionInfo.selectedRows.includes(rowId)\r\n ? _selectionInfo.selectedRows.filter((id) => id !== rowId)\r\n : [..._selectionInfo.selectedRows, rowId];\r\n\r\n const _newSelectAllChecked = _newSelectedRows.length === data.length;\r\n\r\n _setSelectionInfo({\r\n selectedRows: _newSelectedRows,\r\n selectAllChecked: _newSelectAllChecked,\r\n });\r\n\r\n onSelectionCheck &&\r\n onSelectionCheck(_newSelectedRows, _newSelectAllChecked);\r\n };\r\n\r\n /**\r\n * Handles the event when the user changes the sorting configuration\r\n * for multiple columns (ascending or descending).\r\n *\r\n * This function manages the sorting state, toggles the sorting direction\r\n * when the same column is clicked, or adds a new column to the sort order\r\n * if it's not already included. It also handles multi-column sorting based\r\n * on the `multiColumnSort` flag.\r\n *\r\n * @param column - The column for which the sorting needs to be changed.\r\n */\r\n const onSortingMultipleSupportHandler = (\r\n column: { key: string },\r\n customDirection?: \"asc\" | \"desc\" // Optional custom direction\r\n ) => {\r\n setSortConfig((prev) => {\r\n if (!multiColumnSort) {\r\n // If multiSort is false, reset sorting to only one column (single-column sorting)\r\n return [\r\n {\r\n key: column.key,\r\n direction:\r\n customDirection ||\r\n (prev[0]?.key === column.key && prev[0].direction === \"asc\"\r\n ? \"desc\"\r\n : (\"asc\" as \"asc\" | \"desc\")), // Toggle direction or use custom direction\r\n },\r\n ];\r\n }\r\n\r\n // Check if the column is already in the sorting configuration\r\n const existingSort = prev.find((s) => s.key === column.key);\r\n let newSortConfig;\r\n\r\n if (existingSort) {\r\n // If the column is already in the sort config, toggle the sorting direction or use custom direction\r\n newSortConfig = prev.map((s) =>\r\n s.key === column.key\r\n ? {\r\n key: s.key,\r\n direction:\r\n customDirection ||\r\n (s.direction === \"asc\" ? \"desc\" : (\"asc\" as \"asc\" | \"desc\")),\r\n }\r\n : s\r\n );\r\n } else {\r\n // If the column is not in the sort config, add it with the custom direction or default to 'asc'\r\n newSortConfig = [\r\n ...prev,\r\n { key: column.key, direction: customDirection || \"asc\" },\r\n ];\r\n }\r\n\r\n // Remove any sorting entries where direction is undefined (safety check)\r\n newSortConfig = newSortConfig.filter((s) => s.direction !== undefined);\r\n\r\n if (serverSideSorting && onSorting) {\r\n // For server-side sorting, call the provided `onSorting` function to handle the sorting request externally\r\n onSorting(newSortConfig);\r\n } else {\r\n // For client-side sorting, update the state with the new sorting configuration\r\n setSortConfig(newSortConfig);\r\n }\r\n\r\n return newSortConfig;\r\n });\r\n };\r\n\r\n /**\r\n * Clears the sorting configuration and resets the table's sorting state.\r\n *\r\n * This function resets the `sortConfig` state to an empty array, effectively clearing any applied sorting.\r\n * If server-side sorting is enabled, it also triggers a server-side reset by calling the `onSorting` callback\r\n * with an empty array to reset sorting on the server.\r\n *\r\n * @returns {void} - No return value, the sorting is cleared.\r\n */\r\n const onClearSort = () => {\r\n // Clear the sorting configuration by resetting the sortConfig state to an empty array\r\n setSortConfig([]);\r\n\r\n // If server-side sorting is used, trigger the server-side clear sort (optional)\r\n if (serverSideSorting && onSorting) {\r\n onSorting([]); // Send an empty array to reset sorting on the server\r\n }\r\n };\r\n\r\n /**\r\n * Sorts the data based on the current sorting configuration.\r\n *\r\n * This function applies multi-column sorting by iterating over the sorting configuration (`sortConfig`),\r\n * sorting the data in ascending or descending order depending on the direction defined for each column.\r\n * It supports both client-side sorting and server-side sorting by checking the `serverSideSorting` flag.\r\n *\r\n * @returns {Array} - The sorted data based on the current sorting configuration.\r\n */\r\n const sortedItems = useMemo(() => {\r\n if (serverSideSorting) return data; // Skip sorting if using server-side sorting\r\n\r\n const sorted = [...data].sort((a, b) => {\r\n // Iterate through each sorting column and apply the sorting logic\r\n for (let { key, direction } of sortConfig) {\r\n const column = columns.find((col) => col.key === key);\r\n const aValue = a[key];\r\n const bValue = b[key];\r\n\r\n if (column?.onSort) {\r\n // If a custom sorting function is provided, use it to compare the values\r\n const result = column.onSort(aValue, bValue, direction);\r\n if (result !== 0) return result; // If sorting result is not 0, return it immediately\r\n } else {\r\n // Default sorting logic: alphabetical comparison for strings and numerical comparison for numbers\r\n if (typeof aValue === \"string\" && typeof bValue === \"string\") {\r\n const comparison = aValue.localeCompare(bValue);\r\n if (comparison !== 0)\r\n return direction === \"asc\" ? comparison : -comparison; // Apply ascending/descending order\r\n } else if (typeof aValue === \"number\" && typeof bValue === \"number\") {\r\n if (aValue !== bValue)\r\n return direction === \"asc\" ? aValue - bValue : bValue - aValue; // Compare numerically\r\n }\r\n }\r\n }\r\n return 0; // Return 0 if no sorting criteria are met (equal values)\r\n });\r\n\r\n return sorted; // Return the sorted array\r\n }, [data, sortConfig, columns, serverSideSorting]); // Recompute whenever data or sorting configuration changes\r\n\r\n // Calculate the total number of pages based on the pagination method (client-side or server-side)\r\n const totalPages = serverSidePagination\r\n ? Math.ceil((totalRows || 0) / rowsPerPage) // For server-side pagination, calculate total pages based on totalRows (from server)\r\n : Math.ceil(currentData.length / rowsPerPage); // For client-side pagination, calculate total pages based on currentData\r\n\r\n // Slices the data for the current page based on the pagination method (client-side or server-side)\r\n const currentPageData = serverSidePagination\r\n ? currentData // Use full data for server-side pagination; parent component handles slicing\r\n : currentData.slice(\r\n (currentPage - 1) * rowsPerPage, // Calculate the starting index for the slice\r\n currentPage * rowsPerPage // Calculate the ending index for the slice\r\n );\r\n\r\n // Sums up column widths, using the specified value or defaulting to 100px if missing.\r\n const totalWidth = columns.reduce(\r\n (sum, column) => sum + (column.width ? column.width : 100),\r\n 0\r\n );\r\n\r\n // Define pagination components based on the selected pagination type\r\n const pagination: Record<string, JSX.Element> = {\r\n // \"rgx-table-pagination\": The default pagination with table-like controls\r\n \"rgx-table-pagination\": (\r\n <RGXTablePagination\r\n currentPage={currentPage} // Current page number\r\n totalPages={totalPages} // Total number of pages\r\n rowsPerPage={rowsPerPage} // Number of rows per page\r\n totalRows={serverSidePagination ? totalRows ?? 0 : currentData.length} // Total rows count based on server-side or client-side pagination\r\n onPageChange={onPageChange} // Callback to handle page changes\r\n onRowsPerPageChange={onRowsPerPageChange} // Callback to handle changes in the number of rows per page\r\n rowsPerPageOptions={rowsPerPageOptions} // Options for how many rows per page the user can select\r\n style={paginationStyle} // Custom styling for pagination\r\n loading={loading} // Show a loading indicator while data is being fetched\r\n mode={mode}\r\n />\r\n ),\r\n // \"rgx-arrow-pagination\": Custom pagination with arrow-based navigation\r\n \"rgx-arrow-pagination\": (\r\n <RGXArrowPagination\r\n currentPage={currentPage} // Current page number\r\n totalPages={totalPages} // Total number of pages\r\n rowsPerPage={rowsPerPage} // Number of rows per page\r\n totalRows={serverSidePagination ? totalRows ?? 0 : currentData.length} // Total rows count based on server-side or client-side pagination\r\n onPageChange={onPageChange} // Callback to handle page changes\r\n onRowsPerPageChange={onRowsPerPageChange} // Callback to handle changes in the number of rows per page\r\n rowsPerPageOptions={rowsPerPageOptions} // Options for how many rows per page the user can select\r\n style={paginationStyle} // Custom styling for pagination\r\n loading={loading} // Show a loading indicator while data is being fetched\r\n mode={mode}\r\n />\r\n ),\r\n };\r\n\r\n /**\r\n * Updates the `currentData` state whenever the `sortedItems` array changes.\r\n *\r\n * This effect listens for changes in the `sortedItems` array and updates the\r\n * `currentData` state accordingly. This ensures that the component always\r\n * renders the most up-to-date sorted data.\r\n *\r\n * @note This effect is triggered every time the `sortedItems` array changes\r\n * due to sorting changes or any other dependency in `sortedItems`.\r\n */\r\n useEffect(() => {\r\n setCurrentData(sortedItems); // Set the state with the new sorted data\r\n }, [sortedItems]); // Dependency array ensures the effect runs when `sortedItems` changes\r\n\r\n useEffect(() => {\r\n setRowsPerPage(\r\n rowsPerPageOptions?.includes(rowPerPage)\r\n ? rowPerPage\r\n : rowsPerPageOptions[0]\r\n );\r\n }, [rowPerPage]);\r\n\r\n useEffect(() => {\r\n if (serverSidePagination && page && rowPerPage) {\r\n setCurrentPage(page);\r\n }\r\n }, [page, serverSidePagination, rowPerPage]);\r\n\r\n return (\r\n <div className={theme}>\r\n <div\r\n className={mergeClasses(\r\n \"rgx-table-container\",\r\n loading && \"rgx-table-container-loading\",\r\n darkMode && \"rgx-table-container-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-container\"],\r\n ...(loading && {\r\n ...tableStyle[\"rgx-table-container-loading\"],\r\n }),\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-container-dark\"],\r\n }),\r\n }}\r\n >\r\n {/* Conditionally render the loader if loading is true */}\r\n {loading &&\r\n loaderComponent &&\r\n loaderComponent({\r\n style: loaderStyle,\r\n })}\r\n\r\n {/* Render the table structure */}\r\n <table\r\n className=\"rgx-table\"\r\n style={{\r\n ...(totalWidth < 1150\r\n ? { width: \"100%\" }\r\n : { minWidth: `${totalWidth}px` }),\r\n ...tableStyle[\"rgx-table\"],\r\n }}\r\n >\r\n <thead\r\n className={mergeClasses(\r\n \"rgx-table-head\",\r\n darkMode && \"rgx-table-head-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-head\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-head-dark\"],\r\n }),\r\n }}\r\n >\r\n {/* Render header checkbox if enabled */}\r\n <tr\r\n className=\"rgx-table-head-tr\"\r\n style={{\r\n ...tableStyle[\"rgx-table-head-tr\"],\r\n }}\r\n >\r\n {selectionCheckbox && (\r\n <th\r\n className={mergeClasses(\r\n \"rgx-table-head-th-checkbox\",\r\n darkMode && \"rgx-table-head-th-checkbox-dark\"\r\n )}\r\n style={{\r\n width: \"20px\",\r\n ...tableStyle[\"rgx-table-head-th-checkbox\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-head-th-checkbox-dark\"],\r\n }),\r\n }}\r\n >\r\n <input\r\n type=\"checkbox\"\r\n className=\"rgx-table-header-checkbox\"\r\n style={{\r\n ...tableStyle[\"rgx-table-header-checkbox\"],\r\n }}\r\n checked={_selectionInfo.selectAllChecked}\r\n onChange={onHeaderCheckboxChange}\r\n />\r\n </th>\r\n )}\r\n {/* Render table headers based on column definitions */}\r\n {columns?.map((column, index) => (\r\n <th\r\n key={index}\r\n className={mergeClasses(\r\n \"rgx-table-head-th\",\r\n darkMode && \"rgx-table-head-th-dark\"\r\n )}\r\n style={{\r\n textAlign: \"left\",\r\n width: column.width ? `${column.width}px` : \"100px\",\r\n ...tableStyle[\"rgx-table-head-th\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-head-th-dark\"],\r\n }),\r\n }}\r\n >\r\n <div\r\n style={{\r\n display: \"flex\",\r\n justifyContent: \"space-between\",\r\n alignItems: \"center\",\r\n }}\r\n >\r\n <div\r\n onClick={\r\n () =>\r\n column.sortable &&\r\n onSortingMultipleSupportHandler(column) // Trigger sorting on click\r\n }\r\n style={{\r\n display: \"flex\",\r\n cursor: column.sortable ? \"pointer\" : \"default\",\r\n }}\r\n >\r\n {column.name} {/* Render column name */}\r\n {column.sortable && (\r\n <SvgIcon\r\n svgPath={\r\n sortConfig.some((sort) => sort.key === column.key)\r\n ? sortConfig.find(\r\n (sort) => sort.key === column.key\r\n )?.direction === \"asc\"\r\n ? `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-up-wide-narrow\"><path d=\"m3 8 4-4 4 4\"/><path d=\"M7 4v16\"/><path d=\"M11 12h10\"/><path d=\"M11 16h7\"/><path d=\"M11 20h4\"/></svg>`\r\n : `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-down-wide-narrow\"><path d=\"m3 16 4 4 4-4\"/><path d=\"M7 20V4\"/><path d=\"M11 4h10\"/><path d=\"M11 8h7\"/><path d=\"M11 12h4\"/></svg>`\r\n : `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-down-up\"><path d=\"m3 16 4 4 4-4\"/><path d=\"M7 20V4\"/><path d=\"m21 8-4-4-4 4\"/><path d=\"M17 4v16\"/></svg>`\r\n }\r\n className=\"rgx-table-sort-icon\"\r\n style={{\r\n marginLeft: \"8px\",\r\n ...tableStyle[\"rgx-table-sort-icon\"],\r\n }}\r\n />\r\n )}\r\n </div>\r\n {column.sortable && (\r\n <div>\r\n <div\r\n style={{ cursor: \"pointer\", display: \"inline-block\" }}\r\n onClick={() => {\r\n setIsDotPopover(\r\n isDotPopover === column.key ? null : column.key\r\n );\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-ellipsis-vertical\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/></svg>`}\r\n className=\"rgx-table-ellipsis-vertical-icon\"\r\n style={{\r\n marginRight: \"8px\",\r\n marginTop: \"4px\",\r\n marginBottom: \"-4px\",\r\n ...tableStyle[\"rgx-table-ellipsis-vertical-icon\"],\r\n }}\r\n />\r\n </div>\r\n {isDotPopover === column.key && (\r\n <RGXPopover\r\n isOpen={isDotPopover === column.key}\r\n onClose={() => {\r\n setIsDotPopover(null);\r\n }}\r\n style={popupStyle}\r\n mode={mode}\r\n >\r\n {sortConfig.find((sort) => sort.key === column.key)\r\n ?.direction === \"desc\" && (\r\n <div\r\n className={mergeClasses(\r\n \"rgx-table-popup-items\",\r\n darkMode && \"rgx-table-popup-items-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-popup-items\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-popup-items-dark\"],\r\n }),\r\n }}\r\n onClick={() => {\r\n column.sortable &&\r\n onSortingMultipleSupportHandler(\r\n column,\r\n \"asc\"\r\n );\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-up-wide-narrow\"><path d=\"m3 8 4-4 4 4\"/><path d=\"M7 4v16\"/><path d=\"M11 12h10\"/><path d=\"M11 16h7\"/><path d=\"M11 20h4\"/></svg>`}\r\n style={{\r\n marginRight: \"8px\",\r\n fontSize: \"14px\",\r\n ...tableStyle[\"rgx-table-asc-sort-icon\"],\r\n }}\r\n className=\"rgx-table-asc-sort-icon\"\r\n />\r\n <span>Sort Ascending</span>\r\n </div>\r\n )}\r\n\r\n {sortConfig.find((sort) => sort.key === column.key)\r\n ?.direction === \"asc\" && (\r\n <div\r\n className={mergeClasses(\r\n \"rgx-table-popup-items\",\r\n darkMode && \"rgx-table-popup-items-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-popup-items\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-popup-items-dark\"],\r\n }),\r\n }}\r\n onClick={() => {\r\n column.sortable &&\r\n onSortingMultipleSupportHandler(\r\n column,\r\n \"desc\"\r\n );\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-down-wide-narrow\"><path d=\"m3 16 4 4 4-4\"/><path d=\"M7 20V4\"/><path d=\"M11 4h10\"/><path d=\"M11 8h7\"/><path d=\"M11 12h4\"/></svg>`}\r\n style={{\r\n marginRight: \"8px\",\r\n fontSize: \"14px\",\r\n ...tableStyle[\"rgx-table-desc-sort-icon\"],\r\n }}\r\n className=\"rgx-table-desc-sort-icon\"\r\n />\r\n <span>Sort Descending</span>\r\n </div>\r\n )}\r\n\r\n {!Boolean(\r\n sortConfig.some((sort) => sort.key === column.key)\r\n ) && (\r\n <>\r\n <div\r\n className={mergeClasses(\r\n \"rgx-table-popup-items\",\r\n darkMode && \"rgx-table-popup-items-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-popup-items\"],\r\n ...(darkMode && {\r\n ...tableStyle[\r\n \"rgx-table-popup-items-dark\"\r\n ],\r\n }),\r\n }}\r\n onClick={() => {\r\n column.sortable &&\r\n onSortingMultipleSupportHandler(\r\n column,\r\n \"asc\"\r\n );\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-up-wide-narrow\"><path d=\"m3 8 4-4 4 4\"/><path d=\"M7 4v16\"/><path d=\"M11 12h10\"/><path d=\"M11 16h7\"/><path d=\"M11 20h4\"/></svg>`}\r\n style={{\r\n marginRight: \"8px\",\r\n fontSize: \"14px\",\r\n ...tableStyle[\"rgx-table-asc-sort-icon\"],\r\n }}\r\n className=\"rgx-table-asc-sort-icon\"\r\n />\r\n <span>Sort Ascending</span>\r\n </div>\r\n <div\r\n className={mergeClasses(\r\n \"rgx-table-popup-items\",\r\n darkMode && \"rgx-table-popup-items-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-popup-items\"],\r\n ...(darkMode && {\r\n ...tableStyle[\r\n \"rgx-table-popup-items-dark\"\r\n ],\r\n }),\r\n }}\r\n onClick={() => {\r\n column.sortable &&\r\n onSortingMultipleSupportHandler(\r\n column,\r\n \"desc\"\r\n );\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-down-wide-narrow\"><path d=\"m3 16 4 4 4-4\"/><path d=\"M7 20V4\"/><path d=\"M11 4h10\"/><path d=\"M11 8h7\"/><path d=\"M11 12h4\"/></svg>`}\r\n style={{\r\n marginRight: \"8px\",\r\n fontSize: \"14px\",\r\n ...tableStyle[\"rgx-table-desc-sort-icon\"],\r\n }}\r\n className=\"rgx-table-desc-sort-icon\"\r\n />\r\n <span>Sort Descending</span>\r\n </div>\r\n </>\r\n )}\r\n\r\n {Boolean(\r\n sortConfig.some((sort) => sort.key === column.key)\r\n ) && (\r\n <div\r\n className={mergeClasses(\r\n \"rgx-table-popup-items\",\r\n darkMode && \"rgx-table-popup-items-dark\"\r\n )}\r\n style={{\r\n ...tableStyle[\"rgx-table-popup-items\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-popup-items-dark\"],\r\n }),\r\n }}\r\n onClick={() => {\r\n onClearSort();\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-arrow-down-up\"><path d=\"m3 16 4 4 4-4\"/><path d=\"M7 20V4\"/><path d=\"m21 8-4-4-4 4\"/><path d=\"M17 4v16\"/></svg>`}\r\n style={{\r\n marginRight: \"8px\",\r\n fontSize: \"14px\",\r\n ...tableStyle[\r\n \"rgx-table-asc-desc-sort-icon\"\r\n ],\r\n }}\r\n className=\"rgx-table-asc-desc-sort-icon\"\r\n />\r\n <span>Clear Sort</span>\r\n </div>\r\n )}\r\n </RGXPopover>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </th>\r\n ))}\r\n </tr>\r\n </thead>\r\n <tbody\r\n className={mergeClasses(\r\n \"rgx-table-body\",\r\n loading && \"rgx-table-tobody-loading\"\r\n )} // Add loading class if data is loading\r\n style={{\r\n ...tableStyle[\"rgx-table-body\"],\r\n ...(loading && {\r\n ...tableStyle[\"rgx-table-tobody-loading\"],\r\n }),\r\n }}\r\n >\r\n {/* Render table rows for the current page */}\r\n {currentPageData.map((row, rowIndex) => (\r\n <React.Fragment\r\n key={row.id || rowIndex} // Use a unique key for each row\r\n >\r\n <tr\r\n key={row.id || rowIndex} // Use a unique key for each row\r\n className={mergeClasses(\r\n \"rgx-table-body-tr\",\r\n expandedRow === rowIndex && \"rgx-table-body-tr-expanded\",\r\n darkMode && \"rgx-table-body-tr-dark\"\r\n )} // Add class for expanded row\r\n style={{\r\n ...tableStyle[\"rgx-table-body-tr\"],\r\n ...(expandedRow === rowIndex && {\r\n ...tableStyle[\"rgx-table-body-tr-expanded\"],\r\n }),\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-body-tr-dark\"],\r\n }),\r\n }}\r\n onClick={() => {\r\n // Handle row click for expanding or triggering onRowClick callback\r\n expandedComponent &&\r\n setExpandedRow(\r\n expandedRow === rowIndex ? null : rowIndex\r\n );\r\n onRowClick && onRowClick(row); // Call user-provided onRowClick handler\r\n }}\r\n >\r\n {/* Render row checkbox if enabled */}\r\n {selectionCheckbox && (\r\n <td\r\n className={mergeClasses(\r\n \"rgx-table-body-td-checkbox\",\r\n darkMode && \"rgx-table-body-td-checkbox-dark\"\r\n )}\r\n style={{\r\n width: \"20px\",\r\n ...tableStyle[\"rgx-table-body-td-checkbox\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-body-td-checkbox-dark\"],\r\n }),\r\n }}\r\n >\r\n <input\r\n type=\"checkbox\"\r\n className=\"rgx-table-row-checkbox\"\r\n style={{\r\n ...tableStyle[\"rgx-table-row-checkbox\"],\r\n }}\r\n checked={_selectionInfo.selectedRows.includes(row.id)}\r\n onChange={() => onRowCheckboxChange(row.id)}\r\n />\r\n </td>\r\n )}\r\n {/* Render cells based on column definitions */}\r\n {columns.map((column, colIndex) => (\r\n <td\r\n key={colIndex}\r\n className={mergeClasses(\r\n \"rgx-table-body-td\",\r\n darkMode && \"rgx-table-body-td-dark\"\r\n )}\r\n style={{\r\n width: column.width || \"auto\",\r\n ...tableStyle[\"rgx-table-body-td\"],\r\n ...(darkMode && {\r\n ...tableStyle[\"rgx-table-body-td-dark\"],\r\n }),\r\n }}\r\n >\r\n {/* Conditionally render the arrow icon if expandedComponent is passed */}\r\n {/* {expandedComponent && colIndex === 0 && (\r\n <span\r\n className=\"rgx-table-expanded-arrow\"\r\n style={{\r\n ...tableStyle[\"rgx-table-expanded-arrow\"],\r\n }}\r\n onClick={(e) => {\r\n e.stopPropagation(); // Prevent row click event from firing\r\n setExpandedRow(\r\n expandedRow === rowIndex ? null : rowIndex\r\n ); // Toggle expanded row\r\n }}\r\n >\r\n <SvgIcon\r\n svgPath={\r\n expandedRow === rowIndex\r\n ? `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevron-down\"><path d=\"m6 9 6 6 6-6\"/></svg>`\r\n : `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevron-up\"><path d=\"m18 15-6-6-6 6\"/></svg>`\r\n }\r\n className=\"rgx-table-expanded-arrow-icon\"\r\n style={{\r\n ...tableStyle[\"rgx-table-expanded-arrow-icon\"],\r\n }}\r\n />\r\n </span>\r\n )} */}\r\n\r\n {/* Render cell data with optional tooltip and custom render function */}\r\n {column.tooltip ? (\r\n <RGXTooltip\r\n content={\r\n column.tooltipCustomContent\r\n ? column.tooltipCustomContent(row)\r\n : row[column.key] // Display custom tooltip content if provided\r\n }\r\n style={tooltipStyle}\r\n >\r\n {\r\n column.render\r\n ? column.render(row) // Custom render function if available\r\n : row[column.key] ?? \"\" // Default value for cell data\r\n }\r\n </RGXTooltip>\r\n ) : column.render ? (\r\n column.render(row) // Custom render function if available\r\n ) : (\r\n row[column.key] ?? \"\" // Default value for cell data\r\n )}\r\n </td>\r\n ))}\r\n </tr>\r\n\r\n {/* Render expanded row content if expandedRow matches the row index */}\r\n {expandedRow === rowIndex && expandedComponent && (\r\n <tr\r\n className=\"rgx-table-expanded-row-tr\"\r\n style={{\r\n ...tableStyle[\"rgx-table-expanded-row-tr\"],\r\n }}\r\n >\r\n <td\r\n colSpan={columns.length} // Span across all columns for expanded row\r\n className=\"rgx-table-expanded-row-td\"\r\n style={{\r\n ...tableStyle[\"rgx-table-expanded-row-td\"],\r\n }}\r\n >\r\n {/* Call the expanded component and pass the row data */}\r\n {expandedComponent(row)}\r\n </td>\r\n </tr>\r\n )}\r\n </React.Fragment>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n {/* Render the pagination component */}\r\n {pagination[paginationType as keyof typeof pagination]}\r\n </div>\r\n );\r\n};\r\n\r\nexport default ReactGridX;\r\n","import React from \"react\";\nimport { SvgIconProps } from \"../../types/svg-icons-props\";\n\nconst SvgIcon: React.FC<SvgIconProps> = ({\n color = \"currentColor\",\n svgPath,\n style = {},\n className = \"\",\n ...props\n}) => {\n const widthMatch = svgPath.match(/width=[\"']?(\\d+)[\"']?/);\n const heightMatch = svgPath.match(/height=[\"']?(\\d+)[\"']?/);\n\n const width = widthMatch ? `${widthMatch[1]}px` : \"24px\";\n const height = heightMatch ? `${heightMatch[1]}px` : \"24px\";\n\n return (\n <span\n className={className}\n style={{\n display: \"inline-block\",\n width,\n height,\n ...style,\n }}\n dangerouslySetInnerHTML={{\n __html: svgPath.replace(/currentColor/g, color),\n }}\n {...props}\n />\n );\n};\n\nexport default SvgIcon;\n","import React from \"react\";\nimport { RGXArrowPaginationProps } from \"../../../types/arrow-pagination-props\";\nimport SvgIcon from \"../../SVGIcons\";\n\nconst RGXArrowPagination: React.FC<RGXArrowPaginationProps> = ({\n currentPage,\n totalPages,\n rowsPerPage,\n totalRows,\n onPageChange,\n onRowsPerPageChange,\n rowsPerPageOptions = [5, 10, 15],\n style = {},\n loading = false,\n mode = \"light\",\n}) => {\n const darkMode = mode === \"dark\";\n\n return (\n <div\n className=\"rgx-arrow-pagination\"\n style={{\n ...style[\"rgx-arrow-pagination\"],\n }}\n >\n {/* Display pagination information: current page and total rows */}\n <div\n className={`rgx-arrow-pagination-info ${\n darkMode && \"rgx-arrow-pagination-info-dark\"\n }`}\n style={{\n ...style[\"rgx-arrow-pagination-info\"],\n ...(darkMode && {\n ...style[\"rgx-arrow-pagination-info-dark\"],\n }),\n }}\n >\n Showing {(currentPage - 1) * rowsPerPage + 1} to{\" \"}\n {Math.min(currentPage * rowsPerPage, totalRows)} of {totalRows}\n </div>\n\n {/* Rows per page selector and pagination controls */}\n <div\n className=\"rgx-arrow-pagination-row-per-page\"\n style={{\n ...style[\"rgx-arrow-pagination-row-per-page\"],\n }}\n >\n {/* Rows per page dropdown */}\n <div\n className=\"rgx-arrow-pagination-rows-per-page\"\n style={{\n ...style[\"rgx-arrow-pagination-rows-per-page\"],\n }}\n >\n <label\n htmlFor=\"rowsPerPage\"\n className={`rgx-arrow-pagination-rows-per-page-label ${\n darkMode && \"rgx-arrow-pagination-rows-per-page-label-dark\"\n }`}\n style={{\n ...style[\"rgx-arrow-pagination-rows-per-page-label\"],\n ...(darkMode && {\n ...style[\"rgx-arrow-pagination-rows-per-page-label-dark\"],\n }),\n }}\n >\n Rows per page:\n </label>\n <select\n id=\"rowsPerPage\"\n className={`rgx-arrow-pagination-rows-per-page-select ${\n darkMode && \"rgx-arrow-pagination-rows-per-page-select-dark\"\n }`}\n style={{\n ...style[\"rgx-arrow-pagination-rows-per-page-select\"],\n ...(darkMode && {\n ...style[\"rgx-arrow-pagination-rows-per-page-select-dark\"],\n }),\n }}\n value={rowsPerPage}\n onChange={(e) => onRowsPerPageChange(Number(e.target.value))}\n disabled={loading}\n >\n {rowsPerPageOptions?.map((option) => (\n <option key={option} value={option}>\n {option}\n </option>\n ))}\n </select>\n </div>\n\n {/* Pagination controls (previous, next, first, last buttons) */}\n <div\n className=\"rgx-arrow-pagination-controls\"\n style={{\n ...style[\"rgx-arrow-pagination-controls\"],\n }}\n >\n {/* Previous First page button */}\n <button\n disabled={currentPage === 1 || loading}\n onClick={() => onPageChange(1)} // Navigate to the first page\n className={`rgx-arrow-pagination-button ${\n darkMode && \"rgx-arrow-pagination-button-dark\"\n }`}\n style={{\n ...style[\"rgx-arrow-pagination-button\"],\n ...(darkMode && {\n ...style[\"rgx-arrow-pagination-button-dark\"],\n }),\n }}\n >\n <SvgIcon\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevrons-left\"><path d=\"m11 17-5-5 5-5\"/><path d=\"m18 17-5-5 5-5\"/></svg>`}\n />\n </button>\n {/* Previous page button */}\n <button\n disabled={currentPage === 1 || loading}\n onClick={() => onPageChange(currentPage - 1)} // Navigate to the previous page\n className={`rgx-arrow-pagination-button ${\n darkMode && \"rgx-arrow-pagination-button-dark\"\n }`}\n style={{\n ...style[\"rgx-arrow-pagination-button\"],\n ...(darkMode && {\n ...style[\"rgx-arrow-pagination-button-dark\"],\n }),\n }}\n >\n <SvgIcon\n svgPath={`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18