diff --git a/pkg/lib/cockpit-components-table.jsx b/pkg/lib/cockpit-components-table.tsx similarity index 76% rename from pkg/lib/cockpit-components-table.jsx rename to pkg/lib/cockpit-components-table.tsx index 61536db487bc..51c85e7511b9 100644 --- a/pkg/lib/cockpit-components-table.jsx +++ b/pkg/lib/cockpit-components-table.tsx @@ -24,6 +24,8 @@ import { ExpandableRowContent, Table, Thead, Tbody, Tr, Th, Td, SortByDirection, + TdProps, ThProps, TrProps, TableProps, + OnSelect, } from '@patternfly/react-table'; import { EmptyState, EmptyStateBody, EmptyStateFooter, EmptyStateActions } from "@patternfly/react-core/dist/esm/components/EmptyState/index.js"; import { Text, TextContent, TextVariants } from "@patternfly/react-core/dist/esm/components/Text/index.js"; @@ -65,11 +67,56 @@ const _ = cockpit.gettext; * event, isSelected, rowIndex, rowData, extraData. rowData contains props with an id property of the clicked row. * - onHeaderSelect: event, isSelected. */ + +interface ListingTableRowColumnProps { + title: React.ReactNode; + sortKey?: string; + props?: TdProps | ThProps; +} + +type ListingTableRowColumn = React.ReactNode | ListingTableRowColumnProps; + +export interface ListingTableRowProps { + columns: ListingTableRowColumn[]; + props?: TrProps; + expandedContent?: React.ReactNode; + selected?: boolean; + initiallyExpanded?: boolean; + hasPadding?: boolean; +} + +export interface ListingTableColumnProps { + title: string; + header?: boolean; + sortable?: boolean; + props?: ThProps; +} + +export interface ListingTableProps extends Omit { + actions?: React.ReactNode[], + afterToggle?: (expanded: boolean) => void, + caption?: string, + className?: string, + columns: (string | ListingTableColumnProps)[], + emptyCaption?: string, + emptyCaptionDetail?: React.ReactNode, + emptyComponent?: React.ReactNode, + isEmptyStateInTable?: boolean, + loading?: string, + onRowClick?: (event: React.KeyboardEvent | React.MouseEvent | undefined, row: ListingTableRowProps) => void, + onSelect?: OnSelect; + onHeaderSelect?: OnSelect, + rows: ListingTableRowProps[], + showHeader?: boolean, + sortBy?: { index: number, direction: SortByDirection }, + sortMethod?: (rows: ListingTableRowProps[], dir: SortByDirection, index: number) => ListingTableRowProps[], +} + export const ListingTable = ({ actions = [], afterToggle, caption = '', - className, + className = '', columns: cells = [], emptyCaption = '', emptyCaptionDetail, @@ -84,11 +131,11 @@ export const ListingTable = ({ sortBy, sortMethod, ...extraProps -}) => { +} : ListingTableProps) => { let rows = [...tableRows]; - const [expanded, setExpanded] = useState({}); - const [newItems, setNewItems] = useState([]); - const [currentRowsKeys, setCurrentRowsKeys] = useState([]); + const [expanded, setExpanded] = useState>({}); + const [newItems, setNewItems] = useState([]); + const [currentRowsKeys, setCurrentRowsKeys] = useState([]); const [activeSortIndex, setActiveSortIndex] = useState(sortBy?.index ?? 0); const [activeSortDirection, setActiveSortDirection] = useState(sortBy?.direction ?? SortByDirection.asc); const rowKeys = rows.map(row => row.props?.key) @@ -98,8 +145,8 @@ export const ListingTable = ({ useEffect(() => { // Don't highlight all when the list gets loaded - const _currentRowsKeys = JSON.parse(currentRowsKeysStr); - const _rowKeys = JSON.parse(rowKeysStr); + const _currentRowsKeys: React.Key[] = JSON.parse(currentRowsKeysStr); + const _rowKeys: React.Key[] = JSON.parse(rowKeysStr); if (_currentRowsKeys.length !== 0) { const new_keys = _rowKeys.filter(key => _currentRowsKeys.indexOf(key) === -1); @@ -112,10 +159,10 @@ export const ListingTable = ({ setCurrentRowsKeys(crk => [...new Set([...crk, ..._rowKeys])]); }, [currentRowsKeysStr, rowKeysStr]); - const isSortable = cells.some(col => col.sortable); + const isSortable = cells.some(col => typeof col != "string" && col.sortable); const isExpandable = rows.some(row => row.expandedContent); - const tableProps = {}; + const tableProps: TableProps = {}; /* Basic table properties */ tableProps.className = "ct-table"; @@ -174,17 +221,30 @@ export const ListingTable = ({ rows = [{ columns: emptyStateCell }]; } - const sortRows = () => { + const sortRows = (): ListingTableRowProps[] => { + function sortkey(col: ListingTableRowColumn): string { + if (typeof col == "string") + return col; + if (col && typeof col == "object") { + const props = col as ListingTableRowColumnProps; + if (props.sortKey) + return props.sortKey; + if (typeof props.title == "string") + return props.title; + } + return ""; + } + const sortedRows = rows.sort((a, b) => { const aitem = a.columns[activeSortIndex]; const bitem = b.columns[activeSortIndex]; - return ((typeof aitem == 'string' ? aitem : (aitem.sortKey || aitem.title)).localeCompare(typeof bitem == 'string' ? bitem : (bitem.sortKey || bitem.title))); + return sortkey(aitem).localeCompare(sortkey(bitem)); }); return activeSortDirection === SortByDirection.asc ? sortedRows : sortedRows.reverse(); }; - const onSort = (event, index, direction) => { + const onSort = (_event: unknown, index: number, direction: SortByDirection) => { setActiveSortIndex(index); setActiveSortDirection(direction); }; @@ -199,6 +259,8 @@ export const ListingTable = ({ if (rowProps.key && newItems.indexOf(rowProps.key) >= 0) rowProps.className = (rowProps.className || "") + " ct-new-item"; + cockpit.assert(typeof rowProps.key != "bigint"); + const rowKey = rowProps.key || rowIndex; const isExpanded = expanded[rowKey] === undefined ? !!row.initiallyExpanded : expanded[rowKey]; let columnSpanCnt = 0; @@ -208,7 +270,7 @@ export const ListingTable = ({ {isExpandable ? (row.expandedContent ? { if (afterToggle) @@ -230,23 +292,27 @@ export const ListingTable = ({ }} /> } {row.columns.map(cell => { - const { key, ...cellProps } = cell.props || {}; + const cell_as_props = (cell as ListingTableRowColumnProps).props || {}; + const { key, expand, select, ...cellProps } = cell_as_props; const headerCell = cells[columnSpanCnt]; const dataLabel = typeof headerCell == 'object' ? headerCell.title : headerCell; const colKey = dataLabel || columnSpanCnt; columnSpanCnt += cellProps.colSpan || 1; - if (headerCell?.header) + if (typeof headerCell != "string" && headerCell?.header) { return ( - - {typeof cell == 'object' ? cell.title : cell} + + {typeof cell == 'object' ? cell_as_props.title : cell} ); + } return ( - - {typeof cell == 'object' ? cell.title : cell} + + {typeof cell == 'object' ? cell_as_props.title : cell} ); })} @@ -259,7 +325,7 @@ export const ListingTable = ({ ); - return {rowPair}; + return {rowPair}; }); return ( @@ -279,9 +345,9 @@ export const ListingTable = ({ isSelected: rows.every(r => r.selected) }} />} {cells.map((column, columnIndex) => { - const columnProps = column.props; + const columnProps = typeof column == "string" ? {} : column.props; const sortParams = ( - column.sortable + (typeof column != "string" && column.sortable) ? { sort: { sortBy: {