create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
134 lines (118 loc) • 3.59 kB
JavaScript
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as React from 'react';
import ModalPortal from './ModalPortal';
import ModalAnimation from './ModalAnimation';
import ModalContent from './ModalContent';
import ModalFocusTrap from './ModalFocusTrap';
export type ModalProps = {|
animationType?: 'none' | 'slide' | 'fade',
children: any,
hardwareAccelerated?: ?boolean,
onDismiss?: ?() => mixed,
onOrientationChange?: ?(e: {| orientation: 'portrait' | 'landscape' |}) => void,
onRequestClose?: ?() => void,
onShow?: ?() => void,
presentationStyle?: ?('fullScreen' | 'pageSheet' | 'formSheet' | 'overFullScreen'),
statusBarTranslucent?: ?boolean,
supportedOrientations?: ?Array<
'portrait' | 'portrait-upside-down' | 'landscape' | 'landscape-left' | 'landscape-right'
>,
transparent?: ?boolean,
visible?: ?boolean
|};
let uniqueModalIdentifier = 0;
const activeModalStack = [];
const activeModalListeners = {};
function notifyActiveModalListeners() {
if (activeModalStack.length === 0) {
return;
}
const activeModalId = activeModalStack[activeModalStack.length - 1];
activeModalStack.forEach((modalId) => {
if (modalId in activeModalListeners) {
activeModalListeners[modalId](modalId === activeModalId);
}
});
}
function removeActiveModal(modalId) {
if (modalId in activeModalListeners) {
// Before removing this listener we should probably tell it
// that it's no longer the active modal for sure.
activeModalListeners[modalId](false);
delete activeModalListeners[modalId];
}
const index = activeModalStack.indexOf(modalId);
if (index !== -1) {
activeModalStack.splice(index, 1);
notifyActiveModalListeners();
}
}
function addActiveModal(modalId, listener) {
removeActiveModal(modalId);
activeModalStack.push(modalId);
activeModalListeners[modalId] = listener;
notifyActiveModalListeners();
}
const Modal: React.AbstractComponent<
ModalProps,
React.ElementRef<typeof ModalContent>
> = React.forwardRef((props, forwardedRef) => {
const {
animationType,
children,
onDismiss,
onRequestClose,
onShow,
transparent,
visible = true
} = props;
// Set a unique model identifier so we can correctly route
// dismissals and check the layering of modals.
const modalId = React.useMemo(() => uniqueModalIdentifier++, []);
const [isActive, setIsActive] = React.useState(false);
const onDismissCallback = React.useCallback(() => {
removeActiveModal(modalId);
if (onDismiss) {
onDismiss();
}
}, [modalId, onDismiss]);
const onShowCallback = React.useCallback(() => {
addActiveModal(modalId, setIsActive);
if (onShow) {
onShow();
}
}, [modalId, onShow]);
React.useEffect(() => {
return () => removeActiveModal(modalId);
}, [modalId]);
return (
<ModalPortal>
<ModalAnimation
animationType={animationType}
onDismiss={onDismissCallback}
onShow={onShowCallback}
visible={visible}
>
<ModalFocusTrap active={isActive}>
<ModalContent
active={isActive}
onRequestClose={onRequestClose}
ref={forwardedRef}
transparent={transparent}
>
{children}
</ModalContent>
</ModalFocusTrap>
</ModalAnimation>
</ModalPortal>
);
});
export default Modal;