diff --git a/maestro/images/expected/android/bottom_sheet_modal_custom.png b/maestro/images/expected/android/bottom_sheet_modal_custom.png
index c0b019750..919a6de14 100644
Binary files a/maestro/images/expected/android/bottom_sheet_modal_custom.png and b/maestro/images/expected/android/bottom_sheet_modal_custom.png differ
diff --git a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md
index 6cde9d37b..ece410d74 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md
+++ b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Fixed
+
+- Fixed the “non‑worklet function called on the UI thread” error in bottom sheet coming from @gorhom/bottom-sheet usage.
+- Fixed ExpandingDrawer to open correctly and show all elements.
+
+### Changed
+
+- Improved performance by memoizing all callbacks with useCallback and useMemo across all components.
+- BottomSheet now uses BottomSheetScrollView instead of BottomSheetView, enabling proper scrolling in expanded states.
+- NativeBottomSheet now explicitly calculates snapPoints with 90% screen height cap for predictable sizing.
+- CustomModalSheet now measures content height dynamically with 90% screen height cap, removing offscreen SafeAreaView measurement.
+
## [5.0.3] - 2025-12-15
- Updated react-native-reanimated to v3.17.5. This addresses compatibility issues with React Native 0.78 and later versions.
diff --git a/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml b/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml
index cd7d34ef0..ac67b21a7 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml
+++ b/packages/pluggableWidgets/bottom-sheet-native/e2e/specs/maestro/Modal_basic_non_native.yaml
@@ -10,10 +10,12 @@ appId: "${APP_ID}"
text: "Modal basic non native"
- tapOn:
text: "Open"
-# Because the image loading can take some time due to resources on emulator, we need to wait for the image to be visible
-- extendedWaitUntil:
- visible: randText # Any random text that does not exist in the UI
- optional: true # This should be true so that the test won't fail
- timeout: 10000 # 10 seconds
+
+# Wait until the sheet is actually open (button label changes)
+- extendedWaitUntil:
+ visible: randText
+ optional: true
+ timeout: 25000
+
- takeScreenshot:
path: "maestro/images/actual/${PLATFORM}/bottom_sheet_modal_basic_non_native"
diff --git a/packages/pluggableWidgets/bottom-sheet-native/package.json b/packages/pluggableWidgets/bottom-sheet-native/package.json
index 11c4aaab3..ab2055e6f 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/package.json
+++ b/packages/pluggableWidgets/bottom-sheet-native/package.json
@@ -1,7 +1,7 @@
{
"name": "bottom-sheet-native",
"widgetName": "BottomSheet",
- "version": "5.0.3",
+ "version": "5.0.4",
"license": "Apache-2.0",
"repository": {
"type": "git",
diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap b/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap
index 0d20fc1cf..512486a56 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap
+++ b/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/__snapshots__/BottomSheet.spec.tsx.snap
@@ -2,104 +2,159 @@
exports[`Bottom sheet renders a custom bottom action sheet for ios (Basic modal) with custom style 1`] = `null`;
-exports[`Bottom sheet renders a custom modal 1`] = `
+exports[`Bottom sheet renders a custom modal 1`] = `null`;
+
+exports[`Bottom sheet renders a expanding 1`] = `
-
-
-`;
-
-exports[`Bottom sheet renders a expanding 1`] = `
-
-
Header
-
- Content
-
+
+
+ Content
+
+
`;
exports[`Bottom sheet renders a expanding fullscreen 1`] = `
-
+ >
+
+
+ Header
+
+
+
+
+ Content
+
+
+
+
+ Full screen content
+
+
+
`;
exports[`Bottom sheet renders a expanding fullscreen with custom styles 1`] = `
-
+ >
+
+
+ Header
+
+
+
+
+ Content
+
+
+
+
+ Full screen content
+
+
+
`;
diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx
index 19d21f496..cb9a1925c 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx
+++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/CustomModalSheet.tsx
@@ -1,90 +1,107 @@
-import { ReactElement, ReactNode, useEffect, useRef, useState } from "react";
-import { InteractionManager, LayoutChangeEvent, Modal, Pressable, SafeAreaView, StyleSheet, View } from "react-native";
-import BottomSheet, { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetView } from "@gorhom/bottom-sheet";
+import { ReactElement, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { Dimensions, InteractionManager, LayoutChangeEvent, Modal, Pressable } from "react-native";
+import BottomSheet, {
+ BottomSheetBackdrop,
+ BottomSheetBackdropProps,
+ BottomSheetScrollView
+} from "@gorhom/bottom-sheet";
import { EditableValue, ValueStatus } from "mendix";
-import { BottomSheetStyle, defaultPaddings } from "../ui/Styles";
+import { BottomSheetStyle } from "../ui/Styles";
interface CustomModalSheetProps {
triggerAttribute?: EditableValue;
content?: ReactNode;
styles: BottomSheetStyle;
}
+
let lastIndexRef = -1;
export const CustomModalSheet = (props: CustomModalSheetProps): ReactElement => {
const bottomSheetRef = useRef(null);
- const [height, setHeight] = useState(0);
+ const [contentHeight, setContentHeight] = useState(0);
const [currentStatus, setCurrentStatus] = useState(false);
+
const isAvailable = props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available;
- const onLayoutFullscreenHandler = (event: LayoutChangeEvent): void => {
- const layoutHeight = event.nativeEvent.layout.height;
- if (layoutHeight > 0 && layoutHeight !== height) {
- setHeight(layoutHeight);
- }
- };
+ const isOpen =
+ props.triggerAttribute &&
+ props.triggerAttribute.status === ValueStatus.Available &&
+ props.triggerAttribute.value;
- useEffect(() => {
- if (!isAvailable) {
- return;
- }
- if (props.triggerAttribute?.value && !currentStatus) {
- InteractionManager.runAfterInteractions(() => setCurrentStatus(true));
- } else if (!props.triggerAttribute?.value && currentStatus) {
- bottomSheetRef.current?.close();
- setCurrentStatus(false);
+ const onContentLayoutHandler = useCallback(
+ (event: LayoutChangeEvent): void => {
+ const layoutHeight = event.nativeEvent.layout.height;
+ if (layoutHeight > 0 && layoutHeight !== contentHeight) {
+ setContentHeight(layoutHeight);
+ }
+ },
+ [contentHeight]
+ );
+
+ const close = useCallback(() => {
+ bottomSheetRef.current?.close();
+ }, []);
+
+ const renderBackdrop = useCallback(
+ (backdropProps: BottomSheetBackdropProps) => (
+
+
+
+ ),
+ [close]
+ );
+
+ const snapPoints = useMemo(() => {
+ if (contentHeight === 0) {
+ return [1]; // During measurement
}
- }, [props.triggerAttribute, currentStatus]);
- if (height === 0) {
- return (
-
-
-
- );
- }
+ // Use actual measured content height, cap at 90% screen
+ const maxHeight = Dimensions.get("screen").height * 0.9;
+ const snapHeight = Math.min(contentHeight, maxHeight);
+ return [snapHeight];
+ }, [contentHeight]);
- const snapPoints = [height - Number(defaultPaddings.paddingBottom)];
+ const handleSheetChanges = useCallback(
+ (index: number) => {
+ if (!isAvailable) {
+ return;
+ }
- const isOpen =
- props.triggerAttribute &&
- props.triggerAttribute.status === ValueStatus.Available &&
- props.triggerAttribute.value;
+ const hasOpened = lastIndexRef === -1 && index === 0;
+ const hasClosed = index === -1;
+ lastIndexRef = index;
- const renderBackdrop = (backdropProps: BottomSheetBackdropProps) => (
-
-
-
+ if (hasOpened) {
+ props.triggerAttribute?.setValue(true);
+ }
+ if (hasClosed) {
+ props.triggerAttribute?.setValue(false);
+ }
+ },
+ [isAvailable, props.triggerAttribute]
);
- const handleSheetChanges = (index: number) => {
+ useEffect(() => {
if (!isAvailable) {
return;
}
- const hasOpened = lastIndexRef === -1 && index === 0;
- const hasClosed = index === -1;
- lastIndexRef = index;
-
- if (hasOpened) {
- props.triggerAttribute?.setValue(true);
- }
- if (hasClosed) {
- props.triggerAttribute?.setValue(false);
+ if (props.triggerAttribute?.value && !currentStatus) {
+ InteractionManager.runAfterInteractions(() => setCurrentStatus(true));
+ } else if (!props.triggerAttribute?.value && currentStatus) {
+ bottomSheetRef.current?.close();
+ setCurrentStatus(false);
}
- };
-
- const close = () => {
- bottomSheetRef.current?.close();
- };
+ }, [props.triggerAttribute, currentStatus, isAvailable]);
return (
-
+
handleComponent={null}
handleStyle={{ display: "none" }}
>
- {props.content}
+
+ {props.content}
+
);
diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx
index e08e02e9b..e356c0d9a 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx
+++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/ExpandingDrawer.tsx
@@ -1,6 +1,6 @@
-import { ReactNode, ReactElement, useCallback, useState, useRef, Children } from "react";
-import { Dimensions, LayoutChangeEvent, SafeAreaView, StyleSheet, View } from "react-native";
-import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
+import { ReactNode, ReactElement, useCallback, useMemo, useState, useRef, Children } from "react";
+import { Dimensions, LayoutChangeEvent, StyleSheet, View } from "react-native";
+import BottomSheet, { BottomSheetScrollView, BottomSheetView } from "@gorhom/bottom-sheet";
import { BottomSheetStyle } from "../ui/Styles";
interface ExpandingDrawerProps {
@@ -13,124 +13,206 @@ interface ExpandingDrawerProps {
}
let lastIndexRef = -1;
-const OFFSET_BOTTOM_SHEET = 25;
+const OFFSET_BOTTOM_SHEET = 25; // A small offset for visual padding or handle
export const ExpandingDrawer = (props: ExpandingDrawerProps): ReactElement => {
- const [heightContent, setHeightContent] = useState(0);
- const [heightHeader, setHeightHeader] = useState(0);
- const [fullscreenHeight, setFullscreenHeight] = useState(0);
- const [isOpen, setIsOpen] = useState(true);
+ // State to store measured heights
+ const [smallContentHeight, setSmallContentHeight] = useState(0); // Height of the smallContent (header)
+ const [largeContentOnlyHeight, setLargeContentOnlyHeight] = useState(0); // Height of largeContent ONLY
+ const [fullscreenContentOnlyHeight, setFullscreenContentOnlyHeight] = useState(0); // Height of fullscreenContent ONLY
+ const [isOpen, setIsOpen] = useState(true); // Tracks if the drawer is open or closed
const bottomSheetRef = useRef(null);
- const maxHeight = Dimensions.get("screen").height;
+ const screenHeight = Dimensions.get("screen").height;
+ const halfScreen = Math.round(screenHeight * 0.5);
+
const isSmallContentValid = Children.count(props.smallContent) > 0;
const isLargeContentValid = Children.count(props.largeContent) > 0;
+ const isFullscreenContentValid = Children.count(props.fullscreenContent) > 0;
+
+ // Handlers for measuring individual content sections
+ const onLayoutSmallContent = useCallback(
+ (event: LayoutChangeEvent): void => {
+ const height = event.nativeEvent.layout.height;
+ if (height > 0 && height !== smallContentHeight) {
+ setSmallContentHeight(height);
+ }
+ },
+ [smallContentHeight]
+ );
- const onLayoutHandlerHeader = (event: LayoutChangeEvent): void => {
- const height = event.nativeEvent.layout.height;
- if (height > 0 && height <= maxHeight) {
- setHeightHeader(height);
- }
- };
-
- const onLayoutHandlerContent = (event: LayoutChangeEvent): void => {
- const height = event.nativeEvent.layout.height;
- if (height > 0) {
- if (height <= maxHeight) {
- setHeightContent(height);
- } else if (!props.fullscreenContent) {
- setHeightContent(maxHeight);
+ const onLayoutLargeContent = useCallback(
+ (event: LayoutChangeEvent): void => {
+ const height = event.nativeEvent.layout.height;
+ if (height > 0 && height !== largeContentOnlyHeight) {
+ setLargeContentOnlyHeight(height);
}
- }
- };
+ },
+ [largeContentOnlyHeight]
+ );
- const onLayoutFullscreenHandler = (event: LayoutChangeEvent): void => {
- const height = event.nativeEvent.layout.height;
- if (height > 0) {
- setFullscreenHeight(height);
- }
- };
+ const onLayoutFullscreenContent = useCallback(
+ (event: LayoutChangeEvent): void => {
+ const height = event.nativeEvent.layout.height;
+ if (height > 0 && height !== fullscreenContentOnlyHeight) {
+ setFullscreenContentOnlyHeight(height);
+ }
+ },
+ [fullscreenContentOnlyHeight]
+ );
+ // Determine the container style based on expansion state
const containerStyle =
- props.fullscreenContent && isOpen ? props.styles.containerWhenExpandedFullscreen : props.styles.container;
-
- const renderContent = useCallback((): ReactNode => {
- const content = (
-
-
+ isFullscreenContentValid && isOpen ? props.styles.containerWhenExpandedFullscreen : props.styles.container;
+
+ const renderMeasurementTree = useCallback((): ReactElement => {
+ return (
+
+ {/* Measure smallContent */}
+
{props.smallContent}
- {props.largeContent}
-
- );
-
- if (props.fullscreenContent) {
- return (
-
- {content}
- {props.fullscreenContent}
+ {/* Measure largeContent */}
+
+ {props.largeContent}
- );
- }
- return content;
- }, [props.smallContent, props.largeContent, props.fullscreenContent, isOpen, fullscreenHeight]);
-
- if (props.fullscreenContent && fullscreenHeight === 0) {
- return (
-
-
+ {/* Measure fullscreenContent */}
+ {isFullscreenContentValid && (
+
+ {props.fullscreenContent}
+
+ )}
);
- }
-
- if (heightHeader === 0 || (isLargeContentValid && heightContent === 0)) {
- return {renderContent()};
- }
-
- const snapPoints =
- props.fullscreenContent && heightContent
- ? [fullscreenHeight, heightContent, heightHeader]
- : props.fullscreenContent
- ? [fullscreenHeight, heightHeader]
- : isLargeContentValid
- ? [heightContent, heightHeader]
- : [heightHeader];
-
- const collapsedIndex = 0;
-
- const onChange = (index: number) => {
- const hasOpened = lastIndexRef === -1 && index === 0;
- const hasClosed = index === -1;
-
- if (hasOpened) {
- props.onOpen?.();
- setIsOpen(true);
+ }, [
+ props.smallContent,
+ props.largeContent,
+ props.fullscreenContent,
+ isFullscreenContentValid,
+ onLayoutSmallContent,
+ onLayoutLargeContent,
+ onLayoutFullscreenContent
+ ]);
+
+ // Calculate snap points based on measured heights
+ const snapPoints = useMemo(() => {
+ const points: number[] = [];
+
+ if (smallContentHeight > 0) {
+ points.push(smallContentHeight + OFFSET_BOTTOM_SHEET);
+ } else {
+ points.push(50 + OFFSET_BOTTOM_SHEET); // A reasonable default height
}
- if (hasClosed) {
- props.onClose?.();
- setIsOpen(false);
+
+ // Calculate the height for the intermediate snap point (largeContent + smallContent)
+ const combinedLargeContentHeight = smallContentHeight + largeContentOnlyHeight;
+
+ if (isFullscreenContentValid) {
+ if (isLargeContentValid && largeContentOnlyHeight > 0) {
+ const intermediateSnapPoint = Math.min(halfScreen, combinedLargeContentHeight) + OFFSET_BOTTOM_SHEET;
+ if (intermediateSnapPoint > points[points.length - 1]) {
+ points.push(intermediateSnapPoint);
+ }
+ }
+
+ if (fullscreenContentOnlyHeight > 0) {
+ const fullScreenSnapPoint = screenHeight - OFFSET_BOTTOM_SHEET;
+ if (fullScreenSnapPoint > points[points.length - 1]) {
+ points.push(fullScreenSnapPoint);
+ }
+ }
+ } else {
+ // If no fullscreenContent, the expanded state is the max of halfScreen or combined largeContent height
+ if (isLargeContentValid && largeContentOnlyHeight > 0) {
+ const expandedSnapPoint = Math.min(halfScreen, combinedLargeContentHeight) + OFFSET_BOTTOM_SHEET;
+ if (expandedSnapPoint > points[points.length - 1]) {
+ points.push(expandedSnapPoint);
+ }
+ }
}
- lastIndexRef = index;
- };
+
+ // Ensure snap points are unique and sorted in ascending order
+ return Array.from(new Set(points)).sort((a, b) => (typeof a === "number" && typeof b === "number" ? a - b : 0));
+ }, [
+ smallContentHeight,
+ largeContentOnlyHeight,
+ fullscreenContentOnlyHeight,
+ isFullscreenContentValid,
+ isLargeContentValid,
+ halfScreen,
+ screenHeight
+ ]);
+
+ const collapsedIndex = 0; // The initial snap point (smallContent)
+
+ const onChange = useCallback(
+ (index: number) => {
+ // Determine if the drawer is opening or closing based on index changes
+ const hasOpened = lastIndexRef === -1 && index === collapsedIndex; // Initial open to collapsed
+ const hasClosed = index === -1; // Fully closed
+ const hasExpanded = index > collapsedIndex && lastIndexRef <= collapsedIndex; // Expanded from collapsed or further
+ const hasCollapsed = index === collapsedIndex && lastIndexRef > collapsedIndex; // Collapsed back to initial
+
+ if (hasOpened || hasExpanded) {
+ props.onOpen?.();
+ setIsOpen(true);
+ }
+ if (hasClosed || hasCollapsed) {
+ props.onClose?.();
+ setIsOpen(index !== -1); // Set isOpen to false only if fully closed
+ }
+ lastIndexRef = index;
+ },
+ [props.onOpen, props.onClose, collapsedIndex]
+ );
+
+ const hasMinimumMeasurements = !isSmallContentValid || smallContentHeight > 0;
return (
- {snapPoints.length > 1 && (
+ {renderMeasurementTree()}
+
+ {hasMinimumMeasurements && snapPoints.length > 0 && (
p + OFFSET_BOTTOM_SHEET)}
+ index={collapsedIndex} // Start at the collapsed state
+ snapPoints={snapPoints}
onClose={() => setIsOpen(false)}
enablePanDownToClose={false}
onChange={onChange}
animateOnMount
backgroundStyle={containerStyle}
+ enableDynamicSizing={false}
>
- {renderContent()}
+ {/* Sticky header (smallContent) */}
+
+ {props.smallContent}
+
+
+ {/* Scrollable content area */}
+
+ {/* Render largeContent and measure it if needed */}
+ {props.largeContent}
+
+ {/* Render fullscreenContent only if it's enabled */}
+ {isFullscreenContentValid && (
+ // Render and measure fullscreenContent
+ {props.fullscreenContent}
+ )}
+
)}
diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx
index d4baa0e3e..966418537 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx
+++ b/packages/pluggableWidgets/bottom-sheet-native/src/components/NativeBottomSheet.tsx
@@ -1,7 +1,9 @@
-import { ReactElement, useCallback, useEffect, useRef } from "react";
+import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
ActionSheetIOS,
Appearance,
+ Dimensions,
+ LayoutChangeEvent,
Modal,
Platform,
Pressable,
@@ -10,7 +12,11 @@ import {
TouchableHighlight,
View
} from "react-native";
-import BottomSheet, { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetView } from "@gorhom/bottom-sheet";
+import BottomSheet, {
+ BottomSheetBackdrop,
+ BottomSheetBackdropProps,
+ BottomSheetScrollView
+} from "@gorhom/bottom-sheet";
import { EditableValue, ValueStatus } from "mendix";
import { ItemsBasicType } from "../../typings/BottomSheetProps";
import { BottomSheetStyle, ModalItemContainerStyle } from "../ui/Styles";
@@ -28,6 +34,7 @@ let lastIndexRef = -1;
export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement => {
const bottomSheetRef = useRef(null);
+ const [contentHeight, setContentHeight] = useState(0);
const isAvailable = props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available;
const isOpen =
@@ -35,19 +42,19 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement =
props.triggerAttribute.status === ValueStatus.Available &&
props.triggerAttribute.value;
- const manageBottomSheet = () => {
+ const manageBottomSheet = useCallback(() => {
if (props.triggerAttribute && props.triggerAttribute.status === ValueStatus.Available) {
if (props.triggerAttribute.value) {
- bottomSheetRef.current?.expand();
+ bottomSheetRef.current?.snapToIndex(0);
} else {
bottomSheetRef.current?.close();
}
}
- };
+ }, [props.triggerAttribute]);
useEffect(() => {
manageBottomSheet();
- }, [props.triggerAttribute]);
+ }, [manageBottomSheet]);
useEffect(() => {
// Only show the ActionSheet if using native on iOS and the trigger is active.
@@ -78,16 +85,33 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement =
}
}, [isOpen]);
- const renderBackdrop = (backdropProps: BottomSheetBackdropProps) => (
-
-
-
+ const close = useCallback(() => {
+ bottomSheetRef.current?.close();
+ }, []);
+
+ const renderBackdrop = useCallback(
+ (backdropProps: BottomSheetBackdropProps) => (
+
+
+
+ ),
+ [close]
+ );
+
+ const onLayoutHandler = useCallback(
+ (event: LayoutChangeEvent) => {
+ const height = event.nativeEvent.layout.height;
+ if (height > 0 && height !== contentHeight) {
+ setContentHeight(height);
+ }
+ },
+ [contentHeight]
);
const actionHandler = useCallback(
@@ -146,37 +170,49 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement =
return [styles.sheetContainer, props.styles.container];
};
- const handleSheetChanges = (index: number) => {
- if (!isAvailable) {
- return;
- }
- const hasOpened = lastIndexRef === -1 && index === 0;
- const hasClosed = index === -1;
- lastIndexRef = index;
+ const handleSheetChanges = useCallback(
+ (index: number) => {
+ if (!isAvailable) {
+ return;
+ }
+ const hasOpened = lastIndexRef === -1 && index === 0;
+ const hasClosed = index === -1;
+ lastIndexRef = index;
- if (hasOpened) {
- props.triggerAttribute?.setValue(true);
- }
- if (hasClosed) {
- props.triggerAttribute?.setValue(false);
+ if (hasOpened) {
+ props.triggerAttribute?.setValue(true);
+ }
+ if (hasClosed) {
+ props.triggerAttribute?.setValue(false);
+ }
+ },
+ [isAvailable, props.triggerAttribute]
+ );
+
+ const snapPoints = useMemo(() => {
+ if (contentHeight === 0) {
+ return [1]; // During measurement
}
- };
+
+ // Use actual measured height, cap at 90% screen
+ const maxHeight = Dimensions.get("screen").height * 0.9;
+
+ const snapHeight = Math.min(contentHeight, maxHeight);
+ return [snapHeight];
+ }, [contentHeight]);
if (props.useNative && Platform.OS === "ios") {
return ;
}
- const close = () => {
- bottomSheetRef.current?.close();
- };
-
return (
0 ? 0 : -1}
+ snapPoints={snapPoints}
enablePanDownToClose
- animateOnMount
+ animateOnMount={false}
onClose={() => handleSheetChanges(-1)}
onChange={handleSheetChanges}
style={getContainerStyle()}
@@ -185,9 +221,9 @@ export const NativeBottomSheet = (props: NativeBottomSheetProps): ReactElement =
handleComponent={null}
handleStyle={{ display: "none" }}
>
-
+
{props.itemsBasic.map((item, index) => renderItem(item, index))}
-
+
);
diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/package.xml b/packages/pluggableWidgets/bottom-sheet-native/src/package.xml
index 6f741eabd..69139cc5c 100644
--- a/packages/pluggableWidgets/bottom-sheet-native/src/package.xml
+++ b/packages/pluggableWidgets/bottom-sheet-native/src/package.xml
@@ -1,6 +1,6 @@
-
+