cosmos-explorer/src/Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent.tsx
2024-02-29 15:21:53 +01:00

250 lines
7.1 KiB
TypeScript

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[];
}
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 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>;
};
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>
);
};