Material UI v5 selector opening list as horizontal - javascript

I am migrating a codebase from Material-Ui v4 to Material-Ui v5. After doing this, the Select component started to open horizontally instead of vertically as the image below:
This is the code:
import Select from "#mui/material/Select";
import makeStyles from "#mui/styles/makeStyles";
import {
FormControl
} from "#mui/material";
import { styled as styledMui } from "#mui/material/styles";
const PlanSelect = styledMui(Select)({
fieldset: {
legend: {
width: "unset",
},
},
});
export const useStyles = makeStyles((theme) => ({
planSelect: {
float: "right",
width: "350px",
backgroundColor: "white",
marginBottom: 15,
},
}));
...
const CustomComponent = ()=>{
...
return(
...
<FormControl variant="outlined" className={classes.planSelect}>
<PlanSelect
value={productId}
onChange={({ target: { value } }) => setProductId(value)}>
<MenuItem value="standard">Option A</MenuItem>
<MenuItem value="pro">Option B</MenuItem>
</PlanSelect>
</FormControl>
)
Classes:
How can I make the options list from the Select to open vertically as Material-Ui v4 ?

Related

Why is my AppBar MUI styled component not changing style on state change?

I'm trying to change the app bar color upon scrolling. I used a MaterialUI styled feature to create my app bar. I checked the value of the state in the console and it is changing correctly. Unfortunately, the app bar does not react to the state change of my passed prop on the styled component which is supposed to be the trigger for the component background color.
Here is the styled component code:
const AppBar = styled(MUIAppBar)(({ theme, scrollNav }) => ({
backgroundColor: !scrollNav ? '#E6EEF4 !important' : 'red !important',
position: 'fixed',
color: '#232F3D',
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
}))
Here is the trigger for the state change:
const [scrollNav, setScrollNav] = useState(false)
const changeNav = () => {
setScrollNav(window.scrollY >= 96 ? true : false)
}
useEffect(() => {
window.addEventListener('scroll', changeNav)
}, [])
Here is how I pass the state to the styled component:
<AppBar position="fixed" scrollNav={scrollNav}></AppBar>
This example may helpful
Spread the props
Codesandbox link
import { styled } from "#mui/styles";
import { Button, createTheme, ThemeProvider } from "#mui/material";
import { useState } from "react";
const appTheme = createTheme({
palette: {
primary: {
main: "#e56339"
}
}
});
const StyledDiv = styled("div")(({ theme, ...props }) => ({
// background: theme.palette.primary?.main, //theme usage
background: props?.toggled ? "red" : "blue"
}));
export default function App() {
const [toggle, setToggle] = useState(false);
return (
<div>
<ThemeProvider theme={appTheme}>
<StyledDiv toggled={toggle}>Styled div</StyledDiv>
</ThemeProvider>
<div style={{margin:10}}>
<Button variant="contained" onClick={() => setToggle((pre) => !pre)}>toggle</Button>
</div>
</div>
);
}

Material-UI: classes isn't working with external SVG icons?

I have created a Material-UI persistent drawer in which there is a list item component that aims to change the icon color whenever a user clicks on the list item. But my styling is only working with Material-UI icon, not with external SVG.
Here is codesandbox link for the same project to understand it better.
Here is my AppBarDrawer.js parent component that renders my listItem component. Working fine and can be ignored
import React from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import CssBaseline from "#material-ui/core/CssBaseline";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import List from "#material-ui/core/List";
import Typography from "#material-ui/core/Typography";
import Divider from "#material-ui/core/Divider";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
import DrawerList from "./components/DrawerList";
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
appBar: {
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
appBarShift: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
})
},
menuButton: {
marginRight: theme.spacing(2)
},
hide: {
display: "none"
},
drawer: {
width: drawerWidth,
flexShrink: 0
},
drawerPaper: {
width: drawerWidth
},
drawerHeader: {
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-end"
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
marginLeft: -drawerWidth
},
contentShift: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
}),
marginLeft: 0
}
}));
export default function PersistentDrawerLeft() {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Persistent drawer
</Typography>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
<DrawerList />
</List>
</Drawer>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
<Typography paragraph>
Lorem Nulla posuere sollicitudin aliquam ultrices sagittis orci a
</Typography>
</main>
</div>
);
}
The Main file DrawerList.js which is not giving desired out
Here the real issue is my external icons color is not changing to white whenever I click on it however the last icon named ExitToAppOutlined is a Material-UI icon and is working fine on click.
import React, { useState } from "react";
import ListItem from "#material-ui/core/ListItem";
import Link from "#material-ui/core/Link";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import { ExitToAppOutlined } from "#material-ui/icons";
import ListItemText from "#material-ui/core/ListItemText";
import { useStyles } from "./DrawerListStyle";
import Typography from "#material-ui/core/Typography";
import Box from "#material-ui/core/Box";
import { SvgIcon } from "#material-ui/core";
import { ReactComponent as Appointment } from "../../assets/Appointment.svg";
import { ReactComponent as Customers } from "../../assets/manage customers 2.svg";
const itemList = [
{
text: "Book Appointment",
icon: (
<SvgIcon>
{/* external icons as svg */}
<Appointment />
</SvgIcon>
)
},
{
text: "Manage",
icon: (
<SvgIcon>
{/* external icons as svg */}
<Customers />
</SvgIcon>
)
},
{
text: "Logout",
// Material Icons
icon: <ExitToAppOutlined />
}
];
const DrawerList = () => {
const [selectedIndex, setSelectedIndex] = useState(0);
const classes = useStyles();
const ListData = () =>
itemList.map((item, index) => {
const { text, icon } = item;
return (
<ListItem
button
key={text}
component={Link}
selected={index === selectedIndex}
onClick={(e) => handleListItemClick(e, index)}
style={selectedIndex === index ? { backgroundColor: "#6A2CD8" } : {}}
>
<ListItemIcon
className={classes.iconStyle}
style={selectedIndex === index ? { color: "#fff" } : {}}
>
{icon}
<ListItemText>
<Typography
component="div"
className={classes.iconTitle}
style={selectedIndex === index ? { color: "#fff" } : {}}
>
<Box fontWeight={500} fontSize={13.5}>
{text}
</Box>
</Typography>
</ListItemText>
</ListItemIcon>
</ListItem>
);
});
const handleListItemClick = (e, index) => {
setSelectedIndex(index);
};
return (
<div className={classes.root}>
<ListData />
</div>
);
};
export default DrawerList;
DrawerListStyle.js just an stylejs file and can be ignored
import { makeStyles } from "#material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(2)
},
iconStyle: {
margin: theme.spacing(0, 0, 1, 0),
color: "#6A2CD8"
},
iconTitle: {
margin: theme.spacing(0, 0, 0, 1),
color: "#555458"
}
}));
export { useStyles };
Material-UI sets the color of your ListItemIcon when the ListItem is selected, but because your custom svg icons already have the fill attribute set to another color, it overrides the color from MUI. The fix is simple, override the fill attribute again in your custom svg using makeStyles:
const useStyles = makeStyles((theme) => ({
{...}
listItem: {
"&.Mui-selected": {
"& path": {
fill: "white"
}
}
}
}));
<ListItem className={classes.listItem}
Live Demo
you can also use SVGR to build your own icons.
you can follow these steps:
1- install svgr using
npm install --save-dev #svgr/cli
# or use yarn
yarn add --dev #svgr/cli
2- add your icons in public/icons
3- create a file named svgr.config.js and place it in the root of project
module.exports = {
icon: true,
dimensions: false,
expandProps: false,
replaceAttrValues: {
'#000': 'currentColor',
'#292D32': 'currentColor',
'#292d32': 'currentColor',
'#55BB9D': 'currentColor',
'#FFBC50': 'currentColor',
'#A7A7A7': 'currentColor'
}
};
4- add script in package.json
"svg": "svgr --ext=jsx -d src/#share/icons public/icons"
5 - run yarn svg.
once you run above command svgr grab all icons in the public folder and gerenate React component inside src/#share/icons folder based on the config you provided.
and replace every color text of svg into current color.
in this case replace every key in replaceAttrValues to the currentColor.
then in your react component you can simply do this:
import { MyIcon } from '#share/icons';
import { SvgIcon } from '#mui/material';
function MyComponent() {
return <p><SvgIcon component={MyIcon} /> check this icon </p>
};
export default MyComponent;
in this way you can change the color the way you change the text color of p element.

React Emotion Styled Component: Class selector not working

I am creating a Radio button in React using emotion styled-components. Due to the fact we export out components into an online component repo (Bit.dev), I can not use targeted styled-components selectors and have to use classNames. I have built a switch the same way and using a class selector I can change the CSS of another component when the switch (input) is checked. However, when my toggle is changed to "checked" the class selector doesn't work:
Here is my code:
import React from "react";
import PropTypes from "prop-types";
import { Container, Input, Label, Outline, Fill } from "./styles";
const RadioButton = ({ id, ...props }) => {
return (
<Container>
<Input id={id} type="radio" {...props} />
<Label htmlFor={id}>
<Outline className="outline">
<Fill className="fill" />
</Outline>
</Label>
</Container>
);
};
RadioButton.propTypes = {
id: PropTypes.string.isRequired,
};
export default RadioButton;
And styles:
import styled from "#emotion/styled";
export const Container = styled.div(() => ({}));
export const Label = styled.label(() => ({
cursor: "pointer",
}));
export const Outline = styled.span(({ theme }) => ({
border: `3px solid ${theme.colors.Blue}`,
width: "24px",
height: "24px",
borderRadius: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}));
export const Fill = styled.span(({ theme }) => ({
width: "14px",
height: "14px",
margin: "auto",
borderRadius: "50%",
background: "red",
}));
export const Input = styled.input(({ theme }) => ({
opacity: 0,
position: "absolute",
"&: checked + .fill": {
background: theme.colors.Blue,
},
}));
As you can see when the Input is changed to "checked" the background of the fill span should change but it doesn't.
Emotion Styled Components:
https://emotion.sh/docs/styled
I think the issue is come from this selector
&: checked + .fill: {
background: theme.colors.Blue,
},
Because Input and .fill are not in same level.
I've made a simple demo for it, please check
https://codepen.io/doichithhe/pen/bGEQwLJ

Warning: Prop `className` did not match ~ Material UI css arbitrarily breaks on reload

Video reproducing the error/missing css
I know there are already dated versions of this question on stack overflow, like React + Material-UI - Warning: Prop className did not match.
However, when I attempt to google and research people's solutions, there is just no clear answer. Any answers I could find don't match my stack.
My stack:
Node JS
Next JS
Material UI
And from what I could glean from answers to questions like next.js & material-ui - getting them to work is that there is some measure of incompatibility when it comes to Next JS and Material UI.
Code-wise, here is my Appbar component. Initially I was not exporting my useStyles object, but I ended up doing it in a pitiful attempt to follow along with Material UI's express guide to "server rendering". There has to be a fix that doesn't involve changing like every file I have.
import React from 'react';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import IconButton from '#material-ui/core/IconButton';
import Typography from '#material-ui/core/Typography';
import InputBase from '#material-ui/core/InputBase';
import { fade } from '#material-ui/core/styles/colorManipulator';
import { makeStyles } from '#material-ui/core/styles';
import MenuIcon from '#material-ui/icons/Menu';
import SearchIcon from '#material-ui/icons/Search';
import {connectSearchBox} from 'react-instantsearch-dom';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
display: 'none',
[theme.breakpoints.up('sm')]: {
display: 'block',
},
},
search: {
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
},
searchIcon: {
width: theme.spacing(7),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
inputRoot: {
color: 'inherit',
},
inputInput: {
padding: theme.spacing(1, 1, 1, 7),
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: 300,
'&:focus': {
width: 400,
},
},
}
}));
function SearchBox({currentRefinement, refine}){
const classes = useStyles();
return(
<InputBase
type="search"
value={currentRefinement}
onChange={event => refine(event.currentTarget.value)}
placeholder="Search by state, park name, keywords..."
classes = {{
root: classes.inputRoot,
input: classes.inputInput,
}}
/>
)
}
const CustomSearchBox = connectSearchBox(SearchBox);
function SearchAppBar() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static" color="primary">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="Open drawer"
>
<MenuIcon />
</IconButton>
<Typography className={classes.title} variant="h6" noWrap>
Title
</Typography>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<CustomSearchBox/>
</div>
</Toolbar>
</AppBar>
</div>
);
}
export {SearchAppBar, useStyles};
I was just digging around random parts of the internet looking for answers to this error, accidentally npm install'ed styled-components as part of this answer on a Github issue (because they have a very similar object to the counterpart in Material UI called ServerStyleSheet (vs Material UI's ServerStyleSheets), so obviously that didn't work.
BUT......... I ended up just using the ServerStyleSheet fix to try to make it agreeable with Material UI's ServerStyleSheets object, and ended up with this new _document.js.
I'm still dumbfounded I was able to refactor an entirely different fix to make this work but I tested it and it fixes the problem entirely, now reloads are fine.
import Document, { Html, Head, Main, NextScript } from 'next/document';
import {ServerStyleSheets} from "#material-ui/styles";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
try{
ctx.renderPage = () => originalRenderPage({
enhanceApp: App => props => sheet.collect(<App {...props}/>)
});
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
}
} finally {
ctx.renderPage(sheet)
}
}
render() {
return (
<Html>
<Head>
<link rel="shortcut icon" type="image/png" href="../static/favicon.ico"/>
<style>{`body { margin: 0 } /* custom! */`}</style>
<meta name="viewport"content="width=device-width, initial-scale=1.0" />
</Head>
<body className="custom_class">
<Main />
<NextScript />
</body>
</Html>
)}
}
export default MyDocument;
If you wanna see how crazy it was that it worked, here is the fix for the same error in styled-components:
export default MyDocument;
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps (ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
}
} finally {
sheet.seal()
}
}
}
I hope this helped someone with the mess that is Material-UI + Next.js
For my part, adding { name: "MuiExample_Component" } in the makeStyle hook works for some reason. I found this solution while digging on internet. I would appreciate if someone could tell me if it's a good solution or not, but here is the code :
const useStyles = makeStyles({
card: {
backgroundColor: "#f7f7f7",
width: "33%",
},
title: {
color: "#0ab5db",
fontWeight: "bold",
},
description: {
fontSize: "1em"
}
}, { name: "MuiExample_Component" });

material ui select not working when a list of data to be rendered

I am trying to display the select in material ui where I need to select one item of all the values we can select .
Here is the code .
I have datatoloop and I need to iterate over it and select the subjects and get their id in console .I could not figure out as what I did wrong or does material ui accepts only the format it is shown in docs page .
Here is the docs page
docs link
Here is the link to sandbox where I tried editing the code
sandbox link
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Input from "#material-ui/core/Input";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormHelperText from "#material-ui/core/FormHelperText";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
const styles = theme => ({
root: {
display: "flex",
flexWrap: "wrap"
},
formControl: {
margin: theme.spacing.unit,
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing.unit * 2
}
});
let datatoloop = [
{ id: 100, subject: "math" },
{ id: 101, subject: "physics" },
{ id: 102, subject: "chemistry" }
];
class SimpleSelect extends React.Component {
state = {
age: "",
name: "hai"
};
handleChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const { classes } = this.props;
return (
<form className={classes.root} autoComplete="off">
<FormControl className={classes.formControl}>
<InputLabel htmlFor="age-simple">Age</InputLabel>
<Select
value={this.state.age}
onChange={this.handleChange}
inputProps={{
name: "age",
id: "age-simple"
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{datatoloop.map(item => {
<MenuItem value={item.id}>{item.subject}</MenuItem>;
})}
</Select>
</FormControl>
</form>
);
}
}
SimpleSelect.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(SimpleSelect);
You forgot to return components while mapping them in your react tree.
here is the working codesandbox: MenuItems Rendering
return <MenuItem value={item.id}>{item.subject}</MenuItem>;

Categories