Adding table scrolling

This commit is contained in:
Laurent Nguyen 2024-02-29 13:39:24 +01:00
parent c6cec71fd9
commit 82c7760af2
5 changed files with 149 additions and 58 deletions

View File

@ -2264,33 +2264,33 @@ a:link {
width: 82px; width: 82px;
} }
.tabdocuments .scrollable { // .tabdocuments .scrollable {
height: 100%; // height: 100%;
overflow-y: auto; // overflow-y: auto;
overflow-x: hidden; // overflow-x: hidden;
padding-left: 5px; // padding-left: 5px;
padding-right: 5px; // padding-right: 5px;
width: 100%; // width: 100%;
} // }
.tabdocuments > .tabdocumentsGridElement { // .tabdocuments > .tabdocumentsGridElement {
width: 50%; // width: 50%;
} // }
.tabdocuments > .evenlySpacedHeader { // .tabdocuments > .evenlySpacedHeader {
width: 30%; // width: 30%;
} // }
.tabdocuments.scrollable:focus, // .tabdocuments.scrollable:focus,
.tabdocuments.scrollable:active { // .tabdocuments.scrollable:active {
outline: 1px dotted; // outline: 1px dotted;
} // }
.tabdocuments .scrollable table td { // .tabdocuments .scrollable table td {
white-space: nowrap; // white-space: nowrap;
overflow: hidden; // overflow: hidden;
text-overflow: ellipsis; // text-overflow: ellipsis;
} // }
.mongoDocumentEditor .monaco-editor.vs .redsquiggly { .mongoDocumentEditor .monaco-editor.vs .redsquiggly {
display: none !important; display: none !important;

40
package-lock.json generated
View File

@ -51,6 +51,7 @@
"@types/lodash": "4.14.171", "@types/lodash": "4.14.171",
"@types/mkdirp": "1.0.1", "@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.5.7",
"@uiw/react-split": "5.9.3",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "file:./canvas", "canvas": "file:./canvas",
@ -99,6 +100,7 @@
"react-redux": "7.1.3", "react-redux": "7.1.3",
"react-splitter-layout": "4.0.0", "react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1", "react-string-format": "1.0.1",
"react-window": "1.8.10",
"react-youtube": "9.0.1", "react-youtube": "9.0.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
@ -136,6 +138,7 @@
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1", "@types/react-splitter-layout": "3.0.1",
"@types/react-window": "1.8.8",
"@types/sanitize-html": "1.27.2", "@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",
@ -13526,6 +13529,15 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-window": {
"version": "1.8.8",
"resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz",
"integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/retry": { "node_modules/@types/retry": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
@ -14003,6 +14015,18 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@uiw/react-split": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/@uiw/react-split/-/react-split-5.9.3.tgz",
"integrity": "sha512-HgwETU+kRhzZAp+YiE4Yu8bNJm3jxxnGgGPfkadUl8jA1wsMD3aMMskuhQF5akiUUUadiLUvAc8e1RH9Y/SKDw==",
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@ungap/url-search-params": { "node_modules/@ungap/url-search-params": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/@ungap/url-search-params/-/url-search-params-0.2.2.tgz", "resolved": "https://registry.npmjs.org/@ungap/url-search-params/-/url-search-params-0.2.2.tgz",
@ -34753,6 +34777,22 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/react-window": {
"version": "1.8.10",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz",
"integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==",
"dependencies": {
"@babel/runtime": "^7.0.0",
"memoize-one": ">=3.1.1 <6"
},
"engines": {
"node": ">8.0.0"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-youtube": { "node_modules/react-youtube": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-9.0.1.tgz", "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-9.0.1.tgz",

View File

@ -96,6 +96,7 @@
"react-splitter-layout": "4.0.0", "react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1", "react-string-format": "1.0.1",
"react-youtube": "9.0.1", "react-youtube": "9.0.1",
"react-window": "1.8.10",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
"sanitize-html": "2.3.3", "sanitize-html": "2.3.3",
@ -132,6 +133,7 @@
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1", "@types/react-splitter-layout": "3.0.1",
"@types/react-window": "1.8.8",
"@types/sanitize-html": "1.27.2", "@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",

View File

@ -14,7 +14,7 @@ import { LocalStorageUtility, StorageKey } from 'Shared/StorageUtility';
import { Action } from 'Shared/Telemetry/TelemetryConstants'; import { Action } from 'Shared/Telemetry/TelemetryConstants';
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { logConsoleError } from 'Utils/NotificationConsoleUtils'; import { logConsoleError } from 'Utils/NotificationConsoleUtils';
import React, { KeyboardEventHandler, useEffect, useMemo, useState } from "react"; import React, { KeyboardEventHandler, useEffect, useMemo, useRef, useState } from "react";
import { format } from "react-string-format"; import { format } from "react-string-format";
import CloseIcon from "../../../../images/close-black.svg"; import CloseIcon from "../../../../images/close-black.svg";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
@ -442,6 +442,24 @@ const DocumentsTabComponent: React.FunctionComponent<{
setCurrentDocument(content); setCurrentDocument(content);
}); });
const tableContainerRef = useRef(null);
const [tableContainerHeightPx, setTableContainerHeightPx] = useState<number>(undefined);
useEffect(() => {
if (!tableContainerRef.current) {
return undefined;
}
const resizeObserver = new ResizeObserver(() => {
// Do what you want to do when the size of the element changes
setTableContainerHeightPx(tableContainerRef.current.offsetHeight);
console.log('height', tableContainerRef.current.offsetHeight);
});
resizeObserver.observe(tableContainerRef.current);
return () => resizeObserver.disconnect(); // clean up
}, []);
return <FluentProvider theme={dataExplorerLightTheme} style={{ height: "100%" }}> return <FluentProvider theme={dataExplorerLightTheme} style={{ height: "100%" }}>
<div <div
className="tab-pane active" className="tab-pane active"
@ -551,10 +569,11 @@ const DocumentsTabComponent: React.FunctionComponent<{
{/* <!-- Filter - End --> */} {/* <!-- Filter - End --> */}
{/* <Split> doesn't like to be a flex child */} {/* <Split> doesn't like to be a flex child */}
<div style={{ overflow: "hidden" }}> <div style={{ overflow: "hidden", height: "100%" }}>
<Split> <Split>
<div style={{ minWidth: 440, width: "20%" }}> <div style={{ minWidth: 440, width: "20%", display: "flex", flexDirection: "column", height: "100%" }}
<DocumentsTableComponent style={{ width: "100%", height: "100%" }} items={tableItems} onSelectedItem={onSelectedDocument} /> ref={tableContainerRef}>
<DocumentsTableComponent style={{ width: "100%", height: "100%" }} items={tableItems} onSelectedItem={onSelectedDocument} height={tableContainerHeightPx} />
</div> </div>
<div style={{ minWidth: "20%" }}><pre>{JSON.stringify(currentDocument, undefined, " ")}</pre></div> <div style={{ minWidth: "20%" }}><pre>{JSON.stringify(currentDocument, undefined, " ")}</pre></div>
</Split> </Split>

View File

@ -1,5 +1,6 @@
import { Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Table, TableBody, TableCell, TableCellLayout, TableColumnDefinition, TableColumnSizingOptions, TableHeader, TableHeaderCell, TableRow, TableRowId, TableSelectionCell, createTableColumn, useArrowNavigationGroup, useTableColumnSizing_unstable, useTableFeatures, useTableSelection } from '@fluentui/react-components'; 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 React, { useEffect } from 'react';
import { FixedSizeList as List, ListChildComponentProps } from "react-window";
export type DocumentsTableComponentItem = { export type DocumentsTableComponentItem = {
id: string; id: string;
@ -9,6 +10,7 @@ export type DocumentsTableComponentItem = {
export interface IDocumentsTableComponentProps { export interface IDocumentsTableComponentProps {
items: DocumentsTableComponentItem[]; items: DocumentsTableComponentItem[];
onSelectedItem: (index: number) => void; onSelectedItem: (index: number) => void;
height: number;
style?: React.CSSProperties; style?: React.CSSProperties;
} }
@ -43,9 +45,52 @@ const columns: TableColumnDefinition<DocumentsTableComponentItem>[] = [
}), }),
]; ];
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> = ({ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> = ({
items, onSelectedItem, style, items, onSelectedItem, style, height,
}: IDocumentsTableComponentProps) => { }: IDocumentsTableComponentProps) => {
const { targetDocument } = useFluent();
const scrollbarWidth = useScrollbarWidth({ targetDocument });
const [activeItemIndex, setActiveItemIndex] = React.useState<number>(undefined); const [activeItemIndex, setActiveItemIndex] = React.useState<number>(undefined);
const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>({ const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>({
@ -99,7 +144,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
] ]
); );
const rows = getRows((row) => { const rows: TableRowData[] = getRows((row) => {
const selected = isRowSelected(row.rowId); const selected = isRowSelected(row.rowId);
return { return {
...row, ...row,
@ -150,7 +195,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
}; };
return ( return (
<Table {...tableProps}> <Table noNativeElements {...tableProps}>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableSelectionCell <TableSelectionCell
@ -184,35 +229,20 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
</MenuPopover> </MenuPopover>
</Menu> </Menu>
))} ))}
{/** Scrollbar alignment for the header */}
<div role="presentation" style={{ width: scrollbarWidth }} />
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody style={{ height: "100%", flex: 1 }}>
{rows.map(({ item, selected, onClick, onKeyDown, appearance }, index: number) => ( <List
<TableRow height={height - 32}
key={item.id} itemCount={items.length}
// onClick={onClick} itemSize={45}
// onKeyDown={onKeyDown} width="100%"
aria-selected={selected} itemData={rows}
appearance={appearance}
> >
<TableSelectionCell {RenderRow}
checked={selected} </List>
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>
))}
</TableBody> </TableBody>
</Table> </Table>
); );