Improved the scrolling UX in list widgets

- Fixed scrollbar does not reach end of list widget.
 - Estimated row heights to provide better scroll UX.
 - Last item's `<select>` must be visible.

Closes #1380
Closes #1381
Closes #1387

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2022-09-02 11:21:36 +02:00 committed by Akos Kitta
parent df3a34eec6
commit d0dfc656e6
3 changed files with 38 additions and 34 deletions

View File

@ -44,10 +44,6 @@
height: 100%; /* This has top be 100% down to the `scrollContainer`. */
}
.filterable-list-container .items-container {
padding-bottom: calc(2 * var(--theia-statusBar-height));
}
.filterable-list-container .items-container > div > div:nth-child(odd) {
background-color: var(--theia-sideBar-background);
filter: contrast(105%);

View File

@ -1,3 +1,4 @@
import 'react-virtualized/styles.css';
import * as React from '@theia/core/shared/react';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import {
@ -14,7 +15,11 @@ import { Installable } from '../../../common/protocol/installable';
import { ComponentListItem } from './component-list-item';
import { ListItemRenderer } from './list-item-renderer';
function sameAs<T>(left: T[], right: T[], key: (item: T) => string): boolean {
function sameAs<T>(
left: T[],
right: T[],
...compareProps: (keyof T)[]
): boolean {
if (left === right) {
return true;
}
@ -23,12 +28,14 @@ function sameAs<T>(left: T[], right: T[], key: (item: T) => string): boolean {
return false;
}
for (let i = 0; i < leftLength; i++) {
const leftKey = key(left[i]);
const rightKey = key(right[i]);
if (leftKey !== rightKey) {
for (const prop of compareProps) {
const leftValue = left[i][prop];
const rightValue = right[i][prop];
if (leftValue !== rightValue) {
return false;
}
}
}
return true;
}
@ -43,7 +50,7 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
constructor(props: ComponentList.Props<T>) {
super(props);
this.cache = new CellMeasurerCache({
defaultHeight: 300,
defaultHeight: 140,
fixedWidth: true,
});
}
@ -67,6 +74,11 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
rowHeight={this.cache.rowHeight}
deferredMeasurementCache={this.cache}
ref={this.setListRef}
estimatedRowSize={140}
// If default value, then `react-virtualized` will optimize and list item will not receive a `:hover` event.
// Hence, install and version `<select>` won't be visible even if the mouse cursor is over the `<div>`.
// See https://github.com/bvaughn/react-virtualized/blob/005be24a608add0344284053dae7633be86053b2/source/Grid/Grid.js#L38-L42
scrollingResetTimeInterval={0}
/>
);
}}
@ -77,13 +89,13 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
override componentDidUpdate(prevProps: ComponentList.Props<T>): void {
if (
this.resizeAllFlag ||
!sameAs(this.props.items, prevProps.items, this.props.itemLabel)
!sameAs(this.props.items, prevProps.items, 'name', 'installedVersion')
) {
this.clearAll(true);
}
}
private setListRef = (ref: List | null): void => {
private readonly setListRef = (ref: List | null): void => {
this.list = ref || undefined;
};
@ -98,17 +110,7 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
}
}
private clear(index: number): void {
this.cache.clear(index, 0);
this.list?.recomputeRowHeights(index);
// Update the last item if the if the one before was updated
if (index === this.props.items.length - 2) {
this.cache.clear(index + 1, 0);
this.list?.recomputeRowHeights(index + 1);
}
}
private createItem: ListRowRenderer = ({
private readonly createItem: ListRowRenderer = ({
index,
parent,
key,
@ -123,16 +125,20 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<
rowIndex={index}
parent={parent}
>
<div style={style}>
{({ measure, registerChild }) => (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<div ref={registerChild} style={style}>
<ComponentListItem<T>
key={this.props.itemLabel(item)}
item={item}
itemRenderer={this.props.itemRenderer}
install={this.props.install}
uninstall={this.props.uninstall}
onFocusDidChange={() => this.clear(index)}
onFocusDidChange={() => measure()}
/>
</div>
)}
</CellMeasurer>
);
};

View File

@ -51,8 +51,10 @@ export class FilterableListContainer<
<div className={'filterable-list-container'}>
{this.renderSearchBar()}
{this.renderSearchFilter()}
<div className="filterable-list-container">
{this.renderComponentList()}
</div>
</div>
);
}