import React from "react";
import "./styles.css";
import Input from "#material-ui/core/Input";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
const names = [
"Oliver Hansen",
"Van Henry",
"April Tucker",
"Ralph Hubbard",
"Omar Alexander",
"Carlos Abbott",
"Miriam Wagner",
"Bradley Wilkerson",
"Virginia Andrews",
"Kelly Snyder"
];
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
},
disableScrollLock: true
};
export default function App() {
const [personName, setPersonName] = React.useState([]);
const handleChange = event => {
setPersonName(event.target.value);
};
return (
<div className="App" style={{ height: "1000px" }}>
<FormControl>
<Select
labelId="demo-mutiple-name-label"
id="demo-mutiple-name"
multiple
value={personName}
onChange={handleChange}
input={<Input />}
MenuProps={MenuProps}
>
{names.map(name => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
The codes can be found here as well https://codesandbox.io/s/awesome-leaf-ooko1
I am using React 16, latest Material UI core, Material UI Select Component
What I am trying to do here is when I open the drop down menu (with disableScrollLock=true), when I scroll the window, the drop down menu will be relative to the anchor element not fixed on window. How can I achieve that?
I'm not acquainted with React but I've managed to find a working example with a different component.
See this answer: https://stackoverflow.com/a/54011607/152016
Coder used a ReportComboBox instead of a Select, but he tackled another problem of your snippet: growing selection size.
When in your snippet you select a lot of items, there is a UI problem.
Enough digressing, I've changed the answers' snippet to enable scrolling (by setting body { height: 3000px; } for instance, and you can see that the scrolling keeps the select box in its place: https://codesandbox.io/s/react-select-ellipsis-one-row-example-k62hy
Hope this is enough for solving the problem or at least a clue to it.
Related
Hi everyone I have the following code.
I am trying to do following.
When the user selects one of the items from the dropdown list, it appears in select with a close tag.
So I need somehow customize and add besides that close icon also edit button. How can I achieve that?
import React from "react";
import "antd/dist/antd.css";
import { Select } from "and";
const { Option } = Select;
const arr = ["first value", "second value", "third value"];
const App = () => {
return (
<>
<Select
mode="tags"
size={"large"}
placeholder="Please select"
style={{
width: "100%"
}}
>
{arr?.map((el) => (
<Option key={el} value={el}>
{el}
</Option>
))}
</Select>
</>
);
};
export default App;
P.S. unfortunately I am using antd version 3.x.xand it does not support tagRender prop. And there is no way to upgrade to the latest version.
Please help me to resolve this problem.
As a workaround solution for antd version 3.3.0; You can add a custom icon to all the options and hide these icons when they are in the select dropdown like this:
import React from "react";
import "antd/dist/antd.css";
import { Select } from "antd";
import { EditOutlined } from "#ant-design/icons";
import "./demo.css";
const { Option } = Select;
const arr = ["first value", "second value", "third value"];
const App = () => {
const handleEditClicked = (e, el) => {
e.stopPropagation();
e.preventDefault();
console.log(el);
};
return (
<>
<Select
mode="tags"
size={"large"}
placeholder="Please select"
style={{
width: "100%"
}}
>
{arr?.map((el) => (
<Option key={el} value={el}>
<EditOutlined
className="customEditIcon"
onClick={(e) => handleEditClicked(e, el)}
/>
{el}
</Option>
))}
</Select>
</>
);
};
export default App;
And this is the related css to hide icons on dropdown:
.customEditIcon.anticon-edit:before {
display: none;
}
.ant-select-dropdown .customEditIcon {
display: none;
}
You can take a look at this forked sandbox for a live working example of this workaround solution.
My app is a dashboard of MUI <Card />s that can be dragged-and-dropped (d&d) to reorder them. The d&d logic is implemented using react-dnd and has been working well so far.
However, when I add a <DataGridPro /> as the child of a draggable <Card />, the datagrid's native Column ordering - which also is done by dragging-and-dropping - breaks. Dragging a column once or twice generates the following crash:
Invariant Violation
Cannot call hover while not dragging.
▼ 5 stack frames were expanded.
at invariant (https://bfz133.csb.app/node_modules/
react-dnd/invariant/dist/index.js:19:15
checkInvariants
https://bfz133.csb.app/node_modules/dnd-core/dist/actions/dragDrop/hover.js:33:40
DragDropManagerImpl.hover
https://bfz133.csb.app/node_modules/dnd-core/dist/actions/dragDrop/hover.js:18:5
Object.eval [as hover]
https://bfz133.csb.app/node_modules/dnd-core/dist/classes/DragDropManagerImpl.js:25:38
HTML5BackendImpl.handleTopDrop
https://bfz133.csb.app/node_modules/react-dnd-html5-backend/dist/HTML5BackendImpl.js:455:20
▲ 5 stack frames were expanded.
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error.
This error overlay is powered by `react-error-overlay` used in `create-react-app`.
You can begin dragging, the crash only happens when you let go of the mouse button.
The expected behavior is that I should be able to d&d the columns, to change their order, without issues.
Things I've tried
Removing the <DataGridPro /> and replacing that <Card /> with a text-type Card (see the code in the sandbox below) shows that d&d logic works fine with no crashes;
Disabling my app's d&d by commenting out all the relevant code causes the <DataGridPro />'s colum reordering to work as expected;
The above suggests the root cause lies in having both D&Ds work without causing an internal conflict in react-dnd, which led to me trying:
Browsing the documentation to find a way to instruct the component to use my own DndProvider or DndManager, but I couldn't find that in the API - sorry if I misread it!
Googling for the error message "Cannot call hover while not dragging", while limiting myself to contexts including the MUI library or react-dnd, yielded limited results. I found a Chrome bug that was fixed on v. 77.0.3865.120, my Chrome version is 101.0.4951.64 .
EDIT: Found this bug, but it's closed. Should I open a new one? I'd like some input on this, as I wouldn't like to bother the developers if the problem is in my code.
Minimum verified reproducible example
I made a sandbox! Click here to see it
Datagrid Component:
import React from "react";
import { DataGridPro } from "#mui/x-data-grid-pro";
import { useDemoData } from "#mui/x-data-grid-generator";
export function MyDatagridPro() {
const { data } = useDemoData({
dataSet: "Commodity",
rowLength: 5,
maxColumns: 6
});
return <DataGridPro {...data} />;
}
Card widget:
import React, { useRef } from "react";
import {
Card,
CardHeader,
CardContent,
Grid,
Typography,
Divider
} from "#mui/material";
import { useDrag, useDrop } from "react-dnd";
import { MyDatagridPro } from "./MyDatagridPro";
export function MyContentCard(props) {
const domRef = useRef(null);
const [{ isDragging }, dragBinder, previewBinder] = useDrag(
() => ({
type: "mycard",
item: () => ({
orderIndex: props.orderIndex
}),
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
}),
[props]
);
const [{ handlerId, isOver }, dropBinder] = useDrop(
() => ({
accept: "mycard",
collect: (monitor) => ({
handlerId: monitor.getHandlerId(),
isOver: !!monitor.isOver()
}),
canDrop: (item, monitor) => {
if (!domRef.current) return false;
const draggingOrderIndex = item.orderIndex;
const hoveringOrderIndex = props.orderIndex;
if (draggingOrderIndex === hoveringOrderIndex) return false;
const hoverRectangleBound = domRef.current?.getBoundingClientRect();
const [hoverItemX, hoverItemY] = [
hoverRectangleBound.right - hoverRectangleBound.left,
hoverRectangleBound.bottom - hoverRectangleBound.top
];
const mousePosition = monitor.getClientOffset();
const [hoverMouseX, hoverMouseY] = [
mousePosition.x - hoverRectangleBound.left,
mousePosition.y - hoverRectangleBound.top
];
if (
(hoverMouseX < 0 || hoverMouseX > hoverItemX) &&
(hoverMouseY < 0 || hoverMouseY > hoverItemY)
) {
return false;
}
return true;
},
drop: (item) => {
props.swapper(item.orderIndex, props.orderIndex);
}
}),
[props]
);
return (
<Grid item xs={5}>
<Card
ref={(element) => {
if (element) {
domRef.current = element;
previewBinder(dropBinder(domRef));
}
}}
sx={{
height: `calc(6 * 4.5rem)`,
opacity: isDragging ? 0.3 : 1,
display: "flex",
flexDirection: "column",
border: isOver ? "2px solid rgba(0,0,0,0.5);" : ""
}}
data-handler-id={handlerId}
>
<CardHeader ref={dragBinder} title={props.title} />
<Divider />
<CardContent
sx={{
height: "100%",
display: "flex",
flexDirection: "column"
}}
>
{props.type === "text" && <Typography>{props.content}</Typography>}
{props.type === "datagrid" && <MyDatagridPro />}
</CardContent>
</Card>
</Grid>
);
}
export default MyContentCard;
App.js:
import React, { useState } from "react";
import { Grid } from "#mui/material";
import { createDragDropManager } from "dnd-core";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { MyContentCard } from "./MyContentCard";
export const dndManager = createDragDropManager(HTML5Backend);
export default function App() {
const [cards, setCards] = useState([
{
type: "datagrid",
title: "Card 01 - A MUI DataGridPro",
content: ""
},
{
type: "text",
title: "Card 02 - Some text",
content: "Text that belongs to card 2"
}
]);
function swapCards(indexA, indexB) {
const newState = cards.slice();
const cardA = Object.assign({}, cards[indexA]);
newState[indexA] = Object.assign({}, cards[indexB]);
newState[indexB] = cardA;
setCards(newState);
}
return (
<DndProvider manager={dndManager}>
<Grid
container
spacing={1}
columns={10}
p={2}
pb={3}
mt={0}
mb={0}
// flex="1 1 auto"
overflow="auto"
sx={{
backgroundColor: "lightgray"
}}
>
{cards.map((card, i) => {
return (
<MyContentCard
key={i}
type={card.type}
title={card.title}
content={card.content}
orderIndex={i}
swapper={swapCards}
/>
);
})}
</Grid>
</DndProvider>
);
}
I am using npm to show develop a widget.
I want to use material-ui Ratin component and I have integrate it. But when I place the widget in a webpage, it has a html font-size: 62.5%, so the component is too small because in the icon style, there are a 1em unit in height and in width.
screenshot.
This is my code:
import React from 'react';
import Rating from '#material-ui/lab/Rating';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
import { withStyles } from '#material-ui/core/styles';
import StarOutlineIcon from '#material-ui/icons/StarOutline';
const styles = theme => ({
iconFilled:{
color:theme.palette.primary.main,
},
iconEmpty:{
color:theme.palette.primary.main
}
})
class SimpleRating extends React.Component{
state = {
disabled: false,
rating: 0,
opinion: "",
};
changeRating(event, newRating) {
this.setState({
rating: newRating,
disabled: true
});
this.props.send_rating(newRating)
}
defaultLabelText(value) {
let text="sin calificación"
if (value===1){
text = "una estrella"
}
else if (value>1 && value<=5) {
text = ""+ value + " estrellas"
}
return(text)
}
render() {
const { classes } = this.props;
return (
<div>
<Box component="fieldset" mb={3} borderColor="transparent">
<Typography component="legend"></Typography>
<Rating
classes={{
iconFilled: classes.iconFilled,
iconEmpty: classes.iconEmpty
}}
emptyIcon = {<StarOutlineIcon></StarOutlineIcon>}
name={"rating_"+this.props.number}
disabled={this.state.disabled}
getLabelText={this.defaultLabelText.bind(this)}
onChange={this.changeRating.bind(this)}
value={this.state.rating}
/>
</Box>
</div>
);
}
}
export default withStyles(styles)(SimpleRating);
Althougth I have been able to change the color with the styles, I cannot modify that down.
How can I change that properties of the icon?
EDIT:
If i use the class icon in css i change the parent of the star icons while they continue with 1em x 1em size.
screenshot with changes in icon
To increase the size of the rating icons, we can use font-size. Using height and width will not work as it increases the size of the container they're in.
For example:
<Rating
name="rating"
value={starValue}
precision={0.1}
size="large"
readOnly
sx={{
fontSize: "4rem"
}}
/>
will increase the size of the icon further than just .MuiRating-sizeLarge which is their size if you set size="large". For reference, that size is about 1.8rem.
Have you tried to set the icon class of the rating component to a class which defines a new width and height?
const styles = theme => ({
iconFilled:{
color:theme.palette.primary.main,
},
iconEmpty:{
color:theme.palette.primary.main
},
//just some sample values
icon: {
width: 64,
height: 64
})
and then:
<Rating
classes={{
iconFilled: classes.iconFilled,
iconEmpty: classes.iconEmpty,
icon: classes.icon
}}
emptyIcon = {<StarOutlineIcon></StarOutlineIcon>}
name={"rating_"+this.props.number}
disabled={this.state.disabled}
getLabelText={this.defaultLabelText.bind(this)}
onChange={this.changeRating.bind(this)}
value={this.state.rating}
/>
Finally I solve it with this code because I was no able to enter to the icon itself:
icon = {<StarIcon style={{width:"32px",height:"32px"}}></StarIcon>}
emptyIcon = {<StarOutlineIcon style={{width:"32px",height:"32px"}}></StarOutlineIcon>}
Suppose I have a list of items in the dropdown or Autocomplete in Material-UI, how can one auto-scroll to the desired item in the list when I just tap on the drop down, for example in the list of top100Films, I already have the items, meaning when I open the dropdown, it first shows:
{ label: 'The Shawshank Redemption', year: 1994 }
What I want to achieve is, when I open the dropdown the list of items should start at an Item I want e.g:
{ label: 'Inception', year: 2010 }
Should appear as the first, but the list of the dropdown should auto-scroll to that item.
Below is the entire code:
import * as React from 'react';
import TextField from '#mui/material/TextField';
import Autocomplete from '#mui/material/Autocomplete';
export default function ComboBox() {
return (
<Autocomplete
disablePortal
id="combo-box-demo"
options={top100Films}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
);
}
// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top
const top100Films = [
{ label: 'The Shawshank Redemption', year: 1994 },
...
];
How best can this be achieved?
If you want to scroll to the option, you need to identify the equivalent element on the DOM tree, then call Element.scrollIntoView().
You can do that by attaching a data attribute to each option element using renderOption, then every time the user opens, use document.querySelector() to find the option based on the attribute and scroll to it:
<Autocomplete
onOpen={() => {
setTimeout(() => {
const optionEl = document.querySelector(
`[data-name="${movieToScrollTo}"]`
);
optionEl?.scrollIntoView();
}, 1);
}}
renderOption={(props, option) => {
return (
<li {...props} data-name={option.label}>
{option.label}
</li>
);
}}
/>
Live Demo
For the AntD menu ... we utilise <Menu>, <Menu.Item>, <SubMenu>.
But I don't want to use these for navigation but rather for representation. I want to display the attributes of an object using a dropdown as such.
For eg. Apple -> red, fruit; Cucumber -> green, vegetable; would be displayed as a menu with Apple and Cucumber as the Submenu headings, and the dropdowns for each would be red, fruit and green, vegetable respectively.
But I don't want to predefine attributes and Submenu headings. If it was a component (cards for example), I could've made the component render per object, so that if there were 10 objects, 10 cards (for example) would be rendered.
Is it possible to do the same for <SubMenu> and <Menu.Item> where I send the data and it first looks at the key 'Name' and renders as a Submenu Heading and renders attributes individually as Menu Items within the Submenu?
Are there any alternatives I can make use of?
Not sure If my question is very clear ... I'm happy to clarify anything if confused.
Not sure if this is what you want
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Menu } from "antd";
const { SubMenu } = Menu;
const data = [
{
Name: "Apple",
Colour: "red",
Type: "fruit"
},
{
Colour: "green",
Type: "vegetable",
Name: "Cucumber",
Season: "spring"
},
{
Name: "Book",
Title: "hello",
Author: "nick"
}
];
const Sider = () => {
const [openKeys, setOpenKeys] = React.useState(["sub1"]);
const onOpenChange = (keys) => {
const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1);
if (data.indexOf(latestOpenKey) === -1) {
setOpenKeys(keys);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
}
};
return (
<Menu mode="inline" onOpenChange={onOpenChange} style={{ width: 256 }}>
{data.map((each) => (
<SubMenu key={each.Name} title={each.Name}>
{Object.entries(each).map(
([key, value]) =>
key !== "Name" && (
<Menu.Item key={each.Name + "-" + key}>{value}</Menu.Item>
)
)}
</SubMenu>
))}
</Menu>
);
};
ReactDOM.render(<Sider />, document.getElementById("container"));
CodeSandbox Demo
Let me know if this works for you