@bianic-ui/modal
Version:
An accessible dialog (modal) component for React & Bianic UI
332 lines (294 loc) • 8.69 kB
JavaScript
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { CloseButton } from "@bianic-ui/close-button";
import { FocusLock } from "@bianic-ui/focus-lock";
import { Portal } from "@bianic-ui/portal";
import { bianic, forwardRef, useMultiStyleConfig, StylesProvider, useStyles } from "@bianic-ui/system";
import { callAllHandlers, cx, __DEV__, createContext } from "@bianic-ui/utils";
import * as React from "react";
import { RemoveScroll } from "react-remove-scroll";
import { useModal } from "./use-modal";
var [ModalContextProvider, useModalContext] = createContext({
strict: true,
name: "ModalContext",
errorMessage: "useModalContext: `context` is undefined. Seems you forgot to wrap modal components in `<Modal />`"
});
export { ModalContextProvider, useModalContext };
/**
* Modal
*
* React component that provides context, theming, and accessbility properties
* to all other modal components.
*
* It doesn't render any DOM node.
*/
export var Modal = props => {
var {
getContainer,
children,
autoFocus,
trapFocus,
initialFocusRef,
finalFocusRef,
returnFocusOnClose,
blockScrollOnMount,
allowPinchZoom,
preserveScrollBarGap
} = props;
var styles = useMultiStyleConfig("Modal", props);
var modal = useModal(props);
var context = _extends({}, modal, {
autoFocus,
trapFocus,
initialFocusRef,
finalFocusRef,
returnFocusOnClose,
blockScrollOnMount,
allowPinchZoom,
preserveScrollBarGap
});
if (!context.isOpen) return null;
return /*#__PURE__*/React.createElement(ModalContextProvider, {
value: context
}, /*#__PURE__*/React.createElement(Portal, {
getContainer: getContainer
}, /*#__PURE__*/React.createElement(StylesProvider, {
value: styles
}, children)));
};
Modal.defaultProps = {
returnFocusOnClose: true,
scrollBehavior: "outside",
trapFocus: true,
autoFocus: true,
blockScrollOnMount: true,
allowPinchZoom: false
};
if (__DEV__) {
Modal.displayName = "Modal";
}
/**
* ModalContent
*
* React component used to group modal's content. It has all the
* necessary `aria-*` properties to indicate that it's a modal modal
*/
export var ModalContent = /*#__PURE__*/forwardRef(function ModalContent(props, ref) {
var {
className,
children
} = props,
otherProps = _objectWithoutPropertiesLoose(props, ["className", "children"]);
var {
getContentProps
} = useModalContext();
var content = getContentProps(otherProps, ref);
var _className = cx("bianic-modal__content", className);
var styles = useStyles();
return /*#__PURE__*/React.createElement(bianic.section, _extends({
className: _className
}, content, {
__css: _extends({
display: "flex",
flexDirection: "column",
position: "relative",
width: "100%",
outline: 0
}, styles.content)
}), children);
});
if (__DEV__) {
ModalContent.displayName = "ModalContent";
}
/**
* ModalOverlay
*
* React component that renders a backdrop behind the modal. It's
* also used as a wrapper for the modal content for better positioning.
*
* @see Docs https://bianic-ui.com/components/modal
*/
export var ModalOverlay = /*#__PURE__*/forwardRef(function ModalOverlay(props, ref) {
var {
className,
children
} = props,
rest = _objectWithoutPropertiesLoose(props, ["className", "children"]);
var {
getOverlayProps,
autoFocus,
trapFocus,
dialogRef,
initialFocusRef,
blockScrollOnMount,
allowPinchZoom,
finalFocusRef,
returnFocusOnClose,
preserveScrollBarGap
} = useModalContext();
var overlay = getOverlayProps(rest, ref);
var _className = cx("bianic-modal__overlay", className);
var styles = useStyles();
return /*#__PURE__*/React.createElement(FocusLock, {
autoFocus: autoFocus,
isDisabled: !trapFocus,
initialFocusRef: initialFocusRef,
finalFocusRef: finalFocusRef,
restoreFocus: returnFocusOnClose,
contentRef: dialogRef
}, /*#__PURE__*/React.createElement(RemoveScroll, {
removeScrollBar: !preserveScrollBarGap,
allowPinchZoom: allowPinchZoom,
enabled: blockScrollOnMount
}, /*#__PURE__*/React.createElement(bianic.div, _extends({}, overlay, {
className: _className,
__css: _extends({
width: "100vw",
height: "100vh",
position: "fixed",
left: 0,
top: 0
}, styles.overlay)
}), children)));
});
if (__DEV__) {
ModalOverlay.displayName = "ModalOverlay";
}
/**
* ModalHeader
*
* React component that houses the title of the modal.
*
* @see Docs https://bianic-ui.com/components/modal
*/
export var ModalHeader = /*#__PURE__*/forwardRef(function ModalHeader(props, ref) {
var {
className
} = props,
rest = _objectWithoutPropertiesLoose(props, ["className"]);
var {
headerId,
setHeaderMounted
} = useModalContext();
/**
* Notify us if this component was rendered or used
* so we can append `aria-labelledby` automatically
*/
React.useEffect(() => {
setHeaderMounted(true);
return () => setHeaderMounted(false);
}, [setHeaderMounted]);
var _className = cx("bianic-modal__header", className);
var styles = useStyles();
return /*#__PURE__*/React.createElement(bianic.header, _extends({
ref: ref,
className: _className,
id: headerId
}, rest, {
__css: _extends({
flex: 0
}, styles.header)
}));
});
if (__DEV__) {
ModalHeader.displayName = "ModalHeader";
}
/**
* ModalBody
*
* React component that houses the main content of the modal.
*
* @see Docs https://bianic-ui.com/components/modal
*/
export var ModalBody = /*#__PURE__*/forwardRef(function ModalBody(props, ref) {
var {
className
} = props,
rest = _objectWithoutPropertiesLoose(props, ["className"]);
var {
bodyId,
setBodyMounted
} = useModalContext();
/**
* Notify us if this component was rendered or used
* so we can append `aria-describedby` automatically
*/
React.useEffect(() => {
setBodyMounted(true);
return () => setBodyMounted(false);
}, [setBodyMounted]);
var _className = cx("bianic-modal__body", className);
var styles = useStyles();
return /*#__PURE__*/React.createElement(bianic.div, _extends({
ref: ref,
className: _className,
id: bodyId
}, rest, {
__css: styles.body
}));
});
if (__DEV__) {
ModalBody.displayName = "ModalBody";
}
/**
* ModalFooter
*
* React component that houses the action buttons of the modal.
*
* @see Docs https://bianic-ui.com/components/modal
*/
export var ModalFooter = /*#__PURE__*/forwardRef(function ModalFooter(props, ref) {
var {
className
} = props,
rest = _objectWithoutPropertiesLoose(props, ["className"]);
var _className = cx("bianic-modal__footer", className);
var styles = useStyles();
return /*#__PURE__*/React.createElement(bianic.footer, _extends({
ref: ref
}, rest, {
__css: _extends({
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
flex: 0
}, styles.footer),
className: _className
}));
});
if (__DEV__) {
ModalFooter.displayName = "ModalFooter";
}
/**
* ModalCloseButton
*
* React component used closes the modal. You don't need
* to pass the `onClick` to it, it's reads the `onClose` action from the
* modal context.
*/
export var ModalCloseButton = /*#__PURE__*/forwardRef(function ModalCloseButton(props, ref) {
var {
onClick,
className
} = props,
rest = _objectWithoutPropertiesLoose(props, ["onClick", "className"]);
var {
onClose
} = useModalContext();
var _className = cx("bianic-modal__close-btn", className);
return /*#__PURE__*/React.createElement(CloseButton, _extends({
ref: ref,
position: "absolute",
top: "8px",
right: "12px",
className: _className,
onClick: callAllHandlers(onClick, event => {
event.stopPropagation();
onClose();
})
}, rest));
});
if (__DEV__) {
ModalCloseButton.displayName = "ModalCloseButton";
}
//# sourceMappingURL=modal.js.map