Modal.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. const AppContainer = require('../ReactNative/AppContainer');
  12. const I18nManager = require('../ReactNative/I18nManager');
  13. const PropTypes = require('prop-types');
  14. const React = require('react');
  15. const ScrollView = require('../Components/ScrollView/ScrollView');
  16. const StyleSheet = require('../StyleSheet/StyleSheet');
  17. const View = require('../Components/View/View');
  18. import type {ViewProps} from '../Components/View/ViewPropTypes';
  19. import type {DirectEventHandler} from '../Types/CodegenTypes';
  20. import type EmitterSubscription from '../vendor/emitter/EmitterSubscription';
  21. import RCTModalHostView from './RCTModalHostViewNativeComponent';
  22. /**
  23. * The Modal component is a simple way to present content above an enclosing view.
  24. *
  25. * See https://reactnative.dev/docs/modal.html
  26. */
  27. // In order to route onDismiss callbacks, we need to uniquely identifier each
  28. // <Modal> on screen. There can be different ones, either nested or as siblings.
  29. // We cannot pass the onDismiss callback to native as the view will be
  30. // destroyed before the callback is fired.
  31. let uniqueModalIdentifier = 0;
  32. type OrientationChangeEvent = $ReadOnly<{|
  33. orientation: 'portrait' | 'landscape',
  34. |}>;
  35. export type Props = $ReadOnly<{|
  36. ...ViewProps,
  37. /**
  38. * The `animationType` prop controls how the modal animates.
  39. *
  40. * See https://reactnative.dev/docs/modal.html#animationtype
  41. */
  42. animationType?: ?('none' | 'slide' | 'fade'),
  43. /**
  44. * The `presentationStyle` prop controls how the modal appears.
  45. *
  46. * See https://reactnative.dev/docs/modal.html#presentationstyle
  47. */
  48. presentationStyle?: ?(
  49. | 'fullScreen'
  50. | 'pageSheet'
  51. | 'formSheet'
  52. | 'overFullScreen'
  53. ),
  54. /**
  55. * The `transparent` prop determines whether your modal will fill the
  56. * entire view.
  57. *
  58. * See https://reactnative.dev/docs/modal.html#transparent
  59. */
  60. transparent?: ?boolean,
  61. /**
  62. * The `statusBarTranslucent` prop determines whether your modal should go under
  63. * the system statusbar.
  64. *
  65. * See https://reactnative.dev/docs/modal.html#transparent
  66. */
  67. statusBarTranslucent?: ?boolean,
  68. /**
  69. * The `hardwareAccelerated` prop controls whether to force hardware
  70. * acceleration for the underlying window.
  71. *
  72. * This prop works only on Android.
  73. *
  74. * See https://reactnative.dev/docs/modal.html#hardwareaccelerated
  75. */
  76. hardwareAccelerated?: ?boolean,
  77. /**
  78. * The `visible` prop determines whether your modal is visible.
  79. *
  80. * See https://reactnative.dev/docs/modal.html#visible
  81. */
  82. visible?: ?boolean,
  83. /**
  84. * The `onRequestClose` callback is called when the user taps the hardware
  85. * back button on Android or the menu button on Apple TV.
  86. *
  87. * This is required on Apple TV and Android.
  88. *
  89. * See https://reactnative.dev/docs/modal.html#onrequestclose
  90. */
  91. onRequestClose?: ?DirectEventHandler<null>,
  92. /**
  93. * The `onShow` prop allows passing a function that will be called once the
  94. * modal has been shown.
  95. *
  96. * See https://reactnative.dev/docs/modal.html#onshow
  97. */
  98. onShow?: ?DirectEventHandler<null>,
  99. /**
  100. * The `onDismiss` prop allows passing a function that will be called once
  101. * the modal has been dismissed.
  102. *
  103. * See https://reactnative.dev/docs/modal.html#ondismiss
  104. */
  105. onDismiss?: ?() => mixed,
  106. /**
  107. * The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations.
  108. *
  109. * See https://reactnative.dev/docs/modal.html#supportedorientations
  110. */
  111. supportedOrientations?: ?$ReadOnlyArray<
  112. | 'portrait'
  113. | 'portrait-upside-down'
  114. | 'landscape'
  115. | 'landscape-left'
  116. | 'landscape-right',
  117. >,
  118. /**
  119. * The `onOrientationChange` callback is called when the orientation changes while the modal is being displayed.
  120. *
  121. * See https://reactnative.dev/docs/modal.html#onorientationchange
  122. */
  123. onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
  124. |}>;
  125. class Modal extends React.Component<Props> {
  126. static defaultProps: {|hardwareAccelerated: boolean, visible: boolean|} = {
  127. visible: true,
  128. hardwareAccelerated: false,
  129. };
  130. static contextTypes: any | {|rootTag: React$PropType$Primitive<number>|} = {
  131. rootTag: PropTypes.number,
  132. };
  133. _identifier: number;
  134. _eventSubscription: ?EmitterSubscription;
  135. constructor(props: Props) {
  136. super(props);
  137. Modal._confirmProps(props);
  138. this._identifier = uniqueModalIdentifier++;
  139. }
  140. static childContextTypes:
  141. | any
  142. | {|virtualizedList: React$PropType$Primitive<any>|} = {
  143. virtualizedList: PropTypes.object,
  144. };
  145. getChildContext(): {|virtualizedList: null|} {
  146. // Reset the context so VirtualizedList doesn't get confused by nesting
  147. // in the React tree that doesn't reflect the native component hierarchy.
  148. return {
  149. virtualizedList: null,
  150. };
  151. }
  152. componentWillUnmount() {
  153. if (this.props.onDismiss != null) {
  154. this.props.onDismiss();
  155. }
  156. }
  157. UNSAFE_componentWillReceiveProps(nextProps: Props) {
  158. Modal._confirmProps(nextProps);
  159. }
  160. static _confirmProps(props: Props) {
  161. if (
  162. props.presentationStyle &&
  163. props.presentationStyle !== 'overFullScreen' &&
  164. props.transparent
  165. ) {
  166. console.warn(
  167. `Modal with '${
  168. props.presentationStyle
  169. }' presentation style and 'transparent' value is not supported.`,
  170. );
  171. }
  172. }
  173. render(): React.Node {
  174. if (this.props.visible !== true) {
  175. return null;
  176. }
  177. const containerStyles = {
  178. backgroundColor: this.props.transparent ? 'transparent' : 'white',
  179. };
  180. let animationType = this.props.animationType || 'none';
  181. let presentationStyle = this.props.presentationStyle;
  182. if (!presentationStyle) {
  183. presentationStyle = 'fullScreen';
  184. if (this.props.transparent) {
  185. presentationStyle = 'overFullScreen';
  186. }
  187. }
  188. const innerChildren = __DEV__ ? (
  189. <AppContainer rootTag={this.context.rootTag}>
  190. {this.props.children}
  191. </AppContainer>
  192. ) : (
  193. this.props.children
  194. );
  195. return (
  196. <RCTModalHostView
  197. animationType={animationType}
  198. presentationStyle={presentationStyle}
  199. transparent={this.props.transparent}
  200. hardwareAccelerated={this.props.hardwareAccelerated}
  201. onRequestClose={this.props.onRequestClose}
  202. onShow={this.props.onShow}
  203. statusBarTranslucent={this.props.statusBarTranslucent}
  204. identifier={this._identifier}
  205. style={styles.modal}
  206. onStartShouldSetResponder={this._shouldSetResponder}
  207. supportedOrientations={this.props.supportedOrientations}
  208. onOrientationChange={this.props.onOrientationChange}>
  209. <ScrollView.Context.Provider value={null}>
  210. <View style={[styles.container, containerStyles]}>
  211. {innerChildren}
  212. </View>
  213. </ScrollView.Context.Provider>
  214. </RCTModalHostView>
  215. );
  216. }
  217. // We don't want any responder events bubbling out of the modal.
  218. _shouldSetResponder(): boolean {
  219. return true;
  220. }
  221. }
  222. const side = I18nManager.getConstants().isRTL ? 'right' : 'left';
  223. const styles = StyleSheet.create({
  224. modal: {
  225. position: 'absolute',
  226. },
  227. container: {
  228. /* $FlowFixMe(>=0.111.0 site=react_native_fb) This comment suppresses an
  229. * error found when Flow v0.111 was deployed. To see the error, delete this
  230. * comment and run Flow. */
  231. [side]: 0,
  232. top: 0,
  233. flex: 1,
  234. },
  235. });
  236. module.exports = Modal;