MUI 5 using styled components with child component props - reactjs

I'm converting from MUI 4 to 5. Working on converting from makeStyles() to styled components. Is there an example somewhere using the styled() method with a component that has child component props? For example, <ListItemText /> has primaryTypographyProps and secondaryTypographyProps, both of which I'm setting the inner className property for custom styles.
How does something like this...
<ListItemText
{...props}
primaryTypographyProps={{
variant: 'body2',
className: classes.primary,
}}
secondaryTypographyProps={{
variant: 'body1',
className: classes.secondary,
}}
/>
...convert to something like this?
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
...???...
}));
[Edit] This is the closest I've been able to find, but it's not quite there. What I'm trying to do is pass it through a props object, rather than whole components.

I haven't had a chance to verify if this works, but I'm assuming this is the direction I need to go with this:
const StyledListItemText = styled(ListItemText)(() => ({
'MuiListItemText-primary': { ... },
'MuiListItemText-secondary': { ... },
}));

You are very close!
import listItemTextClasses from '#mui/material';
...
const StyledListItemText = styled(ListItemText)(() => ({
[`& .${listItemTextClasses.primary}`]: {...}
[`& .${listItemTextClasses.secondary}`]: {...}
}));
https://mui.com/base/getting-started/customization/#applying-custom-css-rules

Related

MUI: customize button component with tailwind classes

I'm trying to customise the look of MUI's <Button /> component. As i want to do it by using tailwind classes i tried to use defaultProps instead of styleOverrides.
const lightTheme = createTheme({
components: {
MuiButton: { defaultProps: { classes: { root: 'p-8' } } },
},
});
It is working fine except that i'm not able to add tailwind classes when i use the <Button /> component as this will overwrite the changes i made above.
<Button variant="contained" classes={{ root: 'p-12' }}>
Ok
</Button>
Is there a way to extend the definition of the classes prop rather than overwriting it?

How to convert this function from the styled components package to regular jsx

I am currently removing styled components from my react app. But I'm having some trouble with the function below. Does anyone have any suggestions? To be a little more clearer. This function sets the opacity and filtered blur. I need it to do the exact same thing, Just in regular JSX.
const ImgSrc = styled.div.attrs(props => ({
style: {
opacity: `${props.opacity}`,
filter: `${props.filter}`
}
}))
You can simply write ImgSrc to be a component which returns the div with the specified properties like
const ImgSrc = props => (
<div styles={{
opacity: props.opacity,
filter: props.filter
}} />
)

How to make dialog appear over Snackbar?

Codesandbox: https://codesandbox.io/s/elegant-wind-mv842
As can be seen in the sandbox, I am using Notistack for snackbars. I also want to use MUI Dialogs, but the Snackbars appear over the dialogs, which I don't want. Is there a way to make the dialog appear over the snackbars, without closing them?
<div>
<SnackbarProvider maxSnack={3}>
<MessageButtons />
</SnackbarProvider>
<SimpleDialogDemo />
</div>
Is the only component that I am producing in the demo, and it is enough to see the issue.
Just decrease z-index of notistack, e.g.:
const useStyles = makeStyles((theme) => ({
snackbar: {
zIndex: '10 !important',
}
}));
and provide appropriate props for SnackbarProvider
<SnackbarProvider classes={{containerRoot: classes.snackbar}}>
...
</SnackbarProvider>

Material-UI : how to overwrite makeStyles classes for custom component with spread operator in props

I'm creating a custom component which extends Material-UI's Typography component.
The component is for a template I'm creating, so I want to include the option to overwrite the default classes. A very basic guide is provided by Material-UI here however it doesn't provide details about how to do this when you can't simply pass all props to useStyles(props).
I can't pass all props to useStyles(props) as:
Some of the props take in default values that need to be defined
Since it's extending the Typography component, some of the props are destructured (for use in the new custom component) while all remaining props are passed to the Typography component using a spread operator.
Some of the props are only used for styling purposes (i.e. only used to be passed in as props to useStyles(props). If I pass in all props to useStyles(props) then this causes an issue with point 2 above, as I'm unable to destructure these styling-only props so they get passed into the Typography component as part of the spread operator.
Here's some code to illustrate the issue:
// useStyles()
const useStyles = makeStyles(theme => ({
/** Styles applied to the root element */
root: (props: StylesProps) => ({
color: theme.palette.primary.main,
fontFamily: props.fontFamily,
fontWeight: props.weight,
letterSpacing: props.letterSpacing,
lineHeight: 1,
textTransform: props.casing
}),
/** Styles applied to the second logo text if `name[1] !== undefined` */
nameEnd: {
color: theme.palette.secondary.main
},
/** Styles applied to the root element if `textShadow=true` */
textShadow: {
textShadow: theme.textShadow
}
}));
// Component
const Logo = React.forwardRef(function Logo(props: LogoProps, ref) {
const {
casing = "uppercase", // HAS ISSUES 1, 2 and 3
className,
fontFamily, // HAS ISSUES 2 and 3
letterSpacing, // HAS ISSUES 2 and 3
name,
nameSpacing = false,
textShadow = true, // HAS ISSUES 1, 2 an 3
weight, // HAS ISSUES 2 and 3
...other
} = props;
const stylesProps: StylesProps = {
casing,
fontFamily,
letterSpacing,
weight
};
const tiqClasses = useStyles(stylesProps);
const [nameStart, nameEnd] = name;
function LogoText(): JSX.Element {
return (
<React.Fragment>
{nameStart}
{nameSpacing && " "}
{nameEnd && <span className={tiqClasses.nameEnd}>{nameEnd}</span>}
</React.Fragment>
);
}
return (
<Typography
className={clsx(className, tiqClasses.root, {
[tiqClasses.nameEnd]: nameEnd,
[tiqClasses.textShadow]: textShadow
})}
component="p"
ref={ref}
{...other}
>
<LogoText />
</Typography>
);
});
If I change the code to:
// Component
const Logo = React.forwardRef(function Logo(props: LogoProps, ref) {
const {
className,
name,
nameSpacing = false,
...other
} = props;
const tiqClasses = useStyles(props);
const [nameStart, nameEnd] = name;
function LogoText(): JSX.Element {
return (
<React.Fragment>
{nameStart}
{nameSpacing && " "}
{nameEnd && <span className={tiqClasses.nameEnd}>{nameEnd}</span>}
</React.Fragment>
);
}
return (
<Typography
className={clsx(className, tiqClasses.root, {
[tiqClasses.nameEnd]: nameEnd,
[tiqClasses.textShadow]: textShadow
})}
component="p"
ref={ref}
{...other}
>
<LogoText />
</Typography>
);
});
Then it means that:
casing and textShadow props no longer have default values
casing, fontFamily, letterSpacing and weight are all passed to the Typography component under the ...other spread operator since they're no longer being destructured. These are not valid props for the Typography component so they get passed down as a DOM attribute, which then causes errors as they are not valid DOM attributes either.
What I want to achieve
I want to be able to overwrite the root, nameEnd and textShadow classes by using the component like so:
<Logo tiqClasses={{ root: "rootOverwriteClass", nameEnd: "nameEndOverwriteClass" }} classes={{ body1: "body1OverwriteClass" }} {...otherProps} />
I would much prefer to do this with makeStyles than withStyles.
Note: Just to clarify, I'm using tiqClasses instead of classes as classes is already being used by Material-UI. You can see this in the example above where I use different names for each.
How do I achieve this?

scale and change the color of a svg on hover in React

I have the following custom Icon component:
import React from 'react';
import PropTypes from 'prop-types';
const Icon = (props: any) => {
const styles = {
svg: {
display: 'inline-block',
verticalAlign: 'middle',
},
path: {
fill: props.color,
},
};
return (
<svg
style={styles.svg}
width={`${props.size}px`}
height={`${props.size}px`}
viewBox="0 0 1024 1024"
>
<path style={styles.path} d={props.icon} />
</svg>
);
};
Icon.propTypes = {
icon: PropTypes.string.isRequired,
size: PropTypes.number,
color: PropTypes.string
};
Icon.defaultProps = {
size: 16
};
export default Icon
In another component I instantiate it with the following statement:
<Icon
icon={ICONS.TWITTER}
color="#fff"
size={30}
/>
How can I change the size and color on hover?
Thanks in advance!
Assuming you want to solve this using React, you need to make the component that is rendering your <Icon /> component and setting its props aware of wether the <Icon /> component is hovered or not.
This means that the parent component has to become stateful and the <Icon /> component needs to implement some kind of callback when the mouse enters and leaves it. For this, you could use the onMouseEnter and onMouseLeave functions (see the docs as well). These would then just set a boolean flag in the state of the parent component, and based on this, the props would change.
An implementation could look like this:
<Icon
icon={ICONS.TWITTER}
color={this.state.isHovered ? '#333' : '#fff'}
size={this.state.isHovered ? 40 : 30}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
/>
Where handleMouseEnter() could look like this:
handleMouseEnter = () => {
this.setState({ isHovered: true })
}
and handleMouseLeave() would just do the opposite.
Note though, that this is a lot of work to implement something that browsers already do for you (the :hover state in CSS), so it is worth considering if you need the effect to be as dynamic as it now is, or if you could just live with a fixed increase in size and color change.
Also, onMouseEnter and onMouseLeave can cause some issues with touch devices.

Resources