react-native-bottom-safe-area 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # react-native-bottom-safe-area
2
+
3
+ A lightweight, zero-native-dependency React Native library that creates a bottom container which automatically adapts to Safe Area insets (iPhone X+ Home Indicator) and Keyboard visibility.
4
+
5
+ Perfect for "Continue", "Submit", or "Checkout" buttons that need to sit at the bottom of the screen but never get hidden behind the keyboard or system navigation.
6
+
7
+ ## Why this exists?
8
+
9
+ Even with `react-native-safe-area-context`, handling the keyboard interactions along with safe area insets often leads to:
10
+ - Boilerplate code in every screen.
11
+ - Buttons jumping or getting hidden behind the keyboard.
12
+ - Double padding issues (Safe Area + Keyboard Cushion).
13
+ - Manual calculations for different devices.
14
+
15
+ This library encapsulates that logic into a single simple component wrapping your button.
16
+
17
+ ## Features
18
+
19
+ - 🚀 **Auto-handling**: Automatically applies `paddingBottom` based on Safe Area or Keyboard height.
20
+ - ⌨️ **Keyboard Aware**: Smoothly animates padding when keyboard opens/closes.
21
+ - 📱 **Cross Platform**: Works on iOS (Home Indicator) and Android (Navigation Bar).
22
+ - ⚡ **Lightweight**: No native code to link, just pure JS/TS + `react-native-safe-area-context`.
23
+ - 🛡️ **TypeScript**: Fully typed.
24
+
25
+ ## Installation
26
+
27
+ ```sh
28
+ npm install react-native-bottom-safe-area
29
+ # OR
30
+ yarn add react-native-bottom-safe-area
31
+ ```
32
+
33
+ This library requires `react-native-safe-area-context` as a peer dependency. If you haven't installed it yet:
34
+
35
+ ```sh
36
+ npm install react-native-safe-area-context
37
+ cd ios && pod install && cd ..
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ Wrap your functionality (like a Button) with `BottomSafeArea`. It acts as a View that adds dynamic bottom padding.
43
+
44
+ ```tsx
45
+ import React from 'react';
46
+ import { Button, View, Text } from 'react-native';
47
+ import { BottomSafeArea } from 'react-native-bottom-safe-area';
48
+
49
+ export default function MyScreen() {
50
+ return (
51
+ <View style={{ flex: 1, justifyContent: 'space-between' }}>
52
+ <View style={{ padding: 20 }}>
53
+ <Text>Some content here...</Text>
54
+ </View>
55
+
56
+ {/* Place this at the bottom of your container */}
57
+ <BottomSafeArea extraSpacing={10} backgroundColor="white">
58
+ <Button title="Continue" onPress={() => console.log('Pressed')} />
59
+ </BottomSafeArea>
60
+ </View>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### `<BottomSafeArea />`
68
+
69
+ | Prop | Type | Default | Description |
70
+ |------|------|---------|-------------|
71
+ | `children` | `ReactNode` | **Required** | The content to render (Buttons, Text, etc.). |
72
+ | `extraSpacing` | `number` | `0` | Additional padding (in pixels) to add on top of the calculated safe area/keyboard offset. |
73
+ | `backgroundColor` | `string` | `'transparent'` | Background color of the container view. |
74
+ | `style` | `ViewStyle` | `undefined` | Custom styles for the container view. |
75
+
76
+ ## Android Parsing
77
+
78
+ On Android, ensure your `android/app/src/main/AndroidManifest.xml` has `windowSoftInputMode="adjustResize"` for best results, although this library attempts to handle padding manually as well.
79
+
80
+ ## License
81
+
82
+ MIT
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BottomSafeArea = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ if (_reactNative.Platform.OS === 'android' && _reactNative.UIManager.setLayoutAnimationEnabledExperimental) {
12
+ _reactNative.UIManager.setLayoutAnimationEnabledExperimental(true);
13
+ }
14
+
15
+ /**
16
+ * Props for the BottomSafeArea component.
17
+ */
18
+
19
+ /**
20
+ * A component that automatically handles bottom safe area insets and keyboard avoidance.
21
+ *
22
+ * It listens to keyboard events and adjusts the bottom padding accordingly,
23
+ * ensuring that the content (e.g., action buttons) is always visible and clickable,
24
+ * never hidden behind the keyboard or the home indicator.
25
+ *
26
+ * @example
27
+ * <BottomSafeArea>
28
+ * <Button title="Continue" onPress={...} />
29
+ * </BottomSafeArea>
30
+ */
31
+ const BottomSafeArea = ({
32
+ children,
33
+ extraSpacing = 0,
34
+ backgroundColor = 'transparent',
35
+ style
36
+ }) => {
37
+ const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
38
+ const [bottomPadding, setBottomPadding] = (0, _react.useState)(0);
39
+ (0, _react.useEffect)(() => {
40
+ // Function to handle keyboard showing
41
+ const onKeyboardShow = e => {
42
+ // Configure animation for smooth transition
43
+ _reactNative.LayoutAnimation.configureNext(_reactNative.LayoutAnimation.Presets.easeInEaseOut);
44
+
45
+ // When keyboard shows, we want to pad by the keyboard height
46
+ // However, on iOS, the keyboard sits on top of the view, so we might need to adjust logic based on where this component is placed.
47
+ // But typically for a bottom-anchored view, we want to lift it up.
48
+ // The `e.endCoordinates.height` gives the keyboard height.
49
+ // We subtract insets.bottom because the keyboard covers the safe area too.
50
+ // If we are already respecting safe area, we don't want to double count it when keyboard appears.
51
+
52
+ const keyboardHeight = e.endCoordinates.height;
53
+ if (_reactNative.Platform.OS === 'ios') {
54
+ setBottomPadding(keyboardHeight);
55
+ } else {
56
+ setBottomPadding(keyboardHeight);
57
+ }
58
+ };
59
+
60
+ // Function to handle keyboard hiding
61
+ const onKeyboardHide = () => {
62
+ _reactNative.LayoutAnimation.configureNext(_reactNative.LayoutAnimation.Presets.easeInEaseOut);
63
+ setBottomPadding(0);
64
+ };
65
+
66
+ // Subscriptions
67
+ const showSubscription = _reactNative.Keyboard.addListener(_reactNative.Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', onKeyboardShow);
68
+ const hideSubscription = _reactNative.Keyboard.addListener(_reactNative.Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', onKeyboardHide);
69
+ return () => {
70
+ showSubscription.remove();
71
+ hideSubscription.remove();
72
+ };
73
+ }, []);
74
+ const finalBottomPadding = Math.max(bottomPadding, insets.bottom) + extraSpacing;
75
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
76
+ style: [styles.container, {
77
+ paddingBottom: finalBottomPadding,
78
+ backgroundColor: backgroundColor
79
+ }, style]
80
+ }, children);
81
+ };
82
+ exports.BottomSafeArea = BottomSafeArea;
83
+ const styles = _reactNative.StyleSheet.create({
84
+ container: {
85
+ // Default style: full width, acting as a container at the bottom
86
+ width: '100%'
87
+ // We don't enforce absolute positioning here to allow flexibility,
88
+ // but often this component is used at the bottom of a flex:1 container.
89
+ }
90
+ });
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_reactNativeSafeAreaContext","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","Platform","OS","UIManager","setLayoutAnimationEnabledExperimental","BottomSafeArea","children","extraSpacing","backgroundColor","style","insets","useSafeAreaInsets","bottomPadding","setBottomPadding","useState","useEffect","onKeyboardShow","LayoutAnimation","configureNext","Presets","easeInEaseOut","keyboardHeight","endCoordinates","height","onKeyboardHide","showSubscription","Keyboard","addListener","hideSubscription","remove","finalBottomPadding","Math","max","bottom","createElement","View","styles","container","paddingBottom","exports","StyleSheet","create","width"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,83 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { View, StyleSheet, Keyboard, Platform, LayoutAnimation, UIManager } from 'react-native';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
+ if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
5
+ UIManager.setLayoutAnimationEnabledExperimental(true);
6
+ }
7
+
8
+ /**
9
+ * Props for the BottomSafeArea component.
10
+ */
11
+
12
+ /**
13
+ * A component that automatically handles bottom safe area insets and keyboard avoidance.
14
+ *
15
+ * It listens to keyboard events and adjusts the bottom padding accordingly,
16
+ * ensuring that the content (e.g., action buttons) is always visible and clickable,
17
+ * never hidden behind the keyboard or the home indicator.
18
+ *
19
+ * @example
20
+ * <BottomSafeArea>
21
+ * <Button title="Continue" onPress={...} />
22
+ * </BottomSafeArea>
23
+ */
24
+ export const BottomSafeArea = ({
25
+ children,
26
+ extraSpacing = 0,
27
+ backgroundColor = 'transparent',
28
+ style
29
+ }) => {
30
+ const insets = useSafeAreaInsets();
31
+ const [bottomPadding, setBottomPadding] = useState(0);
32
+ useEffect(() => {
33
+ // Function to handle keyboard showing
34
+ const onKeyboardShow = e => {
35
+ // Configure animation for smooth transition
36
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
37
+
38
+ // When keyboard shows, we want to pad by the keyboard height
39
+ // However, on iOS, the keyboard sits on top of the view, so we might need to adjust logic based on where this component is placed.
40
+ // But typically for a bottom-anchored view, we want to lift it up.
41
+ // The `e.endCoordinates.height` gives the keyboard height.
42
+ // We subtract insets.bottom because the keyboard covers the safe area too.
43
+ // If we are already respecting safe area, we don't want to double count it when keyboard appears.
44
+
45
+ const keyboardHeight = e.endCoordinates.height;
46
+ if (Platform.OS === 'ios') {
47
+ setBottomPadding(keyboardHeight);
48
+ } else {
49
+ setBottomPadding(keyboardHeight);
50
+ }
51
+ };
52
+
53
+ // Function to handle keyboard hiding
54
+ const onKeyboardHide = () => {
55
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
56
+ setBottomPadding(0);
57
+ };
58
+
59
+ // Subscriptions
60
+ const showSubscription = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', onKeyboardShow);
61
+ const hideSubscription = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', onKeyboardHide);
62
+ return () => {
63
+ showSubscription.remove();
64
+ hideSubscription.remove();
65
+ };
66
+ }, []);
67
+ const finalBottomPadding = Math.max(bottomPadding, insets.bottom) + extraSpacing;
68
+ return /*#__PURE__*/React.createElement(View, {
69
+ style: [styles.container, {
70
+ paddingBottom: finalBottomPadding,
71
+ backgroundColor: backgroundColor
72
+ }, style]
73
+ }, children);
74
+ };
75
+ const styles = StyleSheet.create({
76
+ container: {
77
+ // Default style: full width, acting as a container at the bottom
78
+ width: '100%'
79
+ // We don't enforce absolute positioning here to allow flexibility,
80
+ // but often this component is used at the bottom of a flex:1 container.
81
+ }
82
+ });
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","useEffect","useState","View","StyleSheet","Keyboard","Platform","LayoutAnimation","UIManager","useSafeAreaInsets","OS","setLayoutAnimationEnabledExperimental","BottomSafeArea","children","extraSpacing","backgroundColor","style","insets","bottomPadding","setBottomPadding","onKeyboardShow","e","configureNext","Presets","easeInEaseOut","keyboardHeight","endCoordinates","height","onKeyboardHide","showSubscription","addListener","hideSubscription","remove","finalBottomPadding","Math","max","bottom","createElement","styles","container","paddingBottom","create","width"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,OAAOA,KAAK,IAAIC,SAAS,EAAEC,QAAQ,QAAmB,OAAO;AAC7D,SACIC,IAAI,EACJC,UAAU,EACVC,QAAQ,EACRC,QAAQ,EACRC,eAAe,EAGfC,SAAS,QACN,cAAc;AACrB,SAASC,iBAAiB,QAAQ,gCAAgC;AAElE,IACIH,QAAQ,CAACI,EAAE,KAAK,SAAS,IACzBF,SAAS,CAACG,qCAAqC,EACjD;EACEH,SAAS,CAACG,qCAAqC,CAAC,IAAI,CAAC;AACzD;;AAEA;AACA;AACA;;AA2BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,cAAc,GAAGA,CAAC;EAC3BC,QAAQ;EACRC,YAAY,GAAG,CAAC;EAChBC,eAAe,GAAG,aAAa;EAC/BC;AACiB,CAAC,KAAK;EACvB,MAAMC,MAAM,GAAGR,iBAAiB,CAAC,CAAC;EAClC,MAAM,CAACS,aAAa,EAAEC,gBAAgB,CAAC,GAAGjB,QAAQ,CAAC,CAAC,CAAC;EAErDD,SAAS,CAAC,MAAM;IACZ;IACA,MAAMmB,cAAc,GAAIC,CAAgB,IAAK;MACzC;MACAd,eAAe,CAACe,aAAa,CAACf,eAAe,CAACgB,OAAO,CAACC,aAAa,CAAC;;MAEpE;MACA;MACA;MACA;MACA;MACA;;MAEA,MAAMC,cAAc,GAAGJ,CAAC,CAACK,cAAc,CAACC,MAAM;MAE9C,IAAIrB,QAAQ,CAACI,EAAE,KAAK,KAAK,EAAE;QACvBS,gBAAgB,CAACM,cAAc,CAAC;MACpC,CAAC,MAAM;QACHN,gBAAgB,CAACM,cAAc,CAAC;MACpC;IACJ,CAAC;;IAED;IACA,MAAMG,cAAc,GAAGA,CAAA,KAAM;MACzBrB,eAAe,CAACe,aAAa,CAACf,eAAe,CAACgB,OAAO,CAACC,aAAa,CAAC;MACpEL,gBAAgB,CAAC,CAAC,CAAC;IACvB,CAAC;;IAED;IACA,MAAMU,gBAAgB,GAAGxB,QAAQ,CAACyB,WAAW,CACzCxB,QAAQ,CAACI,EAAE,KAAK,KAAK,GAAG,kBAAkB,GAAG,iBAAiB,EAC9DU,cACJ,CAAC;IACD,MAAMW,gBAAgB,GAAG1B,QAAQ,CAACyB,WAAW,CACzCxB,QAAQ,CAACI,EAAE,KAAK,KAAK,GAAG,kBAAkB,GAAG,iBAAiB,EAC9DkB,cACJ,CAAC;IAED,OAAO,MAAM;MACTC,gBAAgB,CAACG,MAAM,CAAC,CAAC;MACzBD,gBAAgB,CAACC,MAAM,CAAC,CAAC;IAC7B,CAAC;EACL,CAAC,EAAE,EAAE,CAAC;EAEN,MAAMC,kBAAkB,GAAGC,IAAI,CAACC,GAAG,CAACjB,aAAa,EAAED,MAAM,CAACmB,MAAM,CAAC,GAAGtB,YAAY;EAEhF,oBACId,KAAA,CAAAqC,aAAA,CAAClC,IAAI;IACDa,KAAK,EAAE,CACHsB,MAAM,CAACC,SAAS,EAChB;MACIC,aAAa,EAAEP,kBAAkB;MACjClB,eAAe,EAAEA;IACrB,CAAC,EACDC,KAAK;EACP,GAEDH,QACC,CAAC;AAEf,CAAC;AAED,MAAMyB,MAAM,GAAGlC,UAAU,CAACqC,MAAM,CAAC;EAC7BF,SAAS,EAAE;IACP;IACAG,KAAK,EAAE;IACP;IACA;EACJ;AACJ,CAAC,CAAC","ignoreList":[]}
@@ -0,0 +1,41 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+ /**
4
+ * Props for the BottomSafeArea component.
5
+ */
6
+ export interface BottomSafeAreaProps {
7
+ /**
8
+ * The content to render inside the safe area container.
9
+ * Typically a button or a row of buttons.
10
+ */
11
+ children: ReactNode;
12
+ /**
13
+ * Additional spacing (padding) to add to the bottom, in pixels.
14
+ * Useful if you want more space than just the safe area/keyboard height.
15
+ * Defaults to 0.
16
+ */
17
+ extraSpacing?: number;
18
+ /**
19
+ * Background color of the safe area container.
20
+ * Defaults to 'transparent'.
21
+ */
22
+ backgroundColor?: string;
23
+ /**
24
+ * Optional custom style for the container.
25
+ */
26
+ style?: ViewStyle;
27
+ }
28
+ /**
29
+ * A component that automatically handles bottom safe area insets and keyboard avoidance.
30
+ *
31
+ * It listens to keyboard events and adjusts the bottom padding accordingly,
32
+ * ensuring that the content (e.g., action buttons) is always visible and clickable,
33
+ * never hidden behind the keyboard or the home indicator.
34
+ *
35
+ * @example
36
+ * <BottomSafeArea>
37
+ * <Button title="Continue" onPress={...} />
38
+ * </BottomSafeArea>
39
+ */
40
+ export declare const BottomSafeArea: ({ children, extraSpacing, backgroundColor, style, }: BottomSafeAreaProps) => React.JSX.Element;
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuB,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,EAMH,SAAS,EAGZ,MAAM,cAAc,CAAC;AAUtB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GAAI,qDAK5B,mBAAmB,sBAgErB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "react-native-bottom-safe-area",
3
+ "version": "1.0.0",
4
+ "description": "A reusable BottomSafeArea component that handles safe area insets and keyboard avoidance automatically.",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index.tsx",
9
+ "source": "src/index.tsx",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "android",
14
+ "ios",
15
+ "cpp",
16
+ "*.podspec",
17
+ "!lib/typescript/example",
18
+ "!android/build",
19
+ "!ios/build",
20
+ "!**/__tests__",
21
+ "!**/__fixtures__",
22
+ "!**/__mocks__"
23
+ ],
24
+ "scripts": {
25
+ "test": "echo \"Error: no test specified\" && exit 1",
26
+ "typescript": "tsc --noEmit",
27
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
28
+ "prepare": "bob build",
29
+ "release": "release-it",
30
+ "build": "bob build"
31
+ },
32
+ "keywords": [
33
+ "react-native",
34
+ "ios",
35
+ "android",
36
+ "safe-area",
37
+ "keyboard",
38
+ "bottom-sheet",
39
+ "ui"
40
+ ],
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/user/react-native-bottom-safe-area.git"
44
+ },
45
+ "author": "Sakshi Bheda <sakshibheda@example.com> (https://github.com/sakshibheda)",
46
+ "license": "MIT",
47
+ "bugs": {
48
+ "url": "https://github.com/user/react-native-bottom-safe-area/issues"
49
+ },
50
+ "homepage": "https://github.com/user/react-native-bottom-safe-area#readme",
51
+ "publishConfig": {
52
+ "registry": "https://registry.npmjs.org/"
53
+ },
54
+ "devDependencies": {
55
+ "@react-native/eslint-config": "^0.73.1",
56
+ "@types/jest": "^29.5.5",
57
+ "@types/react": "^18.2.44",
58
+ "eslint": "^8.51.0",
59
+ "prettier": "^3.0.3",
60
+ "react": "18.2.0",
61
+ "react-native": "^0.73.4",
62
+ "react-native-builder-bob": "^0.23.2",
63
+ "react-native-safe-area-context": "^4.9.0",
64
+ "release-it": "^17.0.0",
65
+ "typescript": "^5.2.2"
66
+ },
67
+ "peerDependencies": {
68
+ "react": "*",
69
+ "react-native": "*",
70
+ "react-native-safe-area-context": "*"
71
+ },
72
+ "engines": {
73
+ "node": ">= 18.0.0"
74
+ },
75
+ "react-native-builder-bob": {
76
+ "source": "src",
77
+ "output": "lib",
78
+ "targets": [
79
+ "commonjs",
80
+ "module",
81
+ [
82
+ "typescript",
83
+ {
84
+ "project": "tsconfig.json"
85
+ }
86
+ ]
87
+ ]
88
+ }
89
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,140 @@
1
+ import React, { useEffect, useState, ReactNode } from 'react';
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Keyboard,
6
+ Platform,
7
+ LayoutAnimation,
8
+ ViewStyle,
9
+ KeyboardEvent,
10
+ UIManager,
11
+ } from 'react-native';
12
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
13
+
14
+ if (
15
+ Platform.OS === 'android' &&
16
+ UIManager.setLayoutAnimationEnabledExperimental
17
+ ) {
18
+ UIManager.setLayoutAnimationEnabledExperimental(true);
19
+ }
20
+
21
+ /**
22
+ * Props for the BottomSafeArea component.
23
+ */
24
+ export interface BottomSafeAreaProps {
25
+ /**
26
+ * The content to render inside the safe area container.
27
+ * Typically a button or a row of buttons.
28
+ */
29
+ children: ReactNode;
30
+
31
+ /**
32
+ * Additional spacing (padding) to add to the bottom, in pixels.
33
+ * Useful if you want more space than just the safe area/keyboard height.
34
+ * Defaults to 0.
35
+ */
36
+ extraSpacing?: number;
37
+
38
+ /**
39
+ * Background color of the safe area container.
40
+ * Defaults to 'transparent'.
41
+ */
42
+ backgroundColor?: string;
43
+
44
+ /**
45
+ * Optional custom style for the container.
46
+ */
47
+ style?: ViewStyle;
48
+ }
49
+
50
+ /**
51
+ * A component that automatically handles bottom safe area insets and keyboard avoidance.
52
+ *
53
+ * It listens to keyboard events and adjusts the bottom padding accordingly,
54
+ * ensuring that the content (e.g., action buttons) is always visible and clickable,
55
+ * never hidden behind the keyboard or the home indicator.
56
+ *
57
+ * @example
58
+ * <BottomSafeArea>
59
+ * <Button title="Continue" onPress={...} />
60
+ * </BottomSafeArea>
61
+ */
62
+ export const BottomSafeArea = ({
63
+ children,
64
+ extraSpacing = 0,
65
+ backgroundColor = 'transparent',
66
+ style,
67
+ }: BottomSafeAreaProps) => {
68
+ const insets = useSafeAreaInsets();
69
+ const [bottomPadding, setBottomPadding] = useState(0);
70
+
71
+ useEffect(() => {
72
+ // Function to handle keyboard showing
73
+ const onKeyboardShow = (e: KeyboardEvent) => {
74
+ // Configure animation for smooth transition
75
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
76
+
77
+ // When keyboard shows, we want to pad by the keyboard height
78
+ // However, on iOS, the keyboard sits on top of the view, so we might need to adjust logic based on where this component is placed.
79
+ // But typically for a bottom-anchored view, we want to lift it up.
80
+ // The `e.endCoordinates.height` gives the keyboard height.
81
+ // We subtract insets.bottom because the keyboard covers the safe area too.
82
+ // If we are already respecting safe area, we don't want to double count it when keyboard appears.
83
+
84
+ const keyboardHeight = e.endCoordinates.height;
85
+
86
+ if (Platform.OS === 'ios') {
87
+ setBottomPadding(keyboardHeight);
88
+ } else {
89
+ setBottomPadding(keyboardHeight);
90
+ }
91
+ };
92
+
93
+ // Function to handle keyboard hiding
94
+ const onKeyboardHide = () => {
95
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
96
+ setBottomPadding(0);
97
+ };
98
+
99
+ // Subscriptions
100
+ const showSubscription = Keyboard.addListener(
101
+ Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
102
+ onKeyboardShow
103
+ );
104
+ const hideSubscription = Keyboard.addListener(
105
+ Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
106
+ onKeyboardHide
107
+ );
108
+
109
+ return () => {
110
+ showSubscription.remove();
111
+ hideSubscription.remove();
112
+ };
113
+ }, []);
114
+
115
+ const finalBottomPadding = Math.max(bottomPadding, insets.bottom) + extraSpacing;
116
+
117
+ return (
118
+ <View
119
+ style={[
120
+ styles.container,
121
+ {
122
+ paddingBottom: finalBottomPadding,
123
+ backgroundColor: backgroundColor,
124
+ },
125
+ style,
126
+ ]}
127
+ >
128
+ {children}
129
+ </View>
130
+ );
131
+ };
132
+
133
+ const styles = StyleSheet.create({
134
+ container: {
135
+ // Default style: full width, acting as a container at the bottom
136
+ width: '100%',
137
+ // We don't enforce absolute positioning here to allow flexibility,
138
+ // but often this component is used at the bottom of a flex:1 container.
139
+ },
140
+ });