How can Material-UI's Dialog allow interaction behind the dialog? - javascript

I am using Material-UI in my React app, and I have a Dialog that appears over several form elements after I click a button.
I also have that dialog set to allow it to be dragged using react-draggable.
When the dialog is showing, none of the form elements behind it are ale to be accessed. I realize it intuitively makes sense to block interaction with the elements behind the dialog.
However, I am trying to figure out how to have the dialog showing, and yet still being able to edit the form elements behind it.
Code example here:
Does anyone know if it's possible to show a MaterialUI Dialog and still be able to interact with form elements behind the dialog (ie, when the dialog is dragged away)?

The dialog was meant to block all other interaction so the user can focus on its contents. Anyway, I found one solution, probably not the better but is working here, the code is this:
<Dialog
hideBackdrop // Disable the backdrop color/image
disableEnforceFocus // Let the user focus on elements outside the dialog
style={{ position: 'initial' }} // This was the key point, reset the position of the dialog, so the user can interact with other elements
disableBackdropClick // Remove the backdrop click (just to be sure)
...
>
...
</Dialog>
Here a working example

It is possible, and without too much trouble! When the Dialog is opened, its root container is a div with a class of MuiDialog-root, spawned directly in your <body>. Instead of putting the react-draggable component around the dialog's PaperComponent, we put it around the entire dialog:
<Draggable
handle={'[class*="MuiDialog-root"]'}
cancel={'[class*="MuiDialogContent-root"]'}>
<Dialog
// Styling goes here
>
... // Dialog contents
</Dialog>
</Draggable>
Then, it's necessary to style the Dialog a little. We need to make sure to disable the backdrop, then bring the container size down so that when we click behind it, we are actually selecting other components:
<Dialog
open={open}
onClose={handleClose}
disableEnforceFocus // Allows other things to take focus
hideBackdrop // Hides the shaded backdrop
disableBackdropClick // Prevents backdrop clicks
PaperComponent={PaperComponent}
style={{
top: '30%', // Position however you like
left: '30%',
height: 'fit-content', // Ensures that the dialog is
width: 'fit-content', // exactly the same size as its contents
}}
>
...
</Dialog>
Note the PaperComponent property. As per the material-ui docs on draggable dialogs, this refers to the surface that holds the dialog contents. However, instead of wrapping the paper in a <Draggable>, we need to create this component for styling. If we don't, the PaperComponent will have large, obnoxious margins and won't fit properly in its parent.
function PaperComponent(props: PaperProps) {
// PaperProps is an import from '#material-ui/core'
return (
<Paper {...props} style={{ margin: 0, maxHeight: '100%' }} />
);
}
Be sure to place this function outside of the rendering component. Otherwise, every time the state changes, your dialog contents will be remounted. This was bad for me because I was using an Autocomplete field in the dialog, and every time I selected an option and did something with the onChange(), the text input would disappear. Changing the function scope fixed this problem.

disabling enforce focus can also disturb the origin and position
Use disableAutoFocus
<Dialog
hideBackdrop={true}
disableBackdropClick
disableAutoFocus
...
>
....
</Dialog>

Related

How to totally disable elements events?

I'm using the compound model and the cytoscape-compound-drag-and-drop extension to let the user manually reorganize the layout by grouping some nodes together and moving whole groups easily.
Now I want a button to toggle the display of these groups "boxes", but keep displaying all non-parent nodes.
I first tried hide() on parent nodes, but it also hides the children, so I switched to dynamically applying a class which specifies display:hidden.
It seemed to do the trick, but still the hidden box can be clicked and cytoscape default "visual feedback" for click applies, showing off the area where the hidden box still lies.
I tried plenty of things that didn't work:
- disable events from my hidden style class: tried events:no. Should I report this as a bug ?
- .ungrabify().unselectify().panify().lock()
- on click: destroy the event object
- set e.target._private.active = false
I tried a nasty hack: setting e.target._private.position = {}
The event is still fired, but destroying the position sucessfully prevents the "visual feedback" from happenning, my box effectively stays "hidden".
But still the event occurs on the wrong target: the box, not on the empty space of the cytoscape container. I can keep hacking and leave with it, but isn't there a simpler solution to ?
Is it possible to simply and truly pass through hidden parent nodes events ?
You haven't used events properly.
cy.$('node').forEach(node => {
node.events = 'no'; // will not work
});
The following does work, and you can also restore events whenever you want.
cy.$('node').forEach(node => {
node.style('events', 'no');
});

Conditionally render or prop for modal render

I have a little question about some techniques that are used to render/not render modals.
At the moment there are 2 main ways to do so.
For the first example, we use a visible prop onto the modal and based on that, we will apply a style that will hide the modal. This will be handled by state and then toggled with a button for example :
<Modal
title="Foo"
visible={this.state.visible}
>
Foo
</Modal>
The second way of doing also use state, but uses a condition to render the modal or not :
{this.state.visible && (
<Modal title="Foo">
Foo
</Modal>
)}
The handy thing with this is that the modal will not be rendered until it should.
So what is the best way of doing? I suppose the 2 are right but is there one that is better than the other?
Personally second one is better, because by checking the state at Parent Component, you separate Parent Component logic and Child Component logic, since Modal component only responsible for showing the modal, but the logic whether open or close modal belongs to Parent Component logic. But both solutions will work :)
Just research the question in UI libs docs: antd, material-ui, semantic-ui.
And you will find the answer => prop (with names open, show, visible etc.) is the best way to control visibility (inner state of component).
For example you can see antd modal that use this package
(react-component/dialog):
https://github.com/react-component/dialog/blob/master/src/Dialog.tsx
You can return null or use css (display: none; for sample) for invisible modal

React Modal in reactjs

I have used react-modal for modal dialog,
and in my reactjs render(), i have the following modal
return(
<ReactModal
isOpen={this.state.showModal}
contentLabel="Minimal Modal Example">
<button style={styleClose} onClick={this.handleCloseModal}>
CloseModal</button>
{items}
</ReactModal>
)
But when this dialog open, the items behind are overlapping, how can I have only dialog being visible and not the background elements?
The elements that are overlapping are the components that have radiobuttons. Normal text are not overlapping. How can i make these buttons not to overlap?
I tried setting the zIndex and apply styling but that doesnt work.
Also how can i have close when i also click esc on keyboard ?
The first part of your question needs more info for clarification, I can answer the second part:
Add this to your modal element:
shouldCloseOnEsc={true}
If you haven't, remember to also add onRequestClose={<your component method that hides the modal}
Check this link for more usage: http://reactcommunity.org/react-modal/#usage

Rendering the dropdown part of a SelectField without an invisible overlay

Popup-like components in Material-UI render on an invisible top overlay that is programmatically appended directly to body. For example, when a SelectField or DropDownMenu is opened:
<body>
<div id="app-container>
<div ...>
<!-- This is where the SelectField is rendered. -->
</div>
</div>
<div ...>
<!-- This div is: -->
<!-- (1) where the visible dropdown is rendered when open -->
<!-- (2) a full-screen overlay -->
<!-- (3) removed when the dropdown is closed -->
</div>
</body>
This mechanism is in place so that (1) the user can "click away" to close the open dropdown, (2) the user does not accidentally click on another control when attempting to close the open dropdown.
However, I need to allow clickaways to actually hit the underlying element, without being blocked by the overlay. This can be accomplished in the Popover component by using the useLayerForClickAway prop:
<Popover useLayerForClickAway={false} ... >
...
</Popover>
Unfortunately, no such option exists for SelectField or DropDownMenu (or seems to exist, it's not documented), and I need one of these instead of a Popover.
Moreover, I don't seem to find any option that allows to reliably identify the overlay element created by the dropdown, beside getting the last element in body immediately after the dropdown opened (which is hackish at best). Material-UI also doesn't seem to offer a way to manually override the inline styles of this overlay element.
So, what's the "appropriate" way to disable the invisible overlay, and allow clicks when the dropdown is open?

Semantic-UI Modal causes my element to disappear from the DOM after closing

Really simple question here...note the code snippet first:
$('#gasmask').on('click', function(){
$('#gasmask').modal('show');
});
Clicking the element with an ID of gasmask causes the Semantic-UI modal to show, quite nicely. The issue is that after I click away, causing the modal to disappear, so the does the original element...
Before click:
<div class="content">
<img alt="Gasmask" class="fademein3 ui image" id="gasmask" src="https://s3.amazonaws.com/verumdesigns/gasmask.jpg" style="visibility: inherit; opacity: 1;">
</div>
After modal closes:
<div class="content">
</div>
It just straight up vanishes from the DOM. What's the dealio?
Semantic UI moves the modal contents around in the DOM, as you have figured out. I believe this is so that the modal gets the appropriate inherited styling to display as intended, rather than inherit whichever "component" embeds the modal.
There is an option called "detachable", which you could use like such:
$('.modal')
.modal({
detachable: false
})
.modal('show')
This would ensure that your model contents stay where they are. This has hardly ever been a good option for me, because the modal inherits the styling I use for the component that embeds the modal, which just mucks up the modal. Maybe there is a way to prevent that with CSS?
If CSS can't come to the rescue, then it's up to redesign your frontend.
For me, that meant turning the modal into a singleton component that I can instantiate from another component, passing whichever values are needed to display the modal. Obviously details on that will differ on whether you are using Riot, React, Angular, ...
Another option, if you only have one modal and you don't care where it lives in your DOM, is to id your element with the HTML id attribute. That way you don't care where it is relative to your component.

Categories

Resources