index.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. import {
  2. useImperativeHandle,
  3. type ComponentRef,
  4. forwardRef,
  5. useEffect,
  6. useState,
  7. useRef
  8. } from "react";
  9. import {
  10. type LayoutChangeEvent,
  11. PanResponder,
  12. ScrollView,
  13. Animated,
  14. View
  15. } from "react-native";
  16. import type IBottomSheetProps from "./type";
  17. import type {
  18. IBottomSheetRef
  19. } from "./type";
  20. import stylesheet from "./stylesheet";
  21. import {
  22. NCoreUIKitTheme
  23. } from "../../core/hooks";
  24. import {
  25. useSafeAreaInsets
  26. } from "react-native-safe-area-context";
  27. import type {
  28. RefForwardingComponent
  29. } from "../../types";
  30. import type {
  31. IModalRef
  32. } from "../modal/type";
  33. import {
  34. windowHeight,
  35. uuid
  36. } from "../../utils";
  37. import Modal from "../modal";
  38. const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> = ({
  39. renderHeader: RenderHeaderComponent,
  40. renderBottom: RenderBottomComponent,
  41. isForceFullScreenOnSwipe = false,
  42. isCanFullScreenOnSwipe = false,
  43. handleContainerBackgroundColor,
  44. handleHeight: handleHeightProp,
  45. isWrapSafeAreaContext = true,
  46. backgroundColor = "default",
  47. isWorkAsFullScreen = false,
  48. isWorkWithPortal = true,
  49. isCloseOnOverlay = true,
  50. handleContainerSpacing,
  51. isActive: isActiveProp,
  52. handleBackgroundColor,
  53. isAutoHeight = false,
  54. isSwipeClose = true,
  55. isShowHandle = true,
  56. isCanSwipe = true,
  57. onOverlayPressed,
  58. scrollViewProps,
  59. scrollViewStyle,
  60. modalProps,
  61. snapPoint,
  62. customKey,
  63. children,
  64. onClosed,
  65. onOpened,
  66. onClose,
  67. onOpen,
  68. style,
  69. ...props
  70. }, ref) => {
  71. const {
  72. colors,
  73. spaces
  74. } = NCoreUIKitTheme.useContext();
  75. const {
  76. bottom,
  77. top
  78. } = useSafeAreaInsets();
  79. const [
  80. isMeasured,
  81. setIsMeasured
  82. ] = useState(false);
  83. const [
  84. isActive,
  85. setIsActive
  86. ] = useState(isActiveProp === undefined ? false : isActiveProp);
  87. const bottomSheetKey = useRef(customKey ? customKey : uuid());
  88. let bottomSafeArea = isWrapSafeAreaContext ? bottom : 0;
  89. let topSafeArea = isWrapSafeAreaContext ? top : 0;
  90. if(isForceFullScreenOnSwipe) {
  91. topSafeArea = 0;
  92. }
  93. if(!isWorkWithPortal) {
  94. bottomSafeArea = 0;
  95. topSafeArea = 0;
  96. }
  97. const scrollViewRef = useRef<ComponentRef<ScrollView>>(null);
  98. const modalRef = useRef<IModalRef>(null);
  99. const containerHeightRef = useRef(windowHeight);
  100. const animatedTranslateY = useRef(new Animated.Value(snapPoint && !isWorkAsFullScreen ? snapPoint : containerHeightRef.current)).current;
  101. const animatedHeight = useRef(new Animated.Value(
  102. isAutoHeight ? 0 : snapPoint ?? 0
  103. )).current;
  104. const TOP_GRAB_AREA = 140;
  105. const maxHeight = useRef(isWorkAsFullScreen ? containerHeightRef.current - (isWrapSafeAreaContext ? topSafeArea : 0) : containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0));
  106. const heightValue = useRef(isWorkAsFullScreen ? containerHeightRef.current : snapPoint ?? 0);
  107. const initialTranslateY = useRef(0);
  108. const translateYValue = useRef(0);
  109. const contentHeight = useRef(-1);
  110. const initialHeight = useRef(0);
  111. const scrollViewContentHeight = useRef(-1);
  112. const scrollViewLayoutHeight = useRef(-1);
  113. const initialScrollOffset = useRef(0);
  114. const scrollOffset = useRef(0);
  115. const gestureStartY = useRef(0);
  116. const isCanFullScreenOnSwipeRef = useRef(isCanFullScreenOnSwipe);
  117. const isWorkAsFullScreenRef = useRef(isWorkAsFullScreen);
  118. const isSwipeCloseRef = useRef(isSwipeClose);
  119. const isCanSwipeRef = useRef(isCanSwipe);
  120. if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
  121. maxHeight.current = snapPoint;
  122. }
  123. useImperativeHandle(
  124. ref,
  125. () => ({
  126. close: (callback) => {
  127. closeAnimation(undefined, callback);
  128. },
  129. open: () => {
  130. setIsActive(true);
  131. }
  132. }),
  133. []
  134. );
  135. useEffect(() => {
  136. isCanSwipeRef.current = isCanSwipe;
  137. }, [isCanSwipe]);
  138. useEffect(() => {
  139. isSwipeCloseRef.current = isSwipeClose;
  140. }, [isSwipeClose]);
  141. useEffect(() => {
  142. isCanFullScreenOnSwipeRef.current = isCanFullScreenOnSwipe;
  143. }, [isCanFullScreenOnSwipe]);
  144. useEffect(() => {
  145. isWorkAsFullScreenRef.current = isWorkAsFullScreen;
  146. }, [isWorkAsFullScreen]);
  147. useEffect(() => {
  148. if(isMeasured && contentHeight.current !== -1) {
  149. if(isCanFullScreenOnSwipe && !isWorkAsFullScreen) {
  150. maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
  151. } else if(isAutoHeight || !snapPoint) {
  152. maxHeight.current = contentHeight.current;
  153. }
  154. }
  155. }, [isMeasured]);
  156. useEffect(() => {
  157. if(isActive && isMeasured) {
  158. if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
  159. maxHeight.current = snapPoint;
  160. } else if(isWorkAsFullScreen) {
  161. maxHeight.current = containerHeightRef.current - (isWrapSafeAreaContext ? topSafeArea : 0);
  162. } else {
  163. maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0);
  164. }
  165. openAnimation();
  166. }
  167. }, [
  168. isActive,
  169. isMeasured
  170. ]);
  171. if(isActiveProp !== undefined) {
  172. useEffect(() => {
  173. setIsActive(isActiveProp);
  174. }, [isActiveProp]);
  175. }
  176. useEffect(() => {
  177. if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
  178. maxHeight.current = snapPoint;
  179. } else if (!isWorkAsFullScreen) {
  180. maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0);
  181. } else {
  182. maxHeight.current = containerHeightRef.current - (isWrapSafeAreaContext ? topSafeArea : 0);
  183. }
  184. }, [
  185. isForceFullScreenOnSwipe,
  186. isCanFullScreenOnSwipe,
  187. isWrapSafeAreaContext,
  188. isWorkAsFullScreen,
  189. topSafeArea,
  190. snapPoint
  191. ]);
  192. useEffect(() => {
  193. if (!isActive) {
  194. const newSnapValue = isWorkAsFullScreen
  195. ? containerHeightRef.current
  196. : (snapPoint ?? contentHeight.current);
  197. animatedHeight.setValue(isAutoHeight ? 0 : newSnapValue);
  198. animatedTranslateY.setValue(newSnapValue);
  199. heightValue.current = newSnapValue;
  200. translateYValue.current = newSnapValue;
  201. initialHeight.current = newSnapValue;
  202. }
  203. if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
  204. maxHeight.current = snapPoint;
  205. } else if (!isWorkAsFullScreen) {
  206. maxHeight.current = containerHeightRef.current - (
  207. isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0
  208. );
  209. } else {
  210. maxHeight.current = containerHeightRef.current - (
  211. isWrapSafeAreaContext ? topSafeArea : 0
  212. );
  213. }
  214. }, [
  215. snapPoint,
  216. isWorkAsFullScreen
  217. ]);
  218. useEffect(() => {
  219. const listenerAHeightId = animatedHeight.addListener(({
  220. value
  221. }) => {
  222. heightValue.current = value;
  223. });
  224. const listenerTYId = animatedTranslateY.addListener(({
  225. value
  226. }) => {
  227. translateYValue.current = value;
  228. });
  229. return () => {
  230. animatedHeight.removeListener(listenerAHeightId);
  231. animatedTranslateY.removeListener(listenerTYId);
  232. };
  233. }, []);
  234. const openAnimation = () => {
  235. resetState();
  236. if(onOpen) onOpen();
  237. Animated.timing(animatedTranslateY, {
  238. useNativeDriver: false,
  239. duration: 300,
  240. toValue: 0
  241. }).start(({
  242. finished
  243. }) => {
  244. if(finished) {
  245. if(onOpened) onOpened();
  246. }
  247. });
  248. };
  249. const closeAnimation = (toValue?: number, callback?: () => void) => {
  250. if(onClose) onClose();
  251. const currentSnapPoint = isWorkAsFullScreen
  252. ? containerHeightRef.current
  253. : (isAutoHeight || !snapPoint ? contentHeight.current : snapPoint);
  254. Animated.timing(animatedTranslateY, {
  255. toValue: toValue ?? currentSnapPoint,
  256. useNativeDriver: false,
  257. duration: 300
  258. }).start(({
  259. finished
  260. }) => {
  261. if(finished) {
  262. resetState();
  263. setIsActive(false);
  264. if(onClosed) onClosed();
  265. if(callback) callback();
  266. }
  267. });
  268. };
  269. const onLayout = (event: LayoutChangeEvent) => {
  270. if (isMeasured) return;
  271. const {
  272. height
  273. } = event.nativeEvent.layout;
  274. animatedHeight.setValue(height);
  275. contentHeight.current = height;
  276. setIsMeasured(true);
  277. };
  278. const resetState = () => {
  279. animatedHeight.setOffset(0);
  280. animatedTranslateY.setOffset(0);
  281. scrollOffset.current = 0;
  282. initialScrollOffset.current = 0;
  283. const pivot = isWorkAsFullScreen
  284. ? containerHeightRef.current
  285. : (snapPoint ?? contentHeight.current);
  286. initialHeight.current = pivot;
  287. initialTranslateY.current = 0;
  288. heightValue.current = pivot;
  289. translateYValue.current = 0;
  290. };
  291. const panResponder = useRef(
  292. PanResponder.create({
  293. onStartShouldSetPanResponder: () => false,
  294. onMoveShouldSetPanResponderCapture: () => isCanSwipeRef.current ? true : false,
  295. onPanResponderGrant: (evt) => {
  296. if(!isCanSwipeRef.current) return;
  297. gestureStartY.current = evt.nativeEvent.pageY;
  298. animatedTranslateY.stopAnimation((currentY) => {
  299. translateYValue.current = currentY;
  300. });
  301. animatedHeight.stopAnimation((currentH) => {
  302. heightValue.current = currentH;
  303. });
  304. animatedTranslateY.flattenOffset();
  305. animatedHeight.flattenOffset();
  306. initialTranslateY.current = translateYValue.current;
  307. initialScrollOffset.current = scrollOffset.current;
  308. initialHeight.current = heightValue.current;
  309. animatedHeight.setOffset(heightValue.current);
  310. animatedHeight.setValue(0);
  311. animatedTranslateY.setOffset(translateYValue.current);
  312. animatedTranslateY.setValue(0);
  313. },
  314. onPanResponderMove: (_, gestureState) => {
  315. if(!isCanSwipeRef.current) return;
  316. const {
  317. dy
  318. } = gestureState;
  319. const isAtTop = scrollOffset.current <= 0;
  320. const isAtTavan = heightValue.current >= maxHeight.current - 1;
  321. const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
  322. const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;
  323. if (dy > 0 && isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
  324. const currentDelta = -dy;
  325. const initialS = initialScrollOffset.current;
  326. const usedForS = Math.max(currentDelta, -initialS);
  327. scrollOffset.current = initialS + usedForS;
  328. scrollViewRef.current?.scrollTo({
  329. y: scrollOffset.current,
  330. animated: false
  331. });
  332. return;
  333. }
  334. const delta = -dy;
  335. const pivot = snapPoint ?? contentHeight.current;
  336. const initialH = initialHeight.current;
  337. const initialS = initialScrollOffset.current;
  338. const initialT = initialTranslateY.current;
  339. const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);
  340. if (dy < 0) {
  341. let currentDelta = delta;
  342. const effectiveInitialT = Math.max(0, initialT);
  343. const usedForT = Math.min(currentDelta, effectiveInitialT);
  344. animatedTranslateY.setValue(-usedForT);
  345. currentDelta -= usedForT;
  346. if (currentDelta > 0) {
  347. if (initialH < pivot) {
  348. const spaceToPivot = pivot - initialH;
  349. const usedForH = Math.min(currentDelta, spaceToPivot);
  350. animatedHeight.setValue(usedForH);
  351. currentDelta -= usedForH;
  352. }
  353. const isRestoringHeight = initialH < pivot;
  354. const canScroll = isCanFullScreenOnSwipeRef.current || isWorkAsFullScreenRef.current || !isRestoringHeight;
  355. if (currentDelta > 0 && canScroll) {
  356. const remainingScroll = maxS - initialS;
  357. if (remainingScroll > 0) {
  358. const usedForS = Math.min(currentDelta, remainingScroll);
  359. scrollOffset.current = initialS + usedForS;
  360. scrollViewRef.current?.scrollTo({
  361. y: scrollOffset.current,
  362. animated: false
  363. });
  364. currentDelta -= usedForS;
  365. animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
  366. }
  367. }
  368. if (currentDelta > 0) {
  369. if (isCanFullScreenOnSwipeRef.current) {
  370. const totalUsedBefore = (initialH < pivot ? (pivot - initialH) : 0);
  371. animatedHeight.setValue(totalUsedBefore + currentDelta);
  372. } else {
  373. animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
  374. }
  375. }
  376. }
  377. } else {
  378. let currentDelta = delta;
  379. if (initialH > pivot || initialS > 0) {
  380. if (initialS > 0) {
  381. const usedForS = Math.max(currentDelta, -initialS);
  382. scrollOffset.current = initialS + usedForS;
  383. scrollViewRef.current?.scrollTo({
  384. y: scrollOffset.current,
  385. animated: false
  386. });
  387. currentDelta -= usedForS;
  388. animatedHeight.setValue(initialH > pivot ? 0 : pivot - initialH);
  389. }
  390. if (currentDelta < 0 && initialH > pivot) {
  391. const distanceToPivot = pivot - initialH;
  392. const usedForH = Math.max(currentDelta, distanceToPivot);
  393. animatedHeight.setValue(usedForH);
  394. currentDelta -= usedForH;
  395. }
  396. }
  397. if (currentDelta < 0) {
  398. animatedHeight.setValue(initialH > pivot ? (pivot - initialH) : 0);
  399. animatedTranslateY.setValue(-currentDelta);
  400. scrollOffset.current = 0;
  401. scrollViewRef.current?.scrollTo({
  402. y: 0,
  403. animated: false
  404. });
  405. }
  406. }
  407. },
  408. onPanResponderEnd: (_, gestureState) => {
  409. if(!isCanSwipeRef.current) return;
  410. const isAtTop = scrollOffset.current <= 1;
  411. const isAtTavan = heightValue.current >= maxHeight.current - 1;
  412. const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
  413. const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;
  414. if (isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
  415. return;
  416. }
  417. const currentH = (animatedHeight as Animated.Value & {
  418. __getValue(): number;
  419. }).__getValue();
  420. const currentT = (animatedTranslateY as Animated.Value & {
  421. __getValue(): number;
  422. }).__getValue();
  423. animatedHeight.flattenOffset();
  424. animatedTranslateY.flattenOffset();
  425. heightValue.current = currentH;
  426. translateYValue.current = currentT;
  427. const pivot = snapPoint ?? contentHeight.current;
  428. const tavan = maxHeight.current;
  429. const isFastSwipeDown = gestureState.vy > 0.5;
  430. const isFastSwipeUp = gestureState.vy < -0.5;
  431. if (currentT <= 0.5) {
  432. let toValue = pivot;
  433. if (isFastSwipeUp && isCanFullScreenOnSwipeRef.current) {
  434. toValue = tavan;
  435. } else if (isFastSwipeDown) {
  436. toValue = pivot;
  437. } else {
  438. if (!isCanFullScreenOnSwipeRef.current) {
  439. toValue = pivot;
  440. } else {
  441. const totalRange = tavan - pivot;
  442. const distFromPivot = currentH - pivot;
  443. if (distFromPivot < totalRange * 0.33) {
  444. toValue = pivot;
  445. } else if (distFromPivot > totalRange * 0.66) {
  446. toValue = tavan;
  447. } else {
  448. toValue = (tavan - currentH) < (currentH - pivot) ? tavan : pivot;
  449. }
  450. }
  451. }
  452. Animated.spring(animatedHeight, {
  453. useNativeDriver: false,
  454. toValue: toValue,
  455. friction: 10,
  456. tension: 40
  457. }).start();
  458. } else {
  459. let toValueT = 0;
  460. let isClosing = false;
  461. if (!isSwipeCloseRef.current) {
  462. Animated.spring(animatedHeight, {
  463. useNativeDriver: false,
  464. toValue: pivot,
  465. friction: 10,
  466. tension: 40
  467. }).start();
  468. Animated.timing(animatedTranslateY, {
  469. useNativeDriver: false,
  470. duration: 300,
  471. toValue: 0
  472. }).start();
  473. } else {
  474. if (isFastSwipeDown) {
  475. toValueT = pivot + 100;
  476. isClosing = true;
  477. } else {
  478. if (currentT > pivot * 0.5) {
  479. toValueT = pivot + 100;
  480. isClosing = true;
  481. } else {
  482. toValueT = 0;
  483. isClosing = false;
  484. }
  485. }
  486. if(onClose) onClose();
  487. Animated.timing(animatedTranslateY, {
  488. useNativeDriver: false,
  489. toValue: toValueT,
  490. duration: 300
  491. }).start(({
  492. finished
  493. }) => {
  494. if (finished && isClosing) {
  495. setIsActive(false);
  496. if (onClosed) onClosed();
  497. }
  498. });
  499. }
  500. }
  501. },
  502. onPanResponderTerminationRequest: () => false,
  503. onShouldBlockNativeResponder: () => true
  504. })
  505. ).current;
  506. const renderView = () => {
  507. return <Animated.View
  508. {...props}
  509. {...panResponder.panHandlers}
  510. onLayout={onLayout}
  511. style={[
  512. style,
  513. {
  514. height: (isAutoHeight || !snapPoint) && !isMeasured ? "auto" : animatedHeight,
  515. backgroundColor: colors.content.container[backgroundColor],
  516. paddingBottom: bottomSafeArea + spaces.spacingMd,
  517. opacity: isMeasured || !isAutoHeight ? 1 : 0,
  518. paddingRight: spaces.spacingMd,
  519. paddingLeft: spaces.spacingMd,
  520. paddingTop: spaces.spacingMd,
  521. maxHeight: maxHeight.current,
  522. transform: [{
  523. translateY: animatedTranslateY
  524. }]
  525. },
  526. stylesheet.container
  527. ]}
  528. >
  529. {renderHeader()}
  530. <ScrollView
  531. {...scrollViewProps}
  532. onContentSizeChange={(w, h) => {
  533. scrollViewContentHeight.current = h;
  534. }}
  535. onLayout={(e) => {
  536. scrollViewLayoutHeight.current = e.nativeEvent.layout.height;
  537. }}
  538. onStartShouldSetResponderCapture={() => false}
  539. onMoveShouldSetResponderCapture={() => false}
  540. showsHorizontalScrollIndicator={false}
  541. showsVerticalScrollIndicator={false}
  542. scrollEnabled={!isCanSwipe}
  543. scrollEventThrottle={1}
  544. overScrollMode="never"
  545. ref={scrollViewRef}
  546. bounces={false}
  547. style={[
  548. scrollViewStyle
  549. ]}
  550. >
  551. {children}
  552. </ScrollView>
  553. {renderBottom()}
  554. {renderHandle()}
  555. </Animated.View>;
  556. };
  557. const renderHeader = () => {
  558. if(!RenderHeaderComponent) {
  559. return null;
  560. }
  561. return RenderHeaderComponent();
  562. };
  563. const renderBottom = () => {
  564. if(!RenderBottomComponent) {
  565. return null;
  566. }
  567. return <RenderBottomComponent/>;
  568. };
  569. const renderHandle = () => {
  570. if(!isShowHandle || isWorkAsFullScreen) {
  571. return null;
  572. }
  573. const handleHeight = handleHeightProp ? handleHeightProp : 8;
  574. const handleContainerHeight = handleHeight + ((handleContainerSpacing ? spaces[handleContainerSpacing] : spaces.spacingSm) * 2);
  575. const handleBorderRadius = Math.ceil(handleHeight / 2);
  576. return <View
  577. {...panResponder.panHandlers}
  578. onStartShouldSetResponderCapture={() => false}
  579. onMoveShouldSetResponderCapture={() => false}
  580. style={[
  581. stylesheet.handleContainer,
  582. {
  583. backgroundColor: handleContainerBackgroundColor
  584. ? colors.content.container[handleContainerBackgroundColor]
  585. : "transparent",
  586. height: handleContainerHeight,
  587. transform: [{
  588. translateY: -handleContainerHeight
  589. }]
  590. }
  591. ]}
  592. >
  593. <View
  594. style={[
  595. stylesheet.handle,
  596. {
  597. backgroundColor: handleBackgroundColor
  598. ? colors.content.container[handleBackgroundColor]
  599. : colors.content.container.default,
  600. borderRadius: handleBorderRadius,
  601. height: handleHeight
  602. }
  603. ]}
  604. />
  605. </View>;
  606. };
  607. return <Modal
  608. {...modalProps}
  609. isWorkWithPortal={isWorkWithPortal}
  610. key={`${bottomSheetKey}-modal`}
  611. isContentRequired={false}
  612. isActive={isActive}
  613. alignContent="free"
  614. isAnimated={false}
  615. ref={modalRef}
  616. onContainerLayout={(e) => {
  617. const containerLayoutHeight = e.nativeEvent.layout.height;
  618. if (containerLayoutHeight && containerLayoutHeight !== containerHeightRef.current) {
  619. containerHeightRef.current = containerLayoutHeight;
  620. if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
  621. maxHeight.current = snapPoint;
  622. } else if (!isWorkAsFullScreen) {
  623. maxHeight.current = containerLayoutHeight - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0);
  624. } else {
  625. maxHeight.current = containerLayoutHeight;
  626. }
  627. }
  628. }}
  629. overlayProps={{
  630. onStartShouldSetResponderCapture: () => false,
  631. onMoveShouldSetResponderCapture: () => false
  632. }}
  633. onOverlayPress={() => {
  634. if(onOverlayPressed) onOverlayPressed();
  635. if(isCloseOnOverlay) {
  636. closeAnimation();
  637. }
  638. }}
  639. style={[
  640. {
  641. paddingTop: topSafeArea
  642. }
  643. ]}
  644. >
  645. {renderView()}
  646. </Modal>;
  647. };
  648. export default forwardRef(BottomSheet);