How to make dialog appear over Snackbar? - reactjs

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>

Related

How do I share state between routes in React-Router?

I've seen a few questions similar to this on SO but none that quite matched my needs. I'm using React and Material-UI to make a dashboard. I'm using Material-UI's mini variant drawer as a sidebar, with links that should display routes when clicked. The sidebar can be opened by clicking a button, which updates a state variable and adjusts the CSS className of the sidebar. This causes the sidebar/drawer to "slide" open.
If I click a link on the sidebar, I can easily display a desired route. However, I can't get the route to also "slide" to the side when the sidebar/drawer opens. It will probably be easier to understand by looking at the code, so I've included a link to a codesandbox below:
https://codesandbox.io/s/appbar-with-react-router-bkogj?file=/src/App.js
I basically copy and pasted everything from the Material-UI website (using v4 I believe), then added the route myself. Would appreciate any feedback on how to solve this issue.
For this I think the MiniDrawer component needs to render the content since it necessarily is aware of the space the appbar and drawer components occupy.
MiniDrawer
Take and render a children prop.
export default function MiniDrawer({ children }) {
...
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
...
>
...
</AppBar>
<Drawer
...
>
...
</Drawer>
<main className={classes.content}>{children}</main>
</div>
);
}
App
Render the Outlet as a child component.
export default function App() {
return (
<div className="App">
<AppBar>
<Outlet />
</AppBar>
</div>
);
}
RejectTable
Remove the excess margin so it fills the content area the parent component allows.
const useStyles = makeStyles((theme) => ({
content: {
flexGrow: 1,
padding: theme.spacing(3),
height: "100%",
// marginLeft: "4em" // <-- remove
}
}));

MUI 5 using styled components with child component props

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

Unable to fix popper placement in autocomplete

I am using the <Autocomplete /> component of Material-UI and I have a situation where I want my drop-down to always appear at the bottom. Therefore I did this:
PopperComponent={(props) => <Popper {...props} placement='bottom-start' />}
My drop-down still appear at the top sometimes.
Moreover, when i did the above, the width of my popper is no longer the width of my autocomplete.
I decided then that i want to change the zIndex of the popper so that the app bar won't cover it if the position of the popper switches to the top.
How can i fix it?
Yes, placement appears to be broken when used in Autocomplete's Popper (material-ui v. 4.11.4).
A hack that worked for me is as follows:
<Autocomplete
// Force menu to open below, with the correct width
PopperComponent={({ style, ...props }) => (
<Popper
{...props}
style={{ ...style, height: 0 }} // width is passed in 'style' prop
/>
)}
// Set menu max height (optional)
ListboxProps={{ style: { maxHeight: '30vh' } }}
/>
I am using MUI 5.4.4 and ran into a similar issue where the Autocompeletes Popper component was trying to flip to the top (and therefor disappearing) when there wasn't enough space on the bottom of the page. I fixed the issue by creating custom popper component with a flip modifier with the fallbackPlacements option set to an empty array and setting the popperOptions placement to bottom so that the Popper menu is always at the bottom regardless of if there is enough space or not.
The popper docs for the flip modifier explained it pretty well.
Custom popper:
const CustomerPopper = (props) => {
const modifiers = [
{
name: 'flip',
options: {
fallbackPlacements: []
},
},
]
return (
<Popper
{...props}
modifiers={modifiers}
popperOptions={{
placement: 'bottom',
}}
/>
)
}
Autocomplete:
<Autocomplete
{...otherStuff}
PopperComponent={(props) => <CustomerPopper {...props} />}
/>
If anyone is still looking for an answer. You can achieve this by using flip modifier
const CustomerPopper = (props: any) => <Popper
{...props}
modifiers={{
flip: {
enabled: false,
}
}}
popperOptions={{
placement:'bottom',
}}
/>;

Material-ui's Switch component onChange handler is not firing

I've put some Switches in an app and they work fine. Then I put the Switches in another app, but they don't work when clicked.
Both apps are using the same component. Here it is working in one app:
And here's the other app, not working:
In the second app, the onChange handler doesn't seem to ever fire.
The code in both apps looks like the following:
<Switch
checked={(console.log('checked:', status === 'visible'), status === 'visible')}
onChange={(e, c) => console.log('event:', e, c)}
/>
In the first app I see the output of those console.logs, while in the second app I only see the initial console.log of the checked prop, but I never see any of the onChange prop.
I checked if any ancestor elements have click handlers, and I didn't find any that are returning false, calling stopPropagation, or calling preventDefault.
Notice in the gif that when I click, the ripple effect still works, so click handling is obviously still working.
Any ideas why onChange may not be firing?
UPDATE! I replaced the switches with regular <input type="checkbox"> elements, and it works great! See:
Looks to me like something is wrong with material-ui's <Switch> component. I have a hunch that I will investigate when I get a chance: there might be more than one React singleton in the application. I'll be back to post an update.
I think, this is a weird fix and it is working smoothly for me. So, instead of handleChange I am using handleClick. I am not using event here, instead I am passing a string which is obviously the name of the state or id in case of arrays.
<Switch
checked={this.state.active}
onClick={() => this.handleToggle('active')}
value="active"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
handleToggle = (name: string) => {
this.setState({ active: !this.state.active });
};
I tried handleChange, but the problem still persists. I hope this will get fixed soon.
I've had the same issue with Checkbox and Switch in the WordPress admin area.
Turns out, there was global CSS rule like:
input[type="checkbox"] {
height: 1rem;
width: 1rem;
}
Clicking the upper left corner of the element works, though.
As a solution, I reset some styles in my app root.
EDIT: Nevermind, I just put my whole app into shadow DOM. There are a few gotchas, I'll list them here:
You have to provide a custom insertion point for Material-UI style elements inside the shadow DOM. In general, you have to make nothing gets put outside of you shadow DOM.
You have to load/link the font inside the shadow DOM and outside the shadow DOM (the light DOM).
Use ScopedCssBaseline instead of the global reset.
Dialogs have to have their container prop specified.
This is how I've set things up with Material-UI:
// configure-shadow-dom.js
import { create } from 'jss';
import { jssPreset } from '#material-ui/core/styles';
const shadowHostId = 'my-app-root-id'
export const appRoot = document.createElement('div')
appRoot.setAttribute('id', 'app-root')
const styleInsertionPoint = document.createComment('jss-insertion-point')
export const jss = create({
...jssPreset(),
insertionPoint: styleInsertionPoint,
})
const robotoFontLink = document.createElement('link')
robotoFontLink.setAttribute('rel', 'stylesheet')
robotoFontLink.setAttribute('href', 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap')
const shadowHost = document.getElementById(shadowHostId)
shadowHost.attachShadow({ mode: 'open' })
const shadowRoot = shadowHost.shadowRoot
shadowRoot.appendChild(robotoFontLink)
shadowRoot.appendChild(styleInsertionPoint)
shadowRoot.appendChild(appRoot)
// index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import ScopedCssBaseline from '#material-ui/core/ScopedCssBaseline';
import { StylesProvider } from '#material-ui/core/styles';
import App from './App';
import { jss, appRoot } from './configure-shadow-dom';
ReactDOM.render(
<React.StrictMode>
<StylesProvider jss={jss}>
<ScopedCssBaseline>
<App />
</ScopedCssBaseline>
</StylesProvider>
</React.StrictMode>,
appRoot
);
It turns out that in my case, there was a CSS in the page, something like
.some-component { pointer-events: none; }
.some-component * { pointer-events: auto; }
where .some-component was containing my material-ui buttons and switches. I had to manually set pointer-events to all (or some value, I don't remember at at the moment) for the elements inside the switches.
So, that's one thing to look out for: check what pointer-events style is doing.
in Switch component, changing onChange to onClick worked for me:
<Switch
checked={this.state.active}
onClick={() => this.handleToggle('active')}
value="active"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
still running into similar issues getting an event from the switch. here's a quick fix using internal state (you should be using hooks now and functional components, if not you are missing out, but you can use setState to do what i'm showing here in a class component.)
const [switchChecked, setSwitchChecked] = useState(false);
.......
<Switch checked={switchChecked} onChange={() => {setSwitchChecked(!switchChecked);}}/>
you'll need to have a value for switchChecked in the components local state.
Try this one checked or unchecked passed as a second argument.
<Switch
checked={this.state.active}
onClick={(e,flag) => this.handleToggle('active', flag)}
value="active"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>```
The issue is that you have to sniff the checked attribute from the event: event.target.checked
Full solution:
import { Switch } from '#material-ui/core';
import { useState } from 'react';
function App() {
const [checked, setChecked] = useState(false);
const switchHandler = (event) => {
//THIS IS THE SOLUTION - use event.target.checked to get value of switch
setChecked(event.target.checked);
};
return (
<div>
<Switch checked={checked} onChange={switchHandler} />
</div>
);
}
export default App;

How can I make Dialog take 80% of the screen in Material-UI?

I am working with Material-UI Dialog and I want to make it take 80% of the screen.
Ideally, I want something like this.
I am applying a margin to Dialog but it is not working as intended.
For older material-ui versions like 0.20.0:
<Dialog
title="Dialog With 80% Width"
modal={true}
contentStyle={{
width: '80%',
maxWidth: 'none',
}}
open={true}
>
This dialog spans the 80% width of the screen.
</Dialog>
And in material-ui V1 using these props may can help with your needs
fullWidth={true}
maxWidth = {'md'}
Here is examples and other props for Dialog component, or in more advanced way you can take a look into Dialog component code see what is happening there.
The paperFullWidth css class of Dialog component might be helpful. The only condition for using this class is the fullWidth prop of Dialog component should be true. Below is a sample snippet
import React from "react";
import Dialog from "#material-ui/core/Dialog";
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
dialogCustomizedWidth: {
'max-width': '80%'
}
});
const DialogExample = ({ classes }) => (
<Dialog
open
fullWidth
classes={{ paperFullWidth: classes.dialogCustomizedWidth }}
>
I'm a Dialog with customized width.
</Dialog>
);
export default withStyles(styles)(DialogExample);

Resources