Skip to content

Commit

Permalink
Fix virtualization (windowing) when displaying options with long titl…
Browse files Browse the repository at this point in the history
…es for select widgets (#3661)

* WIP

* First working implementation

* Add react-virtualized as a Loadable

* Changelog

* Improve components, add CSS for breaking long words

* Remove useDimensions hook, since we are not using in this one.

* Increase a bit the maxSize for bundle watch

* Remove  as a dependency
  • Loading branch information
sneridagh committed Sep 21, 2022
1 parent c65e939 commit fd96555
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 34 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Breaking

- `react-window` no longer a Volto dependency @sneridagh
See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more information.

### Feature

### Bugfix
Expand All @@ -12,6 +15,8 @@
- Fix selection error when pressing backspace @robgietema
- Fix sidebarTab in Toc Block @iRohitSingh

- Fix virtualization (windowing) when displaying options with long titles for select widgets. (The virtualization happen when the number of options is greater than 25). Add dynamic height aware options using `react-virtualized`. @sneridagh

### Internal

### Documentation
Expand Down
8 changes: 7 additions & 1 deletion docs/source/upgrade-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ config.registerComponent({
```
````

#### Main workflow change menu changed from Pastanaga UI simplification to classic Plone implementation
### Main workflow change menu changed from Pastanaga UI simplification to classic Plone implementation

Pastanaga UI envisioned a simplification of the classic Plone workflow change dropdown.
The idea is that for users, the transition names were too cryptic and it was difficult to infer the destination state from them.
Expand Down Expand Up @@ -284,6 +284,12 @@ defineMessages({
})
```

### `react-window` no longer a Volto dependency

Volto used this library to generate dynamic "windowed/virtualized" select widget options.
It moved to use `react-virtualized` instead of `react-window` because it provides a more broad set of features that Volto required.
If you were using it in your project, you'll have to include it as a direct dependency of it from now on.

(volto-upgrade-guide-15.x.x)=

## Upgrading to Volto 15.x.x
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"files": [
{
"path": "build/public/static/js/*.js",
"maxSize": "680kB"
"maxSize": "700kB"
}
]
},
Expand Down Expand Up @@ -357,7 +357,7 @@
"react-sortable-hoc": "2.0.0",
"react-test-renderer": "17.0.2",
"react-toastify": "5.4.1",
"react-window": "1.8.6",
"react-virtualized": "9.22.3",
"redraft": "0.10.2",
"redux": "4.1.0",
"redux-actions": "2.6.5",
Expand Down
56 changes: 56 additions & 0 deletions src/components/manage/ReactVirtualized/DynamicRowHeightList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';

class DynamicHeightList extends React.PureComponent {
// As per `react-virtualized` docs, one should use a PureComponent
// to avoid performance issues
// We could have used a React.memo + functional component instead,
// but relied on the class-based implementation for simplicity
constructor(props) {
super(props);

this._cache = new props.reactVirtualized.CellMeasurerCache({
fixedWidth: true,
minHeight: 50,
});

this._rowRenderer = this._rowRenderer.bind(this);
}

_rowRenderer({ index, key, parent, style }) {
const CellMeasurer = this.props.reactVirtualized.CellMeasurer;
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
rowIndex={index}
parent={parent}
>
{({ measure }) => <div style={style}>{this.props.children[index]}</div>}
</CellMeasurer>
);
}

render() {
const List = this.props.reactVirtualized.List;

return (
<List
{...this.props}
ref={(element) => {
this._list = element;
}}
deferredMeasurementCache={this._cache}
overscanRowCount={0}
rowCount={this.props.children.length}
rowHeight={this._cache.rowHeight}
rowRenderer={this._rowRenderer}
width={200}
height={500}
/>
);
}
}

export default injectLazyLibs('reactVirtualized')(DynamicHeightList);
33 changes: 10 additions & 23 deletions src/components/manage/Widgets/SelectStyling.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,17 @@
import React from 'react';
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
import { Icon } from '@plone/volto/components';
import DynamicHeightList from '@plone/volto/components/manage/ReactVirtualized/DynamicRowHeightList';

import downSVG from '@plone/volto/icons/down-key.svg';
import upSVG from '@plone/volto/icons/up-key.svg';
import checkSVG from '@plone/volto/icons/check.svg';
import checkBlankSVG from '@plone/volto/icons/check-blank.svg';
import clearSVG from '@plone/volto/icons/clear.svg';

const height = 50; // The height of each option

export const MenuList = injectLazyLibs('reactWindow')((props) => {
const { FixedSizeList: List } = props.reactWindow;
const { options, children, maxHeight, getValue } = props;
const [value] = getValue();
const initialOffset = options.indexOf(value) * height;

return (
<List
height={maxHeight}
itemCount={children.length}
itemSize={height}
initialScrollOffset={initialOffset}
>
{({ index, style }) => <div style={style}>{children[index]}</div>}
</List>
);
});
export const MenuList = ({ children }) => {
return <DynamicHeightList>{children}</DynamicHeightList>;
};

export const SortableMultiValue = injectLazyLibs([
'reactSelect',
Expand Down Expand Up @@ -58,13 +44,14 @@ export const SortableMultiValueLabel = injectLazyLibs([

export const Option = injectLazyLibs('reactSelect')((props) => {
const { Option } = props.reactSelect.components;
const color = props.isFocused && !props.isSelected ? '#b8c6c8' : '#007bc1';
const svgIcon =
props.isFocused || props.isSelected ? checkSVG : checkBlankSVG;

return (
<Option {...props}>
<div>{props.label}</div>
{props.isFocused && !props.isSelected && (
<Icon name={checkSVG} size="24px" color="#b8c6c8" />
)}
{props.isSelected && <Icon name={checkSVG} size="24px" color="#007bc1" />}
<Icon name={svgIcon} size="20px" color={color} />
</Option>
);
});
Expand Down
4 changes: 3 additions & 1 deletion src/config/Loadables.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export const loadables = {
prismCore: loadable.lib(() => import('prismjs/components/prism-core')),
toastify: loadable.lib(() => import('react-toastify')),
reactSelect: loadable.lib(() => import('react-select'), { ssr: false }),
reactWindow: loadable.lib(() => import('react-window'), { ssr: false }),
reactVirtualized: loadable.lib(() => import('react-virtualized'), {
ssr: false,
}),
reactSortableHOC: loadable.lib(() => import('react-sortable-hoc'), {
ssr: false,
}),
Expand Down
6 changes: 6 additions & 0 deletions src/icons/check-blank.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions theme/themes/pastanaga/extras/widgets.less
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,8 @@ body.babel-view .field.language-independent-field {
opacity: 0.3 !important;
}
}

// makes sure that long words (eg. German) get properly broken in selects.
.react-select__option {
.word-break();
}
33 changes: 26 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,13 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.8.7":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.14.5", "@babel/template@^7.16.7", "@babel/template@^7.3.3", "@babel/template@^7.4.0":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
Expand Down Expand Up @@ -7279,6 +7286,14 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.6.3"
csstype "^2.6.7"

dom-helpers@^5.1.3:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"

dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
Expand Down Expand Up @@ -13032,7 +13047,7 @@ memfs@^3.1.2:
dependencies:
fs-monkey "1.0.3"

"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0, memoize-one@^5.1.1:
memoize-one@^5.0.0, memoize-one@^5.1.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
Expand Down Expand Up @@ -16367,13 +16382,17 @@ react-transition-group@^4, react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"

react-window@1.8.6:
version "1.8.6"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112"
integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==
react-virtualized@9.22.3:
version "9.22.3"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==
dependencies:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
"@babel/runtime" "^7.7.2"
clsx "^1.0.4"
dom-helpers "^5.1.3"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"

react-with-direction@^1.3.1:
version "1.3.1"
Expand Down

0 comments on commit fd96555

Please sign in to comment.