import { Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, TableRowData as RowStateBase, Table, TableBody, TableCell, TableCellLayout, TableColumnDefinition, TableColumnSizingOptions, TableHeader, TableHeaderCell, TableRow, TableRowId, TableSelectionCell, createTableColumn, useArrowNavigationGroup, useTableColumnSizing_unstable, useTableFeatures, useTableSelection, } from "@fluentui/react-components"; import React, { useCallback, useEffect, useMemo } from "react"; import { FixedSizeList as List, ListChildComponentProps } from "react-window"; export type DocumentsTableComponentItem = { id: string; } & Record; export type ColumnHeaders = { idHeader: string; partitionKeyHeaders: string[]; }; export interface IDocumentsTableComponentProps { items: DocumentsTableComponentItem[]; onItemClicked: (index: number) => void; onSelectedRowsChange: (selectedItemsIndices: Set) => void; selectedRows: Set; size: { height: number; width: number }; columnHeaders: ColumnHeaders; style?: React.CSSProperties; } interface TableRowData extends RowStateBase { onClick: (e: React.MouseEvent) => void; onKeyDown: (e: React.KeyboardEvent) => void; selected: boolean; appearance: "brand" | "none"; } interface ReactWindowRenderFnProps extends ListChildComponentProps { data: TableRowData[]; } export const DocumentsTableComponent: React.FC = ({ items, onItemClicked, onSelectedRowsChange, selectedRows, style, size, columnHeaders, }: IDocumentsTableComponentProps) => { const [activeItemIndex, setActiveItemIndex] = React.useState(undefined); const initialSizingOptions: TableColumnSizingOptions = { id: { idealWidth: 280, minWidth: 50, }, }; columnHeaders.partitionKeyHeaders.forEach((pkHeader) => { initialSizingOptions[pkHeader] = { idealWidth: 200, minWidth: 50, }; }); const [columnSizingOptions, setColumnSizingOptions] = React.useState(initialSizingOptions); const onColumnResize = React.useCallback((_, { columnId, width }) => { setColumnSizingOptions((state) => ({ ...state, [columnId]: { ...state[columnId], idealWidth: width, }, })); }, []); // Columns must be a static object and cannot change on re-renders otherwise React will complain about too many refreshes const columns: TableColumnDefinition[] = useMemo( () => [ createTableColumn({ columnId: "id", compare: (a, b) => a.id.localeCompare(b.id), renderHeaderCell: () => columnHeaders.idHeader, renderCell: (item) => ( {item.id} ), }), ].concat( columnHeaders.partitionKeyHeaders.map((pkHeader) => createTableColumn({ columnId: pkHeader, compare: (a, b) => a[pkHeader].localeCompare(b[pkHeader]), // Show Refresh button on last column renderHeaderCell: () => {pkHeader}, renderCell: (item) => ( {item[pkHeader]} ), }), ), ), [columnHeaders], ); const onIdClicked = useCallback((index: number) => onSelectedRowsChange(new Set([index])), [onSelectedRowsChange]); const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => { const { item, selected, appearance, onClick, onKeyDown } = data[index]; return ( {columns.map((column) => ( onSelectedRowsChange(new Set([index]))} onKeyDown={() => onIdClicked(index)} {...columnSizing.getTableCellProps(column.columnId)} tabIndex={column.columnId === "id" ? 0 : -1} > {column.renderCell(item)} ))} ); }; const { getRows, columnSizing_unstable: columnSizing, tableRef, selection: { allRowsSelected, someRowsSelected, toggleAllRows, toggleRow, isRowSelected }, } = useTableFeatures( { columns, items, }, [ useTableColumnSizing_unstable({ columnSizingOptions, onColumnResize }), useTableSelection({ selectionMode: "multiselect", selectedItems: selectedRows, // eslint-disable-next-line react/prop-types onSelectionChange: (e, data) => onSelectedRowsChange(data.selectedItems), }), ], ); const rows: TableRowData[] = getRows((row) => { const selected = isRowSelected(row.rowId); return { ...row, onClick: (e: React.MouseEvent) => toggleRow(e, row.rowId), onKeyDown: (e: React.KeyboardEvent) => { if (e.key === " ") { e.preventDefault(); toggleRow(e, row.rowId); } }, selected, appearance: selected ? ("brand" as const) : ("none" as const), }; }); const toggleAllKeydown = React.useCallback( (e: React.KeyboardEvent) => { if (e.key === " ") { toggleAllRows(e); e.preventDefault(); } }, [toggleAllRows], ); // Load document depending on selection useEffect(() => { if (selectedRows.size === 1 && items.length > 0) { const newActiveItemIndex = selectedRows.values().next().value; if (newActiveItemIndex !== activeItemIndex) { onItemClicked(newActiveItemIndex); setActiveItemIndex(newActiveItemIndex); } } }, [selectedRows, items]); // Cell keyboard navigation const keyboardNavAttr = useArrowNavigationGroup({ axis: "grid" }); // TODO: Bug in fluent UI typings that requires any here // eslint-disable-next-line @typescript-eslint/no-explicit-any const tableProps: any = { "aria-label": "Filtered documents table", role: "grid", ...columnSizing.getTableProps(), ...keyboardNavAttr, size: "extra-small", ref: tableRef, ...style, }; return ( {columns.map((column /* index */) => ( {column.renderHeaderCell()} Keyboard Column Resizing ))} {RenderRow}
); };