@amaui/ui-react
Version:
UI for React
311 lines (308 loc) • 11.9 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
const _excluded = ["tonal", "color", "size", "open", "openDefault", "mainRef", "backgroundRef", "partialyOpened", "minWidth", "maxWidth", "fullScreen", "fullWidth", "background", "modalWrapper", "modalWrapperSurface", "portal", "focus", "freezeScroll", "disableKeyboardClose", "disableBackgroundClose", "backgroundInvisible", "onClose", "NoSurfaceProps", "SurfaceProps", "BackgroundProps", "PortalProps", "TransitionComponentProps", "BackgroundComponent", "TransitionComponent", "Component", "className", "children"];
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
import React from 'react';
import { is, isEnvironment } from '@amaui/utils';
import { style as styleMethod, classNames, useAmauiTheme } from '@amaui/style-react';
import PortalElement from '../Portal';
import FocusElement from '../Focus';
import FadeElement from '../Fade';
import SurfaceElement from '../Surface';
import useMediaQuery from '../useMediaQuery';
import { staticClassName } from '../utils';
const useStyle = styleMethod(theme => ({
root: {
position: 'fixed',
inset: '0',
zIndex: theme.z_index.modal,
pointerEvents: 'none'
},
// Size
size_small: {
padding: theme.methods.space.value(2, 'px'),
borderRadius: `${theme.shape.radius.unit * 2 + theme.shape.radius.unit / 2}px`
},
size_regular: {
padding: theme.methods.space.value('md', 'px'),
borderRadius: `${theme.shape.radius.unit * 3 + theme.shape.radius.unit / 2}px`
},
size_large: {
padding: theme.methods.space.value('lg', 'px'),
borderRadius: `${theme.shape.radius.unit * 4 + theme.shape.radius.unit / 2}px`
},
background: {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
background: 'rgba(0, 0, 0, 0.44)',
zIndex: -1,
pointerEvents: 'all'
},
backgroundInvisible: {
background: 'transparent'
},
modalRoot: {
position: 'relative',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none'
},
surface: {
display: 'flex',
flexDirection: 'column',
minWidth: `280px`,
maxHeight: `calc(100% - ${theme.methods.space.value('xl') * 2}px)`,
margin: theme.methods.space.value('xl', 'px'),
overflow: 'hidden',
pointerEvents: 'all'
},
noSurface: {
pointerEvents: 'all',
maxWidth: '100%'
},
fullScreen: {
width: '100%',
height: '100%',
borderRadius: '0 !important',
maxWidth: 'unset !important',
maxHeight: 'unset !important',
margin: '0 !important'
},
fullWidth: {
width: `calc(100% - ${theme.methods.space.value('xl') * 2}px)`
},
// minWidth
minWidth_xxs: {
minWidth: 'clamp(0px, 320px, calc(100vw - 48px))'
},
minWidth_xs: {
minWidth: 'clamp(0px, 400px, calc(100vw - 48px))'
},
minWidth_sm: {
minWidth: 'clamp(0px, 480px, calc(100vw - 48px))'
},
minWidth_rg: {
minWidth: 'clamp(0px, 560px, calc(100vw - 48px))'
},
minWidth_lg: {
minWidth: 'clamp(0px, 800px, calc(100vw - 48px))'
},
minWidth_xl: {
minWidth: 'clamp(0px, 1120px, calc(100vw - 48px))'
},
minWidth_xxl: {
minWidth: 'clamp(0px, 1360px, calc(100vw - 48px))'
},
minWidth_unset: {
minWidth: `unset`
},
// maxWidth
maxWidth_xxs: {
maxWidth: `320px`
},
maxWidth_xs: {
maxWidth: `400px`
},
maxWidth_sm: {
maxWidth: `480px`
},
maxWidth_rg: {
maxWidth: `560px`
},
maxWidth_lg: {
maxWidth: `800px`
},
maxWidth_xl: {
maxWidth: `1120px`
},
maxWidth_xxl: {
maxWidth: `1360px`
},
maxWidth_unset: {
maxWidth: `unset`
}
}), {
name: 'amaui-Modal'
});
let MODALS_OPEN = 0;
// To do
// 1. Add padding right for freezeScroll for the scroll bar if it exists and width that it is
const Modal = /*#__PURE__*/React.forwardRef((props_, ref) => {
const theme = useAmauiTheme();
const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiModal?.props?.default), props_), [props_]);
const Surface = React.useMemo(() => theme?.elements?.Surface || SurfaceElement, [theme]);
const Portal = React.useMemo(() => theme?.elements?.Portal || PortalElement, [theme]);
const Focus = React.useMemo(() => theme?.elements?.Focus || FocusElement, [theme]);
const Fade = React.useMemo(() => theme?.elements?.Fade || FadeElement, [theme]);
const {
tonal = true,
color = 'primary',
size = 'regular',
open: open_,
openDefault,
mainRef,
backgroundRef,
partialyOpened,
minWidth: minWidth_,
maxWidth: maxWidth_ = 'rg',
fullScreen,
fullWidth,
background = true,
modalWrapper = true,
modalWrapperSurface = true,
portal = true,
focus = true,
freezeScroll = true,
disableKeyboardClose,
disableBackgroundClose,
backgroundInvisible,
onClose: onClose_,
NoSurfaceProps,
SurfaceProps,
BackgroundProps,
PortalProps: PortalProps_,
TransitionComponentProps,
BackgroundComponent = Fade,
TransitionComponent = Fade,
Component = 'div',
className,
children
} = props,
other = _objectWithoutProperties(props, _excluded);
const {
classes
} = useStyle();
const [open, setOpen] = React.useState(openDefault !== undefined ? openDefault : open_);
const [inProp, setInProp] = React.useState(open_);
const [portalElement, setPortalElement] = React.useState();
const refs = {
root: React.useRef(undefined),
focus: React.useRef(undefined),
open: React.useRef(open),
freezeScroll: React.useRef(undefined),
interval: React.useRef(undefined)
};
const mobile = useMediaQuery('(max-width: 767px)', {
element: refs.root.current
});
const minWidth = minWidth_ !== undefined ? minWidth_ : !mobile ? 'sm' : undefined;
refs.open.current = open;
refs.freezeScroll.current = freezeScroll;
const modal = {
open: () => {
MODALS_OPEN++;
const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined;
if (freezeScroll) rootDocument.body.style.overflow = 'hidden';
},
close: () => {
MODALS_OPEN--;
const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined;
if (MODALS_OPEN <= 0 && freezeScroll) rootDocument.body.style.removeProperty('overflow');
}
};
let maxWidth = maxWidth_;
if (fullWidth) maxWidth = undefined;
const onClose = () => {
if (is('function', onClose_) && open) onClose_();
};
const onKeyDown = event => {
if (!refs.open.current) return;
if (event.key === 'Escape' && !disableKeyboardClose) {
event.stopPropagation();
onClose();
}
};
React.useEffect(() => {
const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined;
if (open) modal.open();
// Bug clean up fix
refs.interval.current = setInterval(() => {
if (MODALS_OPEN <= 0 && refs.freezeScroll.current && rootDocument.body.style.overflow === 'hidden') {
rootDocument.body.style.removeProperty('overflow');
}
}, 1400);
return () => {
if (open) modal.close();
clearInterval(refs.interval.current);
};
}, []);
React.useEffect(() => {
if (open_ && !open) {
setOpen(true);
setInProp(true);
modal.open();
} else if (!open_ && open) modalWrapper ? setInProp(false) : setOpen(false);
}, [open_]);
React.useEffect(() => {
if (open) refs.focus.current?.focus();
}, [open]);
const onExited = value => {
setOpen(false);
modal.close();
if (is('function', TransitionComponentProps?.onExited)) TransitionComponentProps?.onExited(value);
};
if (!open && !partialyOpened) return null;
const PortalComponent = portal ? Portal : React.Fragment;
let PortalProps = {};
if (portal) {
const rootDocumentElement = isEnvironment('browser') ? (portalElement || refs.root.current)?.ownerDocument || window.document : undefined;
if (isEnvironment('browser')) PortalProps.element = rootDocumentElement.body;
PortalProps = _objectSpread(_objectSpread({}, PortalProps), PortalProps_);
}
const FocusComponent = focus ? Focus : React.Fragment;
const FocusProps = focus ? {
ref: refs.focus,
onKeyDown
} : {};
const MainProps = {
role: 'dialog',
'aria-labelledby': 'amaui-modal-title',
'aria-describedby': 'amaui-modal-text',
'aria-modal': 'true',
'aria-live': 'assertive'
};
let Main = children && /*#__PURE__*/React.cloneElement(children, _objectSpread({}, MainProps));
if (modalWrapper) Main = /*#__PURE__*/React.createElement("div", _extends({}, MainProps, {
className: classNames([staticClassName('Modal', theme) && ['amaui-Modal-modal-root'], classes.modalRoot])
}), /*#__PURE__*/React.createElement(TransitionComponent, _extends({
in: inProp
}, TransitionComponentProps, {
onExited: onExited,
add: true
}), !modalWrapperSurface ? /*#__PURE__*/React.createElement("div", {
className: classNames([staticClassName('Modal', theme) && ['amaui-Modal-no-surface'], NoSurfaceProps?.className, classes.noSurface])
}, children) : /*#__PURE__*/React.createElement(Surface, _extends({
ref: mainRef,
tonal: tonal,
color: color,
tabIndex: "-1"
}, SurfaceProps, {
className: classNames([staticClassName('Modal', theme) && ['amaui-Modal-surface', `amaui-Modal-size-${size}`], classes.surface, classes[`minWidth_${minWidth}`], classes[`maxWidth_${maxWidth}`], classes[`size_${size}`], fullScreen && classes.fullScreen, fullWidth && classes.fullWidth, SurfaceProps?.className]),
onKeyDown: onKeyDown
}), children)));
return /*#__PURE__*/React.createElement(PortalComponent, PortalProps, /*#__PURE__*/React.createElement(Component, _extends({
ref: item => {
if (ref) {
if (is('function', ref)) ref(item);else ref.current = item;
}
refs.root.current = item;
setPortalElement(item);
},
className: classNames([staticClassName('Modal', theme) && ['amaui-Modal-root', open && `amaui-Modal-open`, focus && `amaui-Modal-focus`], className, classes.root])
}, other), /*#__PURE__*/React.createElement(FocusComponent, FocusProps, background && /*#__PURE__*/React.createElement(BackgroundComponent, _extends({
in: inProp,
add: true
}, BackgroundProps), /*#__PURE__*/React.createElement("div", {
ref: backgroundRef,
onClick: () => !disableBackgroundClose && onClose(),
className: classNames([staticClassName('Modal', theme) && ['amaui-Modal-background', backgroundInvisible && 'amaui-Modal-background-invisible'], classes.background, backgroundInvisible && classes.backgroundInvisible])
})), Main)));
});
Modal.displayName = 'amaui-Modal';
export default Modal;