How to change button props based on breakpoints in Material UI - reactjs

I'm trying to change the variant and/or size on a material-ui button for different screen sizes. For example, use no variant or size="small" below the "sm" breakpoint and variant="outlined" and/or size="large" above "sm".
Normally, I'd make use withStyles and create a style with theme.breakpoints to affect changes by applying the style to the element using className, however, variant and size are props.
After reading the api, scouring the web, and fiddling extensively, I can't seem to figure out any straight-forward way to change the props based on viewport width.
I've thought about creating a "width-detector" and then using some JS logic to change the button element's props accordingly, but that seems just a bit far out as a solution.
So I'm asking here to see if there is an easier solution out there. Thanks.

The withWidth HOC is deprecated per Material UI Docs.
This is the approach that works now, with a combination of useTheme and useMediaQuery.
Edit: useTheme is not really required here, since useMediaQuery automatically provides that as an argument.
// import { useTheme } from "#material-ui/core/styles";
import { useMediaQuery } from "#material-ui/core";
...
function ResponsiveButton() {
// const theme = useTheme();
// const isSmallScreen = useMediaQuery(theme.breakpoints.down("xs"));
const isSmallScreen = useMediaQuery(theme => theme.breakpoints.down("xs"));
const buttonProps = {
variant: isSmallScreen ? "text" : "outlined",
size: isSmallScreen ? "small" : "large"
};
return (
<Button {...buttonProps} color="primary">
Responsive Button
</Button>
);
}
export default ResponsiveButton;

Material UI Docs:
Sometimes you might want to change the React rendering tree based on the breakpoint value. We provide a withWidth() higher-order component for this use case.
withWidth injects a width property into your component that gives you access to the current breakpoint value. This allows you to render different props or content based on screen size.
function ResponsiveButton({ width }) {
// This is equivalent to theme.breakpoints.down("sm")
const isSmallScreen = /xs|sm/.test(width);
const buttonProps = {
variant: isSmallScreen ? "text" : "outlined",
size: isSmallScreen ? "small" : "large"
};
return (
<Button {...buttonProps} color="primary">
Responsive Button
</Button>
);
}
export default withWidth()(ResponsiveButton);

This is my implementation
import Button from "#material-ui/core/Button";
import { useTheme } from "#material-ui/core/styles";
import useMediaQuery from "#material-ui/core/useMediaQuery";
import { useEffect } from "react";
const ResponsiveButton = (props) => {
const theme = useTheme();
const desktop = useMediaQuery(theme.breakpoints.up("lg"));
const tablet = useMediaQuery(theme.breakpoints.up("sm"));
const mobile = useMediaQuery(theme.breakpoints.up("xs"));
const sizes = () => {
if (desktop) return "large";
if (tablet) return "medium";
if (mobile) return "small";
};
return <Button {...props} size={sizes()}/>;
};
export default ResponsiveButton;

To all those who are from 'mui' and not 'material-ui' age, you can create two different components and pass display as an in-line style. Only one will be rendered for a specific screen size.
Refer the example below.
<Typography
sx={{ display:{sx:'none', sm:'none', md:'block', lg:'block', xl:'block'} }}
>
This will be rendered only on screens which are medium-sized and above
</Typography>
<Typography
sx={{ display:{sx:'block', sm:'block', md:'none', lg:'none', xl:'none'} }}
>
This will be rendered only on screens which are below medium-sized
</Typography>
Not sure if you must mention all the sizes inside display. Maybe you can skip lg and xl once you set md to 'block'.

Related

Styled-Components Not Loading Until Page Refreshed

I'm facing an issue where the css from my styled-components aren't loading until we refresh. I pared down a specific file until there is just a button. I kept the file flow, but stripped out practically everything but the button and the green background color.
I would expect that on the first load, the button has a green background. Instead, it has a transparent background. But it does have the green background after refresh.
If additional context helps, we are using material themes at the moment but are transitioning to styled components. This specific page doesn't reference the MUI themes in any way I can tell. That said, I also am getting the multiple #mui/styles warning in the console. (My package.json doesn't directly use #mui/styles, so I am assuming it's a dependency)
I'm currently stumped and would happily take any suggestions on what to check.
It looks like there are several instances of `#material-ui/styles` initialized in this application.
This may cause theme propagation issues, broken class names, specificity issues, and makes your application bigger without a good reason.
See https://material-ui.com/r/styles-instance-warning for more info.
index.tsx
import PrimaryButton from "components/Shared/Button/PrimaryButton";
export default function ReviewPurchaseOrderPage() {
return (
<PrimaryButton
text={"Approve"}
onClick={() => console.log("test for SO question")}
/>
);
}
PrimaryButton.tsx
import ActionButton from "components/Shared/Button/ActionButton";
interface Props {
text: string;
onClick: () => void;
}
export default function PrimaryButton({
text,
onClick,
}: Props) {
return (
<ActionButton
text={text}
onClick={onClick}
/>
);
}
ActionButton.tsx
import { Button } from "#material-ui/core";
import styled from "styled-components";
const StyledActionButton = styled(Button)`
background-color: #769362;
`;
interface Props {
text: string;
onClick: () => void;
}
export default function ActionButton({
text,
onClick,
}: Props) {
return (
<StyledActionButton
onClick={onClick}
>
{text}
</StyledActionButton>
);
}

How to Change appbar Color on Scroll in material ui

I am trying to change the background colour when I scroll down.
But don't know why it's not working.
My NavBar Component
import { useScrollTrigger } from "#mui/material";
const NavBar = (props) => {
function ChangeColorOnScroll(props) {
const { children, window } = props;
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
target: window ? window() : undefined,
});
return React.cloneElement(children, {
style: { background: trigger ? "black" : "transparent" },
});
}
return (
<>
<ChangeColorOnScroll {...props}>
<AppBar position="fixed" color="transparent">
.....-> Component Data
</AppBar>
</ChangeColorOnScroll>
<main>{props.children}</main>
</>
);
};
export default NavBar;
Source code CodeSandbox
*
https://codesandbox.io/s/happy-boyd-njbx2u?file=/src/App.js
I don't see any useScrollTrigger hook implementation on your code but if you want to use it for changing the background color of the AppBar component when the page is scrolled, I give you a lot of examples of using the useScrollTrigger hooks here https://codesandbox.io/s/material-ui-usescrolltrigger-963ov.
One of them is changing the AppBar component background color when the page is scrolled.
The codesandbox above is created using Material UI v4 but is still relevant to MUI v5.

Change color of the IconComponent of Material UI's Select Component

For my project, I'm using MUI's Select Component with the LanguageIcon as the IconComponent.
What I'm trying to do is turn this icon white (it's black per default), but I can't get it to work.
I tried to follow this solution, but it won't work for me.
import { makeStyles } from '#mui/styles'; throws "Module not found: Can't resolve '#mui/styles'" and on their website it says #mui/styles is deprecated.
This is what I currently have:
import * as React from 'react';
import { FunctionComponent } from 'react';
import MenuItem from "#mui/material/MenuItem";
import FormControl from "#mui/material/FormControl";
import Select, {SelectChangeEvent} from "#mui/material/Select";
import { useRouter } from 'next/dist/client/router';
import LanguageIcon from '#mui/icons-material/Language';
const LocaleSelect: FunctionComponent = () => {
const router = useRouter()
const {locale, locales, pathname, asPath, query} = router;
const handleLocaleChange = (event: SelectChangeEvent<string>) => {
router.push({ pathname, query }, asPath, { locale: event.target.value})
}
return(
<FormControl
variant='standard'
sx={{ m: 1, maxWidth: 32 }}
color="primary" >
<Select
disableUnderline
labelId="demo-simple-select-autowidth-label"
id="demo-simple-select-autowidth"
value={locale?.toLocaleUpperCase()}
onChange={handleLocaleChange}
autoWidth
IconComponent={LanguageIcon} >
{locales?.map((l) => {
return <MenuItem key={l} value={l}>
{l.toLocaleUpperCase()}</MenuItem>;
})}
</Select>
</FormControl>
)
}
export default LocaleSelect
This makes it look like this.
I managed to make the globe white by using
IconComponent={() => <LanguageIcon htmlColor='white'/>}
but that moves the globe to the right.
Any help would be fantastic; either by making the globe white or by moving it to the left.
You can do something like this.
<LanguageIcon
htmlColor="white"
sx={{ position: "absolute", left: ".5rem",cursor:'pointer',zIndex:-1 }}
/>
Position absolute makes it easy to align the icon where you want but the icon becomes un-clickable. To solve this I added cursor:pointer which partially solved the issue but wasn't able to click yet. Hence I reduced the z-index.
Reducing the z-index works because, clicking on icon actually clicks the parent and then displays the options.

Apply MUI styles to non-MUI markup elements

In my Gatsby app I am using MUI v5, and must also output user created markup. I want the user-markup to get the same base-styles as their analogous Typography elements (see below). How can I achieve this?
Note - the user markup already contains <p> <h1> or other tags, which cannot be directly modified by React.
Additionally the app is wrapped with ThemeProvider and I'm using styled-components as the style engine for MUI (if that matters...).
import {Typography} from "#mui/material"
export default function() {
return (
<>
<Typography variant="body1" paragraph>My styled Typography</Typography>
// The next line can't receive any classses or modifiers,
// but must end up styled like the <Typography> element.
<p>my custom user content - how can i style this like above?</p>
</>
)
}
You need to import your theme. From there you can access the body1 default typography style to apply to the p element.
import {Typography} from '#mui/material'
import {useTheme} from //im not exactly sure where this comes from '#mui/material' or '#mui/styles'
export default function() {
const theme = useTheme()
return (
<>
<Typography variant="body1" paragraph>My styled Typography</Typography>
<p style={theme.typography.body1}>my custom user content - how can i style this like above?</p>
</>
)
}
import {useTheme ,Typography} from "#mui/material"
export default function() {
const theme = useTheme()
return (
<>
<Typography variant="body1" paragraph>My styled Typography</Typography>
<p style={theme.typography.body1}>my custom user content - how can i style this like above?</p>
</>
)
}
If you want to add the sx prop in your custom component:
const P = styled("p")({});
const theme = useTheme()
<P sx={{ ...theme.typography.body1 }}>
If you want to use system properties:
import { unstable_extendSxProp as extendSxProp } from "#mui/system";
const Psx = styled("p")();
function P(inProps) {
const { sx, ...other } = extendSxProp(inProps);
return <Psx sx={sx} {...other} />;
}
<P {...theme.typography.body1}>
If you want to use variant prop like in Typography:
const T = styled('p')(({ theme, variant = 'body1' }) => ({
...theme.typography[variant],
}));
<T variant="h3">
Live Demo
Related Answer
Passing props to MUI styles

How to fit Tooltip to screen for mobile responsiveness in Material UI?

I want to show Material UI Tooltip fit to screen and screen width rather fit to the children element.
While in not mobile devices, Tooltip component is shown position absolute and place regarding the placement props.
But in mobile devices, I want to make Tooltip width full and also center the screen.
How to make this?
Thanks in advance.
You can provide a minimum width via the tooltips classes prop.
Like this:
import React from "react"
import { makeStyles } from "#material-ui/core/styles"
import Button from "#material-ui/core/Button"
import Tooltip from "#material-ui/core/Tooltip"
const useStyles = makeStyles(theme => ({
customWidth: {
minWidth: "95vw",
}
}))
export default function FullWidthTooltip() {
const classes = useStyles()
return (
<Tooltip
title="Full width"
classes={{ tooltip: classes.customWidth }}
>
<Button variant="contained">Hover me</Button>
</Tooltip>
)
}
You can view a demo on CodeSandbox here

Resources