Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/perf-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@fluentui/eslint-plugin": "*"
},
"dependencies": {
"@fluentui/example-data": "^8.4.2",
"@fluentui/react": "^8.95.1",
"@fluentui/scripts": "^1.0.0",
"@microsoft/load-themed-styles": "^1.10.26",
Expand Down
5 changes: 5 additions & 0 deletions apps/perf-test/src/scenarioIterations.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ const scenarioIterations = {
ComboBox: 1000,
Persona: 1000,
ContextualMenu: 1000,
/* List performance is generally more influenced by the size
* of the list rather than the number of lists on a page.
*/
GroupedList: 2,
GroupedListV2: 2,
};

module.exports = scenarioIterations;
2 changes: 2 additions & 0 deletions apps/perf-test/src/scenarioRenderTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const DefaultRenderTypes = ['mount'];

const scenarioRenderTypes = {
ThemeProvider: AllRenderTypes,
GroupedList: AllRenderTypes,
GroupedListV2: AllRenderTypes,
};

module.exports = {
Expand Down
55 changes: 55 additions & 0 deletions apps/perf-test/src/scenarios/GroupedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import { createListItems, createGroups, IExampleItem } from '@fluentui/example-data';
import { GroupedList, Selection, SelectionMode, DetailsRow, IGroup, IColumn } from '@fluentui/react';

const groupCount = 5;
const groupDepth = 5;
const items = createListItems(Math.pow(groupCount, groupDepth + 1));
const groups = createGroups(groupCount, groupDepth, 0, groupCount);

const columns = Object.keys(items[0])
.slice(0, 3)
.map(
(key: string): IColumn => ({
key: key,
name: key,
fieldName: key,
minWidth: 300,
}),
);

const selection = new Selection();
selection.setItems(items);

const onRenderCell = (
nestingDepth?: number,
item?: IExampleItem,
itemIndex?: number,
group?: IGroup,
): React.ReactNode => {
return item && typeof itemIndex === 'number' && itemIndex > -1 ? (
<DetailsRow
columns={columns}
groupNestingDepth={nestingDepth}
item={item}
itemIndex={itemIndex}
selection={selection}
selectionMode={SelectionMode.multiple}
group={group}
/>
) : null;
};

const Scenario = () => {
return (
<GroupedList
items={items}
groups={groups}
onRenderCell={onRenderCell}
selection={selection}
selectionMode={SelectionMode.multiple}
/>
);
};

export default Scenario;
62 changes: 62 additions & 0 deletions apps/perf-test/src/scenarios/GroupedListV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from 'react';
import { createListItems, createGroups, IExampleItem } from '@fluentui/example-data';
import {
GroupedListV2_unstable as GroupedListV2,
Selection,
SelectionMode,
DetailsRow,
IGroup,
IColumn,
} from '@fluentui/react';

const groupCount = 5;
const groupDepth = 5;
const items = createListItems(Math.pow(groupCount, groupDepth + 1));
const groups = createGroups(groupCount, groupDepth, 0, groupCount);

const columns = Object.keys(items[0])
.slice(0, 3)
.map(
(key: string): IColumn => ({
key: key,
name: key,
fieldName: key,
minWidth: 300,
}),
);

const selection = new Selection();
selection.setItems(items);

const onRenderCell = (
nestingDepth?: number,
item?: IExampleItem,
itemIndex?: number,
group?: IGroup,
): React.ReactNode => {
return item && typeof itemIndex === 'number' && itemIndex > -1 ? (
<DetailsRow
columns={columns}
groupNestingDepth={nestingDepth}
item={item}
itemIndex={itemIndex}
selection={selection}
selectionMode={SelectionMode.multiple}
group={group}
/>
) : null;
};

const Scenario = () => {
return (
<GroupedListV2
items={items}
groups={groups}
onRenderCell={onRenderCell}
selection={selection}
selectionMode={SelectionMode.multiple}
/>
);
};

export default Scenario;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: improve groupedlist virtualization",
"packageName": "@fluentui/react",
"email": "seanmonahan@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { IGroup } from '@fluentui/react/lib/GroupedList';
import { GroupedListV2_unstable as GroupedListV2 } from '@fluentui/react/lib/GroupedListV2';
import { IColumn, DetailsRow } from '@fluentui/react/lib/DetailsList';
import { Selection, SelectionMode, SelectionZone } from '@fluentui/react/lib/Selection';
import { Toggle, IToggleStyles } from '@fluentui/react/lib/Toggle';
import { useBoolean, useConst } from '@fluentui/react-hooks';
import { createListItems, createGroups, IExampleItem } from '@fluentui/example-data';

const toggleStyles: Partial<IToggleStyles> = { root: { marginBottom: '20px' } };
const groupCount = 3;
const groupDepth = 3;
const items = createListItems(Math.pow(groupCount, groupDepth + 1));
const columns = Object.keys(items[0])
.slice(0, 3)
.map(
(key: string): IColumn => ({
key: key,
name: key,
fieldName: key,
minWidth: 300,
}),
);

const groups = createGroups(groupCount, groupDepth, 0, groupCount);

export const GroupedListV2BasicExample: React.FunctionComponent = () => {
const [isCompactMode, { toggle: toggleIsCompactMode }] = useBoolean(false);
const selection = useConst(() => {
const s = new Selection();
s.setItems(items, true);
return s;
});

const onRenderCell = (
nestingDepth?: number,
item?: IExampleItem,
itemIndex?: number,
group?: IGroup,
): React.ReactNode => {
return item && typeof itemIndex === 'number' && itemIndex > -1 ? (
<DetailsRow
columns={columns}
groupNestingDepth={nestingDepth}
item={item}
itemIndex={itemIndex}
selection={selection}
selectionMode={SelectionMode.multiple}
compact={isCompactMode}
group={group}
/>
) : null;
};

return (
<div>
<Toggle
label="Enable compact mode"
checked={isCompactMode}
onChange={toggleIsCompactMode}
onText="Compact"
offText="Normal"
styles={toggleStyles}
/>
<SelectionZone selection={selection} selectionMode={SelectionMode.multiple}>
<GroupedListV2
items={items}
// eslint-disable-next-line react/jsx-no-bind
onRenderCell={onRenderCell}
selection={selection}
selectionMode={SelectionMode.multiple}
groups={groups}
compact={isCompactMode}
/>
</SelectionZone>
</div>
);
};

// @ts-expect-error Storybook
GroupedListV2BasicExample.storyName = 'V2 Basic';
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from 'react';
import { IGroup, IGroupHeaderProps, IGroupFooterProps } from '@fluentui/react/lib/GroupedList';
import { GroupedListV2_unstable as GroupedListV2 } from '@fluentui/react/lib/GroupedListV2';
import { Link } from '@fluentui/react/lib/Link';
import { createListItems, createGroups, IExampleItem } from '@fluentui/example-data';
import { getTheme, mergeStyleSets, IRawStyle } from '@fluentui/react/lib/Styling';

const theme = getTheme();
const headerAndFooterStyles: IRawStyle = {
minWidth: 300,
minHeight: 40,
lineHeight: 40,
paddingLeft: 16,
};
const classNames = mergeStyleSets({
header: [headerAndFooterStyles, theme.fonts.xLarge],
footer: [headerAndFooterStyles, theme.fonts.large],
name: {
display: 'inline-block',
overflow: 'hidden',
height: 24,
cursor: 'default',
padding: 8,
boxSizing: 'border-box',
verticalAlign: 'top',
background: 'none',
backgroundColor: 'transparent',
border: 'none',
paddingLeft: 32,
},
});

const onRenderHeader = (props?: IGroupHeaderProps): JSX.Element | null => {
if (props) {
const toggleCollapse = (): void => {
props.onToggleCollapse!(props.group!);
};
return (
<div className={classNames.header}>
This is a custom header for {props.group!.name}
&nbsp; (
<Link
// eslint-disable-next-line react/jsx-no-bind
onClick={toggleCollapse}
>
{props.group!.isCollapsed ? 'Expand' : 'Collapse'}
</Link>
)
</div>
);
}

return null;
};

const onRenderCell = (nestingDepth?: number, item?: IExampleItem, itemIndex?: number): React.ReactNode => {
return item ? (
<div role="row" data-selection-index={itemIndex}>
<span role="cell" className={classNames.name}>
{item.name}
</span>
</div>
) : null;
};

const onRenderFooter = (props?: IGroupFooterProps): JSX.Element | null => {
return props ? <div className={classNames.footer}>This is a custom footer for {props.group!.name}</div> : null;
};

const groupedListProps = {
onRenderHeader,
onRenderFooter,
};
const items: IExampleItem[] = createListItems(20);
const groups: IGroup[] = createGroups(4, 0, 0, 5);

export const GroupedListV2CustomExample: React.FunctionComponent = () => (
<GroupedListV2 items={items} onRenderCell={onRenderCell} groupProps={groupedListProps} groups={groups} />
);

// @ts-expect-error Storybook
GroupedListV2CustomExample.storyName = 'V2 Custom';
Loading