-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[base] Add useModal
hook
#38187
Merged
Merged
[base] Add useModal
hook
#38187
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
2999d30
[joy] Add `useModal` internal hook
mnajdova d459eef
Update packages/mui-joy/src/Modal/useModal.ts
mnajdova 8483b6f
Import ariaHidden from Base UI
mnajdova a77fc9b
Merge branch 'joy/useModal-hook' of https://github.com/mnajdova/mater…
mnajdova fa82187
wip useModal in base UI
mnajdova 8dc8ea9
Merge branch 'master' into joy/useModal-hook
mnajdova 0307cb0
docs:api
mnajdova cb78f37
Fix propagation of custom event handlers
mnajdova b28568f
Define the hook's types
mnajdova 83277a7
Fix import name for unstable hooks
mnajdova 3a1578f
Fix imports and circular dependency
mnajdova 7979081
Add hook demo
mnajdova 283633c
Fix isTopModal type & usage
mnajdova 06d0475
demos fixes
mnajdova 40d5174
Add use client directive
mnajdova 7bd4142
prettier
mnajdova 122cc4e
lint & docs:api
mnajdova 50a2cfb
ci fixes
mnajdova 2331dc3
Fix useAutocomplete issue
mnajdova 0f6740b
Merge branch 'master' into joy/useModal-hook
mnajdova b8103b7
docs:api
mnajdova 577ad5e
Merge branch 'master' into joy/useModal-hook
mnajdova e54dfda
Review comments
mnajdova ebe554d
ref -> rootRef
mnajdova 707fe36
fixes
mnajdova f1c79bf
more fixes
mnajdova 1e7ced7
Update packages/mui-base/src/unstable_useModal/useModal.ts
mnajdova 5ce713a
simplify condition
mnajdova File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
'use client'; | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import clsx from 'clsx'; | ||
import { Box, styled } from '@mui/system'; | ||
import { Portal } from '@mui/base/Portal'; | ||
import { FocusTrap } from '@mui/base/FocusTrap'; | ||
import { Button } from '@mui/base/Button'; | ||
import { unstable_useModal as useModal } from '@mui/base/unstable_useModal'; | ||
import Fade from '@mui/material/Fade'; | ||
|
||
export default function UseModal() { | ||
const [open, setOpen] = React.useState(false); | ||
const handleOpen = () => setOpen(true); | ||
const handleClose = () => setOpen(false); | ||
|
||
return ( | ||
<div> | ||
<TriggerButton onClick={handleOpen}>Open modal</TriggerButton> | ||
<Modal | ||
aria-labelledby="transition-modal-title" | ||
aria-describedby="transition-modal-description" | ||
open={open} | ||
onClose={handleClose} | ||
closeAfterTransition | ||
> | ||
<Fade in={open}> | ||
<Box sx={style}> | ||
<h2 id="transition-modal-title">Text in a modal</h2> | ||
<span id="transition-modal-description" style={{ marginTop: 16 }}> | ||
Duis mollis, est non commodo luctus, nisi erat porttitor ligula. | ||
</span> | ||
</Box> | ||
</Fade> | ||
</Modal> | ||
</div> | ||
); | ||
} | ||
|
||
const Modal = React.forwardRef(function Modal(props, forwardedRef) { | ||
const { | ||
children, | ||
closeAfterTransition = false, | ||
container, | ||
disableAutoFocus = false, | ||
disableEnforceFocus = false, | ||
disableEscapeKeyDown = false, | ||
disablePortal = false, | ||
disableRestoreFocus = false, | ||
disableScrollLock = false, | ||
hideBackdrop = false, | ||
keepMounted = false, | ||
onClose, | ||
open, | ||
onTransitionEnter, | ||
onTransitionExited, | ||
...other | ||
} = props; | ||
|
||
const propsWithDefaults = { | ||
...props, | ||
closeAfterTransition, | ||
disableAutoFocus, | ||
disableEnforceFocus, | ||
disableEscapeKeyDown, | ||
disablePortal, | ||
disableRestoreFocus, | ||
disableScrollLock, | ||
hideBackdrop, | ||
keepMounted, | ||
}; | ||
|
||
const { | ||
getRootProps, | ||
getBackdropProps, | ||
getTransitionProps, | ||
portalRef, | ||
isTopModal, | ||
exited, | ||
hasTransition, | ||
} = useModal({ | ||
...propsWithDefaults, | ||
rootRef: forwardedRef, | ||
}); | ||
|
||
const classes = { | ||
hidden: !open && exited, | ||
}; | ||
|
||
const childProps = {}; | ||
if (children.props.tabIndex === undefined) { | ||
childProps.tabIndex = '-1'; | ||
} | ||
|
||
// It's a Transition like component | ||
if (hasTransition) { | ||
const { onEnter, onExited } = getTransitionProps(); | ||
childProps.onEnter = onEnter; | ||
childProps.onExited = onExited; | ||
} | ||
|
||
const rootProps = { | ||
...other, | ||
className: clsx(classes), | ||
...getRootProps(other), | ||
}; | ||
|
||
const backdropProps = { | ||
open, | ||
...getBackdropProps(), | ||
}; | ||
|
||
if (!keepMounted && !open && (!hasTransition || exited)) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Portal ref={portalRef} container={container} disablePortal={disablePortal}> | ||
{/* | ||
* Marking an element with the role presentation indicates to assistive technology | ||
* that this element should be ignored; it exists to support the web application and | ||
* is not meant for humans to interact with directly. | ||
* https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md | ||
*/} | ||
<CustomModalRoot {...rootProps}> | ||
{!hideBackdrop ? <CustomModalBackdrop {...backdropProps} /> : null} | ||
<FocusTrap | ||
disableEnforceFocus={disableEnforceFocus} | ||
disableAutoFocus={disableAutoFocus} | ||
disableRestoreFocus={disableRestoreFocus} | ||
isEnabled={isTopModal} | ||
open={open} | ||
> | ||
{React.cloneElement(children, childProps)} | ||
</FocusTrap> | ||
</CustomModalRoot> | ||
</Portal> | ||
); | ||
}); | ||
|
||
Modal.propTypes = { | ||
children: PropTypes.element.isRequired, | ||
closeAfterTransition: PropTypes.bool, | ||
container: PropTypes.oneOfType([ | ||
function (props, propName) { | ||
if (props[propName] == null) { | ||
return new Error("Prop '" + propName + "' is required but wasn't specified"); | ||
} else if ( | ||
typeof props[propName] !== 'object' || | ||
props[propName].nodeType !== 1 | ||
) { | ||
return new Error("Expected prop '" + propName + "' to be of type Element"); | ||
} | ||
}, | ||
PropTypes.func, | ||
]), | ||
disableAutoFocus: PropTypes.bool, | ||
disableEnforceFocus: PropTypes.bool, | ||
disableEscapeKeyDown: PropTypes.bool, | ||
disablePortal: PropTypes.bool, | ||
disableRestoreFocus: PropTypes.bool, | ||
disableScrollLock: PropTypes.bool, | ||
hideBackdrop: PropTypes.bool, | ||
keepMounted: PropTypes.bool, | ||
onClose: PropTypes.func, | ||
onTransitionEnter: PropTypes.func, | ||
onTransitionExited: PropTypes.func, | ||
open: PropTypes.bool.isRequired, | ||
}; | ||
|
||
const Backdrop = React.forwardRef((props, ref) => { | ||
const { open, ...other } = props; | ||
return ( | ||
<Fade in={open}> | ||
<div ref={ref} {...other} /> | ||
</Fade> | ||
); | ||
}); | ||
|
||
Backdrop.propTypes = { | ||
open: PropTypes.bool, | ||
}; | ||
|
||
const blue = { | ||
200: '#99CCF3', | ||
400: '#3399FF', | ||
500: '#007FFF', | ||
}; | ||
|
||
const grey = { | ||
50: '#f6f8fa', | ||
100: '#eaeef2', | ||
200: '#d0d7de', | ||
300: '#afb8c1', | ||
400: '#8c959f', | ||
500: '#6e7781', | ||
600: '#57606a', | ||
700: '#424a53', | ||
800: '#32383f', | ||
900: '#24292f', | ||
}; | ||
|
||
const style = (theme) => ({ | ||
position: 'absolute', | ||
top: '50%', | ||
left: '50%', | ||
transform: 'translate(-50%, -50%)', | ||
width: 400, | ||
borderRadius: '12px', | ||
padding: '16px 32px 24px 32px', | ||
backgroundColor: theme.palette.mode === 'dark' ? '#0A1929' : 'white', | ||
boxShadow: `0px 2px 24px ${theme.palette.mode === 'dark' ? '#000' : '#383838'}`, | ||
}); | ||
|
||
const CustomModalRoot = styled('div')` | ||
position: fixed; | ||
z-index: 1300; | ||
inset: 0; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
`; | ||
|
||
const CustomModalBackdrop = styled(Backdrop)` | ||
z-index: -1; | ||
position: fixed; | ||
inset: 0; | ||
background-color: rgb(0 0 0 / 0.5); | ||
-webkit-tap-highlight-color: transparent; | ||
`; | ||
|
||
const TriggerButton = styled(Button)( | ||
({ theme }) => ` | ||
font-family: IBM Plex Sans, sans-serif; | ||
font-size: 0.875rem; | ||
font-weight: 600; | ||
box-sizing: border-box; | ||
min-height: calc(1.5em + 22px); | ||
border-radius: 12px; | ||
padding: 6px 12px; | ||
line-height: 1.5; | ||
background: transparent; | ||
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; | ||
color: ${theme.palette.mode === 'dark' ? grey[100] : grey[900]}; | ||
|
||
&:hover { | ||
background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; | ||
border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; | ||
} | ||
|
||
&:focus-visible { | ||
border-color: ${blue[400]}; | ||
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[500] : blue[200]}; | ||
} | ||
`, | ||
); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe to generalize, if it's code generated, I guess this applies to everything.