Change style of children in MaterialUI only in one element - reactjs

I am using <ListItemIcon/> from Material-UI. In one component I use two different Icons and I want them to be different sizes and other styles in general. <ListItemIcon/> is build with svg which has class name MuiSvgIcon-root - this is where I should change the fontSize of Icon. I dont know how to do it. When I do
const useStyles = makeStyles({
root:{
fontSize: "2rem"
}
},{name: 'MuiSvgIcon'});
It changes setting for every Icon size in my project.
My component
function Section(props) {
const classes = useStyles();
const { title, listInfo, icon, gridSize } = props;
return (
<List>
<ListItem>
<ListItemIcon >{icon}</ListItemIcon> //here icon bigger
<ListItemText primary={title} />
</ListItem>
<Divider variant="middle" />
<Grid container>
{listInfo.map((item, index) => {
return (
<Grid item xs={gridSize}>
<ListItem key={index}>
<ListItemIcon>
<Brightness1Icon /> //here icon smaller
</ListItemIcon>
<ListItemText
primary={item.primaryText}
secondary={item.secondaryText}
/>
</ListItem>
</Grid>
);
})}
</Grid>
</List>
);
}

By specifying name: 'MuiSvgIcon' in your makeStyles call, you are causing Material-UI to override the global MuiSvgIcon-root class. If you use the name option in makeStyles, you should never give it a Mui name since these are treated differently and intended for use within the library code.
Below is one way to customize the icon size:
const BiggerListItemIcon = withStyles({
root: {
"& .MuiSvgIcon-root": { fontSize: "2em" }
}
})(ListItemIcon);
This can then be used as in the following example:
import React from "react";
import { makeStyles, withStyles } from "#material-ui/core/styles";
import List from "#material-ui/core/List";
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/Inbox";
import DraftsIcon from "#material-ui/icons/Drafts";
const useStyles = makeStyles(theme => ({
root: {
width: "100%",
maxWidth: 360,
backgroundColor: theme.palette.background.paper
}
}));
const BiggerListItemIcon = withStyles({
root: {
"& .MuiSvgIcon-root": { fontSize: "2em" }
}
})(ListItemIcon);
export default function SimpleList() {
const classes = useStyles();
return (
<div className={classes.root}>
<List component="nav" aria-label="main mailbox folders">
<ListItem button>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItem>
<ListItem button>
<BiggerListItemIcon>
<DraftsIcon />
</BiggerListItemIcon>
<ListItemText primary="Drafts" />
</ListItem>
</List>
</div>
);
}

There are many ways to do styling with material-ui, I won't say this is the best way, but personally it is my own favorite way
import { styled } from "#material-ui";
const Section = () => {
return (
...
<SmallerListItemIcon>
<Brightness1Icon />
</SmallerListItemIcon>
...
)
}
const SmallerListItemIcon = styled(ListItemIcon)({
fontSize: "<your size here>"
});

Related

Share values and events across different components in React

I am trying to build a dashboard app with basic AppBar and a drawer based on this Demo
https://codesandbox.io/s/nj3u0q?file=/demo.tsx
But in this Demo, AppBar and Drawer and the Main content are all in a single page.
But I made it as separate components and did the layout like this
RootContainer.tsx
import * as React from 'react';
import { styled, useTheme } from '#mui/material/styles';
import Box from '#mui/material/Box';
import CssBaseline from '#mui/material/CssBaseline';
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '#mui/material/AppBar';
import Drawer from './Drawer';
import AppBar from './AppBar';
import Main from './MainContent';
export default function PersistentDrawerLeft() {
const [open, setOpen] = React.useState(false);
return (
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar />
<Drawer />
<Main />
</Box>
);
}
Appbar.tsx
import * as React from 'react';
import { styled, useTheme } from '#mui/material/styles';
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import Typography from '#mui/material/Typography';
import IconButton from '#mui/material/IconButton';
import MenuIcon from '#mui/icons-material/Menu';
const drawerWidth = 240;
interface AppBarProps extends MuiAppBarProps {
open?: boolean;
}
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})<AppBarProps>(({ theme, open }) => ({
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: `${drawerWidth}px`,
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
export default function AppBar() {
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
return (
<AppBar position="fixed" style={{background:'#002a5e'}} open={open}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
sx={{ mr: 2, ...(open && { display: 'none' }) }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Test
</Typography>
</Toolbar>
</AppBar>
);
}
But the problem is when I click the sandwich button on the appbar, it reduces its width to show the Drawer, but the drawer not at all showing.
Drawer.tsx
import * as React from "react";
import { styled, useTheme } from "#mui/material/styles";
import Drawer from "#mui/material/Drawer";
import List from "#mui/material/List";
import Divider from "#mui/material/Divider";
import IconButton from "#mui/material/IconButton";
import ChevronLeftIcon from "#mui/icons-material/ChevronLeft";
import ChevronRightIcon from "#mui/icons-material/ChevronRight";
import ListItem from "#mui/material/ListItem";
import ListItemButton from "#mui/material/ListItemButton";
import ListItemIcon from "#mui/material/ListItemIcon";
import ListItemText from "#mui/material/ListItemText";
import InboxIcon from "#mui/icons-material/MoveToInbox";
import MailIcon from "#mui/icons-material/Mail";
const drawerWidth = 240;
const DrawerHeader = styled("div")(({ theme }) => ({
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-end",
}));
export default function Drawer() {
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<Drawer
sx={{
width: drawerWidth,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: drawerWidth,
boxSizing: "border-box",
},
}}
variant="persistent"
anchor="left"
open={open}
>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</DrawerHeader>
<Divider />
<List>
{["Manage Recipe", "Reports", "Settings"].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Drawer>
);
}
I guess this problem is because the open constant is not getting updated into the drawer component. I am pretty new or only fews days of experience in react. Please help how can share these click event and constant in appbar to the drawer
It looks like with the separation, each component is having their own open state and handling functions, therefore the behavior is not shared.
Try pass these as props from the parent to communicate with children components.
React document about: Passing Props to a Component
There seems to be already a open state in the parent component RootContainer, so perhaps this state and handlers can be passed as props to the components, such as below example.
Over simplified example from forked demo: codesandbox
Define state and handler in the parent component, and pass these down to the children:
RootContainer.tsx
export default function PersistentDrawerLeft() {
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<Box sx={{ display: "flex" }}>
<CssBaseline />
<AppBar open={open} onDrawerOpen={handleDrawerOpen} />
<Drawer open={open} onDrawerClose={handleDrawerClose} />
<Main />
</Box>
);
}
Have each child component use the open state and handlers from the props, instead of their own, so that they share the same state:
Appbar.tsx
export default function AppBar({ open, onDrawerOpen }) {
const theme = useTheme();
return (
<AppBar position="fixed" style={{ background: "#002a5e" }} open={open}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={onDrawerOpen}
edge="start"
sx={{ mr: 2, ...(open && { display: "none" }) }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Test
</Typography>
</Toolbar>
</AppBar>
);
}
Drawer.tsx
export default function Drawer({ open, onDrawerClose }) {
const theme = useTheme();
return (
<Drawer
sx={{
width: drawerWidth,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: drawerWidth,
boxSizing: "border-box"
}
}}
variant="persistent"
anchor="left"
open={open}
>
<DrawerHeader>
<IconButton onClick={onDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</DrawerHeader>
<Divider />
<List>
{["Manage Recipe", "Reports", "Settings"].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Drawer>
);
}
Hope this will help.

Material-UI ListItem "flex-start" not working

I am using the Material-UI List item (https://material-ui.com/components/lists/)
And I would like to "left-justify" my items in my list item to the left. Apparantly I can do this with the flex property "flex-start" which I can set on the alignItems property of a ListItem like so :
<List>
<ListItem alignItems="flex-start">
<ListItemText primary={"First Element "} />
<ListItemText primary={" Second Element"} />
</ListItem>
</List>
However this does not seem to work as they are evenly spaced out when I see it in the browser.
Am I doing something wrong?
Try this...
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
root: {
display:'flex',
alignItems:'flex-start',
flexDirection: "row"
},
});
export default function MyComponent(props) {
const classes = useStyles(props);
return (
<List className={classes.root}>
<ListItem>
<ListItemText primary={"First Element "} />
</ListItem>
<ListItem>
<ListItemText primary={"Second Element "} />
</ListItem>
</List>
);
}
ListItemText has default flex: 1 1 auto; style. You need to change it like:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import { List, ListItem, ListItemText } from "#material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
"& .MuiListItemText-root": {
flex: "initial"
}
}
}));
export default function CustomStyles() {
const classes = useStyles();
return (
<List classes={classes}>
<ListItem alignItems="flex-start">
<ListItemText primary={"First Element "} />
<ListItemText primary={" Second Element"} />
</ListItem>
</List>
);
}

Horizontally Align Primary/Secondary ListItemText, Material UI?

I want the primary and secondary text within a ListItemText to both be center-aligned, unlike the question here.
However, after trying alignItems, alignContent, alignSelf, and primaryTypographyProps={{ align: "center" }}, I am stumped.
What's the correct way to go about this?
Below is an example with center-aligned text.
import React from "react";
import { makeStyles, withStyles } from "#material-ui/core/styles";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const useStyles = makeStyles(theme => ({
root: {
width: "100%",
maxWidth: 360,
backgroundColor: theme.palette.background.paper
}
}));
const CenteredListItemText = withStyles({
root: {
textAlign: "center"
}
})(ListItemText);
export default function SimpleList() {
const classes = useStyles();
return (
<div className={classes.root}>
<List component="nav" aria-label="main mailbox folders">
<ListItem button>
<CenteredListItemText primary="Inbox" secondary="Inbox secondary" />
</ListItem>
<ListItem button>
<CenteredListItemText primary="Drafts" secondary="Drafts secondary" />
</ListItem>
</List>
</div>
);
}

React-admin - Create sub menu

How to create sub menu in react-admin, because in admin-on-rest i can use prop menuItems in MenuItem Component.
Does react-admin have same props for this case?
I try create my own component, but with prop primary in ListItemText for give list name my app always show error Uncaught TypeError: Cannot read property '#global'
Thank you
As previously explained by the maintainers/developers of react-admin, this feature is not offered by react-admin.
If you want a sub-menu, you should create a custom menu, following the instructions given in the official react-admin documentation and implement the Material-UI's nested-list logic.
This is a simple example of what your sub-menu could look like, according to the links I provided:
<List component="nav" >
<ListItem button onClick={this.handleClick}>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText inset primary="YOUR-SECTION-TITLE" />
{this.state.open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<MenuItemLink to="/your-api-endpoint-1" primaryText="API-ENDPOINT-1" onClick={this.props.onMenuClick} />
<MenuItemLink to="/your-api-endpoint-2" primaryText="API-ENDPOINT-2" onClick={this.props.onMenuClick} />
<MenuItemLink to="/your-api-endpoint-3" primaryText="API-ENDPOINT-3" onClick={this.props.onMenuClick} />
</List>
</Collapse>
</List>
Simple submenu component compatible with React Admin v4 https://marmelab.com/react-admin/Menu.html
import * as React from 'react';
import { useState } from 'react';
import PropTypes from 'prop-types';
import ExpandMoreIcon from '#mui/icons-material/ExpandMore';
import { List, ListItem, ListItemText, Collapse } from '#mui/material';
import { useTranslate, useSidebarState } from 'react-admin';
export const SubMenu = (props: SubMenuProps) => {
const { isDropdownOpen = false, primaryText, leftIcon, children, ...rest } = props;
const translate = useTranslate();
const [open] = useSidebarState();
const [isOpen, setIsOpen] = useState(isDropdownOpen);
const handleToggle = () => {
setIsOpen(!isOpen);
};
return (
<React.Fragment>
<ListItem
dense
button
onClick={handleToggle}
sx={{
paddingLeft: '1rem',
color: 'rgba(0, 0, 0, 0.54)',
}}
>
{isOpen ? <ExpandMoreIcon /> : leftIcon}
<ListItemText
inset
disableTypography
primary={translate(primaryText)}
sx={{
paddingLeft: 2,
fontSize: '1rem',
color: 'rgba(0, 0, 0, 0.6)',
}}
/>
</ListItem>
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<List
component="div"
disablePadding
sx={open ? {
paddingLeft: '25px',
transition: 'padding-left 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms',
} : {
paddingLeft: 0,
transition: 'padding-left 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms',
}}
>
{children}
</List>
</Collapse>
</React.Fragment>
)
}
export type SubMenuProps = {
children?: React.ReactNode;
isDropdownOpen?: boolean;
leftIcon?: React.ReactElement;
primaryText?: string;
};
export default SubMenu;
Usage:
import * as React from 'react';
import { Menu } from 'react-admin';
import BookIcon from '#mui/icons-material/Book';
import PeopleIcon from '#mui/icons-material/People';
import LabelIcon from '#mui/icons-material/Label';
import SubMenu from './SubMenu';
export const MainMenu = () => (
<Menu>
<Menu.DashboardItem />
<SubMenu primaryText="CMS" leftIcon={<BookIcon />}>
<Menu.Item to="/admin/pages" primaryText="Pages" leftIcon={<BookIcon />}/>
<Menu.Item to="/admin/articles" primaryText="Articles" leftIcon={<PeopleIcon />}/>
</SubMenu>
<Menu.Item to="/custom-route" primaryText="Miscellaneous" leftIcon={<LabelIcon />}/>
</Menu>
);

MUI - Styling text inside ListItemText

I'm trying to apply style to the text inside a ListItemText (MUI):
const text = {
color: 'red'
}
<ListItem button><ListItemText style={text} primary="MyText" /></ListItem>
But the rendered Typograhy element inside is not styled at all ("MyText" is not red).
Looking at the generated code, it seems that the default CSS rules for Typography > subheading is overriding my CSS.
Thanks for your help
Edit: In the first version of the question, there was a misake ("className" instead of "style" prop on ListItemText, sorry about that).
I beleive the only way to achieve this right now is to use the 'disableTypography' prop of the ListItemText element.
<ListItemText
disableTypography
primary={<Typography variant="body2" style={{ color: '#FFFFFF' }}>MyTitle</Typography>}
/>
This lets you embed your own text element with whatever styling you want on it.
this is good one, you can implement without disabling typography
<ListItemText
classes={{ primary: this.props.classes.whiteColor }}
primary="MyTitle"
/>
Per the documentation, the <ListItemText /> component exposes the prop primaryTypographyProps, which we can use to accomplish what you're attempting in your question:
const text = {
color: "red"
};
<ListItem button>
<ListItemText primaryTypographyProps={{ style: text }} primary="MyText" />
</ListItem>
Hope that helps!
<ListItem >
<Avatar style={{ backgroundColor: "#ff6f00" }}>
<LabIcon />
</Avatar>
<ListItemText
primary={<Typography variant="h6" style={{ color: '#ff6f00' }}>Lab</Typography>}
secondary="Experiments" />
</ListItem>
Turns out there's an even better way to do this as such:
const styles = {
selected: {
color: 'green',
background: 'red',
},
}
const DashboardNagivationItems = props => (
<ListItemText
classes={{ text: props.classes.selected }}
primary="Scheduled Calls"
/>
)
export default withStyles(styles)(DashboardNagivationItems)
You can read more about how this is done here: https://material-ui-next.com/customization/overrides/#overriding-with-classes
Method 1
const textColor = {
color: "red"
};
<ListItemText primaryTypographyProps={{ style: textColor }} primary="BlodyLogic" />
For the Secondary Text
const secondaryColor = {
color: 'green'
}
<ListItemText secondaryTypographyProps={{ style: secondaryColor }}
secondary="If you say that you" />
Method 2
<ListItemText
primary={
<Typography variant="caption" display="block" gutterBottom>
caption text
</Typography>
}
/>
Custom design:
const useStyles = makeStyles({
text: {
color: 'red',
fontSize: 49
},
});
/.....// all make classes
<ListItemText
primary={
<Typography className={classes.text}>
caption text
</Typography>
}
/>
Offical Docs
MUI v5 update
You can leverage system properties in Typography to directly add styling props in the primary and secondary components inside the ListItemText:
<ListItemText
primary="Photos"
secondary="Jan 9, 2014"
primaryTypographyProps={{
fontSize: 22,
color: 'primary.main',
}}
secondaryTypographyProps={{
fontSize: 15,
color: 'green',
}}
/>
You can also use styled if you want to reuse ListItemText in multiple places:
import MuiListItemText from '#mui/material/ListItemText';
import { styled } from '#mui/material/styles';
const ListItemText = styled(MuiListItemText)({
'& .MuiListItemText-primary': {
color: 'orange',
},
'& .MuiListItemText-secondary': {
color: 'gray',
},
});
Live Demo
If you are using material-ui 3.x, this is how it is done:
import { withStyles } from '#material-ui/core/styles';
const styles = {
listItemText: {
color: 'white',
},
}
class YourComponent extends Component {
...
render() {
const { classes } = this.props; // this is magically provided by withStyles HOC.
return (
<ListItem button>
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText classes={{ primary: classes.listItemText }} primary="My Bookings" />
</ListItem>
);
...
}
export default withStyles(styles)(YourComponent);
set all your text related styles on primary property. Sad that it's hidden so deep in the documentation. https://material-ui.com/api/list-item/
Material v1.0
I would add something to #SundaramRavi in regards to:
the way he is using style element which is not great as for Material v1.0 (read the very important difference between v0.16+ and v1.0.
the way files can be structured for better separation of concern.
Whatever.styles.js
const styles = theme => ({
white: {
color: theme.palette.common.white
}
});
exports.styles = styles;
Whatever.js
const React = require('react');
const PropTypes = require('prop-types');
const {styles} = require('./Whatever.styles');
class Whatever extends React.Component {
constructor(props) {
super(props);
}
render() {
const {classes} = this.props;
return (
<div>
<ListItemText
disableTypography
primary={
<Typography type="body2" style={{body2: classes.white}}>
MyTitle
</Typography>
}
/>
</div>
);
}
}
Whatever.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
exports.Whatever = withStyles(styles, {withTheme: true})(Whatever);
you can easily style text by using & span
const useStyles = makeStyles(theme => ({
listItem: {
"& span": {
color: "red"
}
}
}));
..
..
..
<ListItem button>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText className={classes.listItem} primary="Sent mail"/>
</ListItem>
If your are using "#material-ui/core": "^4.11.4" (4.X.X or newer version) then it's simple:
#1st step: Define your styles like this:
const useStyles = makeStyles((theme: Theme) =>
createStyles({
// Other Styling if you wish to use it
root: {
width: '100%',
maxWidth: '36ch',
backgroundColor: theme.palette.background.paper
},
// Other Styling if you wish to use it
inline: {
display: 'inline'
},
// Styling that you asked for
textColor: {
color: 'red'
}
}),
);
#2nd step: Define a constant to use the specific Styles like this:
const AlignItemsList = (props: any) => {
const classes = useStyles(); // Like this one
......
}
#3rd step: In your ListItemText component do like this:
const AlignItemsList = (props: any) => {
const classes = useStyles();
......
<ListItemText
primary="Your Text Goes Here"
classes={{ primary: classes.textColor }} // Like this one
...
/>
};
#4th & final step: Just export your component normally without any other stuff, like this:
export default AlignItemsList;
MUI v5
I recommend to you use global styles for all components. For example you can override styles when you use createTheme.
Here is small example:
export default createTheme({
components: {
MuiListItemText: {
styleOverrides: {
root: {
marginTop: 0,
marginBottom: 0,
},
primary: {
fontSize: '1rem',
},
secondary: {
fontSize: '0.8rem',
},
},
},
},
});
More details on official page

Resources