How to integrate react-perfect-scrollbar with react-select - reactjs

I want to apply style to scrollbar, scrollbar style works perfectly in chrome using css. but does not work in Firefox and Iexplore.
Hence I opted to perfect-scroll-bar, But scrollbar does not move as expected if we navigate options using arrow keys, scroll position does not change.
Below is the demo link:
https://codesandbox.io/s/18pvjy0olj
Thanks in advance!

Basically you need to use MenuList component and wrap children with perfect scroll bar :
function MenuList(props) {
return (
<div className={props.selectProps.classes.menuList}>
<PerfectScrollbar>{props.children}</PerfectScrollbar>
</div>
);
}
Also, don't forget to setup a height property to parent container :
menuList: {
height:300
}
And update components object
const components = {
Control,
Menu,
MenuList, // here
MultiValue,
NoOptionsMessage,
Option,
Placeholder,
SingleValue,
ValueContainer
};
I made an update on your example : https://codesandbox.io/s/n5pmxp57n0

You need to set Scrollbar into MenuProps something like...
import { InputLabel, MenuItem, FormControl as MuiFormControl, Select as MuiSelect } from "#material-ui/core";
import PerfectScrollbar from "react-perfect-scrollbar";
import "../vendor/perfect-scrollbar.css";
import { spacing } from "#material-ui/system";
const Scrollbar = styled(PerfectScrollbar)`
height: 300px;
`;
const FormControlSpacing = styled(MuiFormControl)(spacing);
const FormControl = styled(FormControlSpacing)`
min-width: 200px;
`;
const Select = styled(MuiSelect)(spacing);
const MenuProps = {
MenuListProps: {
component: Scrollbar,
},
};
return (
<React.Fragment>
<FormControl variant="outlined" m={3}>
<InputLabel id="select-label" shrink>SELECT LIST</InputLabel>
<Select
...
MenuProps={MenuProps}
>
{selectList.map((item) => (
<MenuItem key={item.value} value={item.value}>{item.text}</MenuItem>
))}
</Select>
</FormControl
</React.Fragment>
)

Just an improvement to #Alexandre answer for automatic height when scrolling is not needed.
Example: when the users start typing in select and options are reduced to one or two only
Instead of 7 for comparing props.children.length, just use the total number of options that are visible when the scrollbar is visible.
function MenuList(props) {
return (
<div style={{height: props.children.length >= 7 ? 300 : "unset"}}>
<PerfectScrollbar>{props.children}</PerfectScrollbar>
</div>
);
}

Related

Is there a Material-UI component to search/select tags like in stackoverflow?

I am looking for something like this to implement in ReactJS/Material-UI. Any existing component or library for this?
You can do that with the Autocomplete component by overriding the ListboxProps of the Listbox which is the container of the dropdown and set the CSS grid to display 3 equal width columns using the fr unit:
<Autocomplete
ListboxProps={{
style: {
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
maxHeight: "initial",
}
}}
Now assuming the option has the following type:
type TagOption = {
tag: string;
views: number;
description: string;
};
In the Stackoverflow dropdown, there are only 6 options at max, so we need to restrict the number of options shown in the Listbox:
import Autocomplete, { createFilterOptions } from "#mui/material/Autocomplete";
const _filterOptions = createFilterOptions<TagOption>();
const filterOption = (props, state) => {
const results = _filterOptions(props, state);
return results.slice(0, 6);
};
<Autocomplete filterOptions={filterOption}
We also want to create a customized option component for each grid item. Here is a minimal example without any much styles to get you started:
function OptionItem({ option, ...other }) {
return (
<li
{...other}
style={{
display: "block"
}}
>
<div>
<Chip label={option.tag} />
{option.views}
</div>
<div>{option.description}</div>
</li>
);
}
renderOption={(props, option, { selected }) => (
<OptionItem {...props} option={option} />
)}
And finally set the isOptionEqualToValue and getOptionLabel to ensure that the option get filtered properly and the input display the correct tag when selected:
isOptionEqualToValue={(option, value) => option.tag === value.tag}
getOptionLabel={(option) => option.tag}
Live Demo (Source)
That's not a perfect fit, but you can use autocomplete (using multiple, customizable and asynchronous)

DetailsList - Office UI Fabric - How to highlight a row on a button click of the row?

I am using DetailsList of Office UI Frabric: https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist
I have a button on each row which opens a div. I want to highlight the row for which the div is opened.
I tired the solution below but it doesnt work in React :
How to conditionally change a color of a row in detailslist?
I found the solution to this:
const onRenderColumnListRow: IDetailsListProps['onRenderRow'] = (props) => {
const customStyles: Partial<IDetailsRowStyles> = {};
if (props) {
customStyles.root = { backgroundColor: '#f2f8ff', color: '#171717' };
return <DetailsRow {...props} styles={customStyles} />;
}
return null;
};
Call this method as below:
<DetailsList
items={displayedItems}
columns={detailsListColumns}
selectionMode={SelectionMode.none}
getKey={getKey}
layoutMode={DetailsListLayoutMode.justified}
styles={styles}
onRenderRow={onRenderRowStyle}
/>

React Select - Multi Select custom way to display multiple options

I am looking to customize the multiselect and the way we create the display of showing selected options.
Right now, with many options selected the select component takes up a prohibitive amount of space for certain UIs. See example:
I'd like to utilize the out of the box chip display for selected options within the input, but I only want to show only a few selected options (like 3/4 max) and then add a "badge" count for the number of selected options that aren't shown in the value container in the input. The options that are selected but are past the max number of chips allowed to show in the input should show as selected within the dropdown list, while the chips that do show's values should not show in our dropdown.
I've implemented part of this with using a custom ValueContainer to show only the first few chip selections, and then adding a count of additional/"overflow" selections. I'm unsure of how I can utilize the prop hideSelectedOptions to achieve this to show selected items in the list only when my max is met without showing all of them since this prop takes a boolean.
Here's what I have so far: https://codesandbox.io/s/custom-react-select-sjtib
import React, { Component } from "react";
import Select, { components } from "react-select";
import { colourOptions } from "./docs/data";
import "./example.css";
class CustomSelect extends Component {
state = {
values: []
};
handleChange = values => {
this.setState({ values });
};
render() {
const { values } = this.state;
return (
<div>
<Select
hideSelectedOptions={values.length < 3 ? true : false}
isMulti
options={colourOptions}
onChange={this.handleChange}
value={values}
components={{ ValueContainer }}
/>
</div>
);
}
}
export default CustomSelect;
const ValueContainer = ({ children, getValue, ...props }) => {
let maxToShow = 3;
var length = getValue().length;
let displayChips = React.Children.toArray(children).slice(0, maxToShow);
let shouldBadgeShow = length > maxToShow;
let displayLength = length - maxToShow;
return (
<components.ValueContainer {...props}>
{!props.selectProps.inputValue && displayChips}
<div className="root">
{shouldBadgeShow &&
`+ ${displayLength} item${length != 1 ? "s" : ""} selected`}
</div>
</components.ValueContainer>
);
};
I would personally keep hideSelectedOptions={false} and go for styles property usage (options property to be more exact) and setting display: 'none' for the ones which shouldn't be visible:
const styles = {
option: (base, value) => {
return (shouldBeShown(value) ? { ...base } : { display: 'none'});
}
};
shouldBeShown(value) is a custom function for checking if the particular option should be shown.
In order to get option data you can use value.data.
Then you can set styles={styles} in Select component:
<Select
hideSelectedOptions={false}
isMulti
styles={styles}
onChange={this.handleChange}
options={options}
value={values}
components={{ ValueContainer }}
/>

Decrease the height of a row in a datagrid

How can apply style to decrease a data-grid row height which has a checkbox in the first column in react-admin?
I'm trying to change rowstyle property for data-grid, but by default checkbox is showed and not for apart component.
Thanks.
App screenshot
Showing checkbox can be turned off: <List bulkActionButtons={false} ... >
import { withStyles, createStyles } from '#material-ui/core/styles'
const styles = theme => createStyles({
row: {
height: '35px',
}
})
const MyList = withStyles(styles)(({ classes, ...props }) => (
<List bulkActionButtons={false} {...props} >
<Datagrid classes={classes} >
...
</Datagrid>
</List>
))

Dropdown menu flip position issue. React-Select + Material-UI Popper

I use the example autocomplete field from the Material-UI lib documentation. (https://material-ui.com/demos/autocomplete/#react-select)
There is a problem with fliping the menu when it opens at the bottom of the page or the browser's viewport.
Is there a way to fix this problem with Material-UI and react-select?
Or do I need to write something custom?
If you are not using a <Menu/> custom component, you can use the menuPlacement="auto" prop of <Select/>, then your problem is solved.
const components = {
Control,
// Menu , <-- delete it
NoOptionsMessage,
Option,
Placeholder,
SingleValue,
ValueContainer
};
https://github.com/JedWatson/react-select/issues/403
Otherwise you can choose another selector, material-ui provides 2 more differents integration with the <Popper/> component: react-autosuggest and downshift.
https://material-ui.com/demos/autocomplete/
Hope it helps!
i've faced the same problem, for <Select /> component i have used what TomLgls suggest, but for <AsyncSelect /> as a work-around, i used some offset calculations in my component :
const rootHeight = document.getElementById('root').offsetHeight ;
const selectElement = document.getElementById('async_select_container_id');
const selectOffsetBottom= selectElement.offsetHeight + selectElement.offsetTop;
...
<AsyncSelect
{...listProps}
menuPlacement={
rootHeight - selectOffsetBottom > 210 ? 'auto' : 'top' // react-select menu height is 200px in my case
}
/>
i hope it helps as well
If you have created customMenu component then in that give className as open-menu-top and write this code for class:
.menu-open-top {
top: auto;
bottom: 100%;
}
Your CustomMenu maybe look like this:
const CustomMenu = ({ children, innerProps, innerRef, selectProps }) => {
return (
<div
ref={innerRef}
{...innerProps}
className={`rs-menu ${customMenuClass} open-menu-top`}
>
{Your Logic}
</div>
);
};

Resources