import { Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, TableRowData as RowStateBase, Table, TableBody, TableCell, TableCellLayout, TableColumnDefinition, TableColumnSizingOptions, TableHeader, TableHeaderCell, TableRow, TableRowId, TableSelectionCell, createTableColumn, useArrowNavigationGroup, useFluent, useScrollbarWidth, useTableColumnSizing_unstable, useTableFeatures, useTableSelection } from '@fluentui/react-components'; import React, { useEffect } from 'react'; import { FixedSizeList as List, ListChildComponentProps } from "react-window"; export type DocumentsTableComponentItem = { id: string; type: string; }; export interface IDocumentsTableComponentProps { items: DocumentsTableComponentItem[]; onSelectedItem: (index: number) => void; height: number; style?: React.CSSProperties; } const columns: TableColumnDefinition<DocumentsTableComponentItem>[] = [ createTableColumn<DocumentsTableComponentItem>({ columnId: "id", compare: (a, b) => { return a.id.localeCompare(b.id); }, renderHeaderCell: () => { return "id"; }, renderCell: (item) => { return ( <TableCellLayout>{item.id}</TableCellLayout> ); }, }), createTableColumn<DocumentsTableComponentItem>({ columnId: "type", compare: (a, b) => { return a.type.localeCompare(b.type); }, renderHeaderCell: () => { return "/type"; }, renderCell: (item) => { return ( <TableCellLayout>{item.type}</TableCellLayout> ); }, }), ]; interface TableRowData extends RowStateBase<DocumentsTableComponentItem> { onClick: (e: React.MouseEvent) => void; onKeyDown: (e: React.KeyboardEvent) => void; selected: boolean; appearance: "brand" | "none"; } interface ReactWindowRenderFnProps extends ListChildComponentProps { data: TableRowData[]; } const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => { const { item, selected, appearance, onClick, onKeyDown } = data[index]; return <TableRow aria-rowindex={index + 2} style={style} key={item.id} // onClick={onClick} // onKeyDown={onKeyDown} aria-selected={selected} appearance={appearance} > <TableSelectionCell checked={selected} checkboxIndicator={{ "aria-label": "Select row" }} onClick={onClick} onKeyDown={onKeyDown} /> {columns.map((column) => ( <TableCell key={column.columnId} // onClick={(/* e */) => setSelectedRows(new Set<TableRowId>([index]))} onKeyDown={onKeyDown} // {...columnSizing.getTableCellProps(column.columnId)} > {column.renderCell(item)} </TableCell> ))} </TableRow>; }; export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> = ({ items, onSelectedItem, style, height, }: IDocumentsTableComponentProps) => { const { targetDocument } = useFluent(); const scrollbarWidth = useScrollbarWidth({ targetDocument }); const [activeItemIndex, setActiveItemIndex] = React.useState<number>(undefined); const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>({ id: { idealWidth: 280, minWidth: 273, }, type: { minWidth: 110, defaultWidth: 120, }, }); const onColumnResize = React.useCallback((_, { columnId, width }) => { setColumnSizingOptions((state) => ({ ...state, [columnId]: { ...state[columnId], idealWidth: width, }, })); }, []); const [selectedRows, setSelectedRows] = React.useState<Set<TableRowId>>( () => new Set<TableRowId>([0]) ); const { getRows, columnSizing_unstable: columnSizing, tableRef, selection: { allRowsSelected, someRowsSelected, toggleAllRows, toggleRow, isRowSelected, }, } = useTableFeatures( { columns, items, }, [ useTableColumnSizing_unstable({ columnSizingOptions, onColumnResize }), useTableSelection({ selectionMode: "multiselect", selectedItems: selectedRows, onSelectionChange: (e, data) => setSelectedRows(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<HTMLDivElement>) => { 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) { onSelectedItem(newActiveItemIndex); setActiveItemIndex(newActiveItemIndex); } } }, [selectedRows, items]); // Cell keyboard navigation const keyboardNavAttr = useArrowNavigationGroup({ axis: "grid" }); const tableProps = { "aria-label": "Filtered documents table", role: "grid", ...columnSizing.getTableProps(), ...keyboardNavAttr, size: "extra-small", ref: tableRef, ...style, }; return ( <Table noNativeElements {...tableProps}> <TableHeader> <TableRow> <TableSelectionCell checked={ allRowsSelected ? true : someRowsSelected ? "mixed" : false } onClick={toggleAllRows} onKeyDown={toggleAllKeydown} checkboxIndicator={{ "aria-label": "Select all rows " }} /> {columns.map((column, index) => ( <Menu openOnContext key={column.columnId}> <MenuTrigger> <TableHeaderCell key={column.columnId} {...columnSizing.getTableHeaderCellProps(column.columnId)} > {column.renderHeaderCell()} </TableHeaderCell> </MenuTrigger> <MenuPopover> <MenuList> <MenuItem onClick={columnSizing.enableKeyboardMode( column.columnId )} > Keyboard Column Resizing </MenuItem> </MenuList> </MenuPopover> </Menu> ))} {/** Scrollbar alignment for the header */} <div role="presentation" style={{ width: scrollbarWidth }} /> </TableRow> </TableHeader> <TableBody style={{ height: "100%", flex: 1 }}> <List height={height !== undefined ? height - 32 : 0} itemCount={items.length} itemSize={45} width="100%" itemData={rows} > {RenderRow} </List> </TableBody> </Table> ); };