diff --git a/components/expandable-row-table-component-v2/.DS_Store b/components/expandable-row-table-component-v2/.DS_Store deleted file mode 100644 index 7e590d2..0000000 Binary files a/components/expandable-row-table-component-v2/.DS_Store and /dev/null differ diff --git a/components/expandable-row-table-component-v2/README.md b/components/expandable-row-table-component-v2/README.md deleted file mode 100644 index 6503e6c..0000000 --- a/components/expandable-row-table-component-v2/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# Expandable Row Table for Retool - -A highly customizable expandable row table component built with React, TypeScript, and the Retool Custom Component SDK. - -This component is designed for advanced operational dashboards, analytics tooling, audit monitoring, transaction inspection, and enterprise-style data grid interactions inside Retool. - ---- - -## Features - -### Expandable Rows - -* Expandable detail sections -* Recursive nested JSON rendering -* Automatic key-value formatting -* Multi-key expandable support -* API response inspection -* Object and array visualization -* Long text handling -* URL rendering - -### Advanced Table Features - -* Dynamic column visibility -* Drag-and-drop column rearranging -* Persistent column ordering -* ASC → DESC → Reset sorting -* Search and filtering -* Pagination -* Responsive layout -* CSV export -* Interactive row selection - -### Styling Controls - -* Header colors -* Row colors -* Alternate row colors -* Font customization -* Footer styling -* Pagination styling -* Theme-aware design - ---- - -## Technology Stack - -* React 18 -* TypeScript -* Retool Custom Component SDK -* Lucide React Icons - ---- - -## Inputs - -| Property | Type | Description | -| ---------------------- | ------- | -------------------------------- | -| tableData | Array | Main table dataset | -| pageSize | Number | Rows per page | -| expandableDataKey | String | Expandable object field(s) | -| showToolbar | Boolean | Toggle toolbar | -| showSearchBar | Boolean | Toggle search | -| headerColor | String | Header background | -| headerTextColor | String | Header text color | -| rowBackground | String | Row background | -| alternateRowBackground | String | Alternate row background | -| accentColor | String | Accent/highlight color | -| footerBackgroundColor | String | Footer background | -| paginationTextColor | String | Pagination text color | -| rowHeight | Enum | Small / Medium / Large / Dynamic | -| columnWidthMode | Enum | Auto / Manual | - ---- - -## Outputs - -| Property | Type | Description | -| --------------- | ------ | -------------------------- | -| selectedRowData | Object | Selected row data | -| columnOrder | Array | Current column arrangement | -| visibleColumns | Array | Visible table columns | - ---- - -## Expandable Data Examples - -### Single Key - -```txt -metadata -``` - -### Multiple Keys - -```txt -metadata,api_response,tags -``` - -### JSON Array Format - -```json -["metadata","api_response","tags"] -``` - ---- - -## Sorting Behavior - -* First click → Ascending -* Second click → Descending -* Third click → Reset sorting - ---- - -## Column Rearranging - -Columns support drag-and-drop reordering and persist after refresh using Retool state storage. - ---- - -## Use Cases - -* Audit Logs -* Transaction Monitoring -* API Debugging -* Financial Dashboards -* Shipment Tracking -* Analytics Dashboards -* Internal Admin Tools -* Operational Monitoring - ---- - -## License - -MIT License diff --git a/components/expandable-row-table-component-v2/cover.png b/components/expandable-row-table-component-v2/cover.png deleted file mode 100644 index 1af05fe..0000000 Binary files a/components/expandable-row-table-component-v2/cover.png and /dev/null differ diff --git a/components/expandable-row-table-component-v2/metadata.json b/components/expandable-row-table-component-v2/metadata.json deleted file mode 100644 index b1938a4..0000000 --- a/components/expandable-row-table-component-v2/metadata.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "expandable-row-table", - "title": "Expandable Row Table", - "author": "@widlestudiollp", - "shortDescription": "An advanced expandable row table component for Retool with nested JSON rendering, drag-and-drop column rearranging, pagination, sorting, filtering, CSV export, and enterprise-style data grid interactions.", - "tags": [ - "Table", - "Data Grid", - "Expandable Rows", - "Analytics", - "JSON Viewer", - "Dashboard", - "React", - "TypeScript", - "Retool" - ] -} \ No newline at end of file diff --git a/components/expandable-row-table-component-v2/package.json b/components/expandable-row-table-component-v2/package.json deleted file mode 100644 index 73ab964..0000000 --- a/components/expandable-row-table-component-v2/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "my-react-app", - "version": "0.1.0", - "private": true, - "dependencies": { - "@tryretool/custom-component-support": "latest", - "fuse.js": "^7.3.0", - "lucide-react": "^0.553.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "scripts": { - "dev": "npx retool-ccl dev", - "deploy": "npx retool-ccl deploy" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "@types/react": "^18.2.55", - "@typescript-eslint/eslint-plugin": "^7.3.1", - "@typescript-eslint/parser": "^7.3.1", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.1", - "postcss-modules": "^6.0.0", - "prettier": "^3.0.3" - }, - "retoolCustomComponentLibraryConfig": { - "name": "ExpandableRowTable", - "label": "Expandable Row Table", - "description": "Table with exapndable row feature with responsive.", - "entryPoint": "src/index.tsx", - "outputPath": "dist" - } -} \ No newline at end of file diff --git a/components/expandable-row-table-component-v2/src/index.tsx b/components/expandable-row-table-component-v2/src/index.tsx deleted file mode 100644 index 281af38..0000000 --- a/components/expandable-row-table-component-v2/src/index.tsx +++ /dev/null @@ -1,1903 +0,0 @@ -import React, { FC, useEffect, useMemo, useState } from 'react' -import { Retool } from '@tryretool/custom-component-support' -import { - ChevronDown, - ChevronRight, - Download, - RefreshCcw, -} from 'lucide-react' - -export const ExpandableTableComponent: FC = () => { - const [data] = Retool.useStateArray({ - name: 'tableData', - label: 'Table Data', - description: - 'Main dataset for the table component', - }) - - const [ - selectedRowData, - setSelectedRowData, - ] = Retool.useStateObject({ - name: 'selectedRowData', - label: 'Selected Row Data', - description: - 'Currently selected row output', - }) - - const [expandableDataKey] = - Retool.useStateString({ - name: 'expandableDataKey', - initialValue: '', - label: 'Expandable Data Keys', - description: - 'Single or multiple expandable keys. Supports: metadata OR metadata,api_response OR ["metadata","api_response"]', - }) - - const [pageSize] = - Retool.useStateNumber({ - name: 'pageSize', - initialValue: 10, - label: 'Page Size', - description: - 'Number of rows per page', - }) - - const [headerColor] = - Retool.useStateString({ - name: 'headerColor', - initialValue: '#f3f4f6', - label: 'Header Background', - description: - 'Table header background color', - }) - - const [headerTextColor] = - Retool.useStateString({ - name: 'headerTextColor', - initialValue: '#111827', - label: 'Header Text Color', - description: - 'Table header text color', - }) - - const [accentColor] = - Retool.useStateString({ - name: 'accentColor', - initialValue: '#2563eb', - label: 'Accent Color', - description: - 'Primary highlight color', - }) - - const [ - visibleColumns, - setVisibleColumns, - ] = Retool.useStateArray({ - name: 'visibleColumns', - label: 'Visible Columns', - description: - 'Columns visible in the table', - }) - - const [ - columnOrder, - setColumnOrder, - ] = Retool.useStateArray({ - name: 'columnOrder', - label: 'Column Order', - description: - 'Persisted draggable column order', - }) - - const [fontFamily] = - Retool.useStateString({ - name: 'fontFamily', - initialValue: - 'Inter, system-ui, sans-serif', - label: 'Font Family', - description: - 'Global table font family', - }) - - const [headerFontSize] = - Retool.useStateNumber({ - name: 'headerFontSize', - initialValue: 14, - label: 'Header Font Size', - description: - 'Header text font size', - }) - - const [bodyFontSize] = - Retool.useStateNumber({ - name: 'bodyFontSize', - initialValue: 14, - label: 'Body Font Size', - description: - 'Table body font size', - }) - - const [rowBackground] = - Retool.useStateString({ - name: 'rowBackground', - initialValue: '#ffffff', - label: 'Row Background', - description: - 'Default table row background', - }) - - const [alternateRowBackground] = - Retool.useStateString({ - name: - 'alternateRowBackground', - initialValue: '#fafafa', - label: - 'Alternate Row Background', - description: - 'Alternate row background color', - }) - - const [emptyStateMessage] = - Retool.useStateString({ - name: 'emptyStateMessage', - initialValue: 'No rows found', - label: 'Empty State Message', - description: - 'Shown when no table rows exist', - }) - - const [showSearchBar] = - Retool.useStateBoolean({ - name: 'showSearchBar', - initialValue: true, - label: 'Show Search Bar', - inspector: 'checkbox', - description: - 'Toggle search bar visibility', - }) - - const [showToolbar] = - Retool.useStateBoolean({ - name: 'showToolbar', - initialValue: true, - label: 'Show Toolbar', - inspector: 'checkbox', - description: - 'Toggle footer toolbar actions', - }) - - const [rowHeight] = - Retool.useStateEnumeration({ - name: 'rowHeight', - enumDefinition: [ - 'small', - 'medium', - 'large', - 'dynamic', - ], - enumLabels: { - small: 'Small', - medium: 'Medium', - large: 'Large', - dynamic: 'Dynamic', - }, - initialValue: 'medium', - inspector: 'select', - label: 'Row Height', - description: - 'Height style of table rows', - }) - - const [columnWidthMode] = - Retool.useStateEnumeration({ - name: 'columnWidthMode', - enumDefinition: [ - 'auto', - 'manual', - ], - enumLabels: { - auto: 'Auto', - manual: 'Manual', - }, - initialValue: 'auto', - inspector: 'select', - label: 'Column Width Mode', - description: - 'Auto width or manual resizable columns', - }) - - const [footerBackgroundColor] = - Retool.useStateString({ - name: - 'footerBackgroundColor', - initialValue: '#ffffff', - label: - 'Footer Background Color', - description: - 'Footer background color', - }) - - const [footerBorderColor] = - Retool.useStateString({ - name: - 'footerBorderColor', - initialValue: '#e5e7eb', - label: - 'Footer Border Color', - description: - 'Footer top border color', - }) - - const [paginationTextColor] = - Retool.useStateString({ - name: - 'paginationTextColor', - initialValue: '#111827', - label: - 'Pagination Text Color', - description: - 'Pagination text/icon color', - }) - - const [paginationButtonBackground] = - Retool.useStateString({ - name: - 'paginationButtonBackground', - initialValue: '#ffffff', - label: - 'Pagination Button Background', - description: - 'Pagination button background', - }) - - const [paginationActiveBackground] = - Retool.useStateString({ - name: - 'paginationActiveBackground', - initialValue: '#eff6ff', - label: - 'Pagination Active Background', - description: - 'Current page indicator background', - }) - - const [paginationFontSize] = - Retool.useStateNumber({ - name: - 'paginationFontSize', - initialValue: 14, - label: - 'Pagination Font Size', - description: - 'Pagination font size', - }) - - const safeData = Array.isArray(data) - ? data - : [] - - const safeHeaderColor = - headerColor || '#f3f4f6' - - const safeHeaderTextColor = - headerTextColor || '#111827' - - const safeAccentColor = - accentColor || '#2563eb' - - const safeFontFamily = - fontFamily || - 'Inter, system-ui, sans-serif' - - const safeRowBackground = - rowBackground || '#ffffff' - - const safeAlternateRowBackground = - alternateRowBackground || '#fafafa' - - const safeHeaderFontSize = - Number(headerFontSize) || 14 - - const safeBodyFontSize = - Number(bodyFontSize) || 14 - - const safePageSize = - Number(pageSize) || 10 - - const safeFooterBackgroundColor = - footerBackgroundColor || - '#ffffff' - - const safeFooterBorderColor = - footerBorderColor || - '#e5e7eb' - - const safePaginationTextColor = - paginationTextColor || - '#111827' - - const safePaginationButtonBackground = - paginationButtonBackground || - '#ffffff' - - const safePaginationActiveBackground = - paginationActiveBackground || - '#eff6ff' - - const safePaginationFontSize = - Number( - paginationFontSize - ) || 14 - - const [selectedRow, setSelectedRow] = - useState(null) - - const [expandedRow, setExpandedRow] = - useState(null) - - const [searchTerm, setSearchTerm] = - useState('') - - const [sortConfig, setSortConfig] = - useState<{ - key: string - direction: 'asc' | 'desc' - } | null>(null) - - const [currentPage, setCurrentPage] = - useState(1) - - const [ - draggedColumn, - setDraggedColumn, - ] = useState( - null - ) - - const getRowHeight = () => { - switch (rowHeight) { - case 'small': - return 38 - - case 'large': - return 72 - - case 'dynamic': - return 'auto' - - default: - return 52 - } - } - - useEffect(() => { - if ( - selectedRow !== null && - paginatedData.length - ) { - const matchedRow = - paginatedData.find( - (row, index) => - (row?.id ?? index) === - selectedRow - ) - - if (matchedRow) { - setSelectedRowData({ - ...matchedRow, - __selectedRowId: - selectedRow, - }) - } - } - }, [selectedRow]) - - const rowHeightValue = - getRowHeight() - - const allColumns = useMemo(() => { - if ( - !safeData.length || - typeof safeData[0] !== 'object' - ) { - return [] - } - - return Object.keys( - safeData[0] - ).filter((key) => { - if (!expandableDataKey) - return true - - return ( - key !== expandableDataKey - ) - }) - }, [safeData, expandableDataKey]) - - useEffect(() => { - if (!allColumns.length) { - return - } - - if ( - !Array.isArray( - columnOrder - ) || - !columnOrder.length - ) { - setColumnOrder(allColumns) - - return - } - - const missingColumns = - allColumns.filter( - (col) => - !columnOrder.includes(col) - ) - - if (missingColumns.length) { - setColumnOrder([ - ...columnOrder, - ...missingColumns, - ]) - } - }, [allColumns]) - - const tableHeaders = useMemo(() => { - const orderedColumns = - Array.isArray( - columnOrder - ) && - columnOrder.length - ? columnOrder.filter((col) => - allColumns.includes(col) - ) - : allColumns - - if ( - !Array.isArray( - visibleColumns - ) || - !visibleColumns.length - ) { - return orderedColumns - } - - return orderedColumns.filter((col) => - visibleColumns.includes(col) - ) - }, [ - allColumns, - visibleColumns, - columnOrder, - ]) - - const filteredSortedData = useMemo(() => { - let filtered = [...safeData] - - if (searchTerm) { - filtered = filtered.filter( - (row) => - Object.values( - row || {} - ).some((value) => - String(value) - .toLowerCase() - .includes( - searchTerm.toLowerCase() - ) - ) - ) - } - - if (sortConfig) { - filtered.sort((a, b) => { - const A = - a?.[sortConfig.key] - const B = - b?.[sortConfig.key] - - if (A < B) { - return sortConfig.direction === - 'asc' - ? -1 - : 1 - } - - if (A > B) { - return sortConfig.direction === - 'asc' - ? 1 - : -1 - } - - return 0 - }) - } - - return filtered - }, [ - safeData, - searchTerm, - sortConfig, - ]) - - const paginatedData = useMemo(() => { - const start = - (currentPage - 1) * - safePageSize - - return filteredSortedData.slice( - start, - start + safePageSize - ) - }, [ - filteredSortedData, - currentPage, - safePageSize, - ]) - - const totalPages = Math.max( - 1, - Math.ceil( - filteredSortedData.length / - safePageSize - ) - ) - - useEffect(() => { - if (paginatedData.length > 0) { - const existsOnPage = - paginatedData.some( - (row, index) => - (row?.id ?? index) === - selectedRow - ) - - if (!existsOnPage) { - const firstRow = - paginatedData[0] - - const rowId = - firstRow?.id ?? 0 - - setSelectedRow(rowId) - - setSelectedRowData({ - ...firstRow, - __selectedRowId: - rowId, - }) - } - } - }, [paginatedData]) - - const toggleRow = (id: any) => { - setExpandedRow((prev) => - prev === id ? null : id - ) - } - - const handleSort = ( - key: string - ) => { - if ( - !sortConfig || - sortConfig.key !== key - ) { - setSortConfig({ - key, - direction: 'asc', - }) - - return - } - - if ( - sortConfig.direction === - 'asc' - ) { - setSortConfig({ - key, - direction: 'desc', - }) - - return - } - - if ( - sortConfig.direction === - 'desc' - ) { - setSortConfig(null) - - return - } - } - - const exportCSV = () => { - const rows = [] - - rows.push(tableHeaders.join(',')) - - filteredSortedData.forEach( - (row) => { - rows.push( - tableHeaders - .map((header) => - JSON.stringify( - row?.[header] ?? '' - ) - ) - .join(',') - ) - } - ) - - const blob = new Blob( - [rows.join('\n')], - { - type: 'text/csv', - } - ) - - const url = - URL.createObjectURL(blob) - - const a = - document.createElement('a') - - a.href = url - - a.download = - 'table-data.csv' - - a.click() - - URL.revokeObjectURL(url) - } - - const formatLabel = (key: string) => { - return key - .split('.') - .map((part) => - part - .replace(/_/g, ' ') - .replace( - /([a-z])([A-Z])/g, - '$1 $2' - ) - .replace(/\b\w/g, (c) => - c.toUpperCase() - ) - ) - .join('.') - } - - const isDateValue = ( - value: any - ) => { - if ( - value === null || - value === undefined - ) { - return false - } - - if (value instanceof Date) { - return true - } - - if ( - typeof value !== 'string' - ) { - return false - } - - const parsedDate = - new Date(value) - - return ( - !isNaN( - parsedDate.getTime() - ) && - ( - /^\d{4}-\d{2}-\d{2}/.test( - value - ) || - value.includes('T') || - value.includes(':') - ) - ) - } - - const formatDateTime = ( - value: any - ) => { - try { - const date = - new Date(value) - - if ( - isNaN(date.getTime()) - ) { - return value - } - - return date.toLocaleString( - 'en-US', - { - month: 'long', - day: 'numeric', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', - hour12: true, - } - ) - } catch { - return value - } - } - - const normalizeExpandableData = ( - data: any, - parentKey = '' - ): any[] => { - if ( - data === null || - data === undefined || - data === '' - ) { - return [] - } - - if (typeof data === 'string') { - const trimmed = data.trim() - - if ( - (trimmed.startsWith('{') && - trimmed.endsWith('}')) || - (trimmed.startsWith('[') && - trimmed.endsWith(']')) - ) { - try { - const parsed = JSON.parse(trimmed) - - return normalizeExpandableData( - parsed, - parentKey - ) - } catch { - return [ - { - key: formatLabel( - parentKey || 'Value' - ), - value: isDateValue(data) - ? formatDateTime( - data - ) - : data, - }, - ] - } - } - - return [ - { - key: formatLabel( - parentKey || 'Value' - ), - value: isDateValue(data) - ? formatDateTime( - data - ) - : String(data), - }, - ] - } - - if (Array.isArray(data)) { - return data.flatMap( - (item, index) => - normalizeExpandableData( - item, - parentKey - ? `${parentKey}.${index}` - : `${index}` - ) - ) - } - - if ( - typeof data === 'object' && - data !== null - ) { - return Object.entries(data).flatMap( - ([key, value]) => - normalizeExpandableData( - value, - parentKey - ? `${parentKey}.${key}` - : key - ) - ) - } - - return [ - { - key: formatLabel( - parentKey || 'Value' - ), - value: isDateValue(data) - ? formatDateTime( - data - ) - : String(data), - }, - ] - } - - const getExpandableData = ( - item: any - ) => { - if (!expandableDataKey) { - return item - } - - let keys: string[] = [] - - if ( - typeof expandableDataKey === - 'string' - ) { - const trimmed = - expandableDataKey.trim() - - if ( - trimmed.startsWith('[') - ) { - try { - const parsed = - JSON.parse(trimmed) - - if ( - Array.isArray(parsed) - ) { - keys = parsed.map( - String - ) - } - } catch { - keys = trimmed - .split(',') - .map((k) => - k.trim() - ) - } - } else { - keys = trimmed - .split(',') - .map((k) => - k.trim() - ) - } - } - - if (!keys.length) { - return item - } - - const combined: Record< - string, - any - > = {} - - keys.forEach((key) => { - combined[key] = - item?.[key] - }) - - return combined - } - - const renderExpandableContent = ( - details: any - ) => { - const normalized = - normalizeExpandableData(details) - - if (!normalized.length) { - return ( -
- {emptyStateMessage || - 'No data found'} -
- ) - } - - return ( -
- {normalized.map( - (item, index) => ( -
-
- {item.key} -
- -
- {item.value} -
-
- ) - )} -
- ) - } - - return ( -
-
- {/*
-
- Visible Columns -
- -
- {allColumns.map((column) => { - const isSelected = - !visibleColumns?.length || - visibleColumns.includes( - column - ) - - return ( - - ) - })} -
-
*/} - {showSearchBar && ( -
- - setSearchTerm( - e.target.value - ) - } - style={{ - width: 280, - height: 38, - padding: '0 14px', - border: - '1px solid #d1d5db', - borderRadius: 8, - outline: 'none', - fontSize: 14, - }} - /> -
- )} - -
- - - - - ) - )} - - - - - {!paginatedData.length ? ( - - - - ) : ( - paginatedData.map( - ( - item, - index - ) => { - const rowId = - item?.id ?? - index - - const isExpanded = - expandedRow === - rowId - - const expandableData = - getExpandableData(item) - - return ( - - { - setSelectedRow( - rowId - ) - - setSelectedRowData( - { - ...item, - __selectedRowId: - rowId, - } - ) - }} - style={{ - background: - selectedRow === - rowId - ? `${safeAccentColor}15` - : index % - 2 === - 0 - ? safeRowBackground - : safeAlternateRowBackground, - borderBottom: - '1px solid #e5e7eb', - height: - rowHeightValue, - cursor: - 'pointer', - transition: - '0.15s ease', - }} - > - - - {tableHeaders.map( - ( - key - ) => { - const value = - item[ - key - ] - - const renderCellValue = - () => { - if ( - value === - null || - value === - undefined || - value === - '' - ) { - return '—' - } - - if ( - typeof value === - 'boolean' - ) { - return value - ? 'Yes' - : 'No' - } - - if ( - typeof value === - 'number' - ) { - return value.toLocaleString() - } - - if ( - Array.isArray( - value - ) - ) { - return ( -
- {value.map( - ( - v, - i - ) => ( - - {typeof v === - 'object' - ? JSON.stringify( - v - ) - : String( - v - )} - - ) - )} -
- ) - } - if ( - isDateValue(value) - ) { - return formatDateTime( - value - ) - } - if ( - typeof value === - 'object' && - value !== - null - ) { - return ( -
-
-                                          {JSON.stringify(
-                                            value,
-                                            null,
-                                            2
-                                          )}
-                                        
-
- ) - } - - if ( - typeof value === - 'string' && - /^(https?:\/\/)/i.test( - value - ) - ) { - return ( - - { - value - } - - ) - } - - if ( - typeof value === - 'string' && - value.length > - 120 - ) { - return ( -
- { - value - } -
- ) - } - - return String( - value - ) - } - - return ( - - ) - } - )} - - - {isExpanded && ( - - - - )} - - ) - } - ) - )} - -
- - {tableHeaders.map( - (key) => ( - { - setDraggedColumn(key) - }} - onDragOver={(e) => { - e.preventDefault() - }} - onDrop={() => { - if ( - !draggedColumn || - draggedColumn === key - ) { - return - } - - const updated = [ - ...columnOrder, - ] - - const fromIndex = - updated.indexOf( - draggedColumn - ) - - const toIndex = - updated.indexOf(key) - - updated.splice( - fromIndex, - 1 - ) - - updated.splice( - toIndex, - 0, - draggedColumn - ) - - setColumnOrder(updated) - }} - onClick={() => - handleSort( - key - ) - } - style={{ - padding: - '14px 16px', - textAlign: - 'left', - cursor: - 'pointer', - fontWeight: 600, - fontSize: - safeHeaderFontSize, - resize: - columnWidthMode === - 'manual' - ? 'horizontal' - : 'none', - overflow: - 'auto', - minWidth: 160, - color: - safeHeaderTextColor, - borderBottom: - '1px solid #d1d5db', - whiteSpace: - 'nowrap', - userSelect: - 'none', - opacity: - draggedColumn === key - ? 0.5 - : 1, - }} - > -
- {key - .replace( - /_/g, - ' ' - ) - .replace( - /\b\w/g, - ( - c - ) => - c.toUpperCase() - )} - - {sortConfig?.key === - key && - (sortConfig.direction === - 'asc' - ? '↑' - : sortConfig.direction === - 'desc' - ? '↓' - : '')} -
-
- {emptyStateMessage || - 'No rows found'} -
{ - e.stopPropagation() - - toggleRow( - rowId - ) - }} - > - {isExpanded ? ( - - ) : ( - - )} - - {renderCellValue()} -
- {renderExpandableContent( - expandableData - )} -
-
- -
-
- Showing{' '} - {(currentPage - 1) * - safePageSize + - 1} - - - {Math.min( - currentPage * - safePageSize, - filteredSortedData.length - )}{' '} - of{' '} - { - filteredSortedData.length - } -
- -
- - -
-
- {currentPage} -
- - - of {totalPages} - -
- - -
- - {showToolbar && ( -
- - - -
- )} -
-
-
- ) -} \ No newline at end of file