Skip to content

Commit cb8161c

Browse files
authored
feat: insets prop for KeyboardToolbar (#866)
## 📜 Description Handle safe-area paddings in `KeyboardToolbar` component (landscape mode). ## 💡 Motivation and Context I had a lot of ideas how to fix this problem. Some of them: - pass `insets` as prop; - automatically grab insets from `useSafeAreaInsets` hook; - render view wrapper (users can add their own SafeAreaView implementation). The **automatically grab insets from `useSafeAreaInsets` hook** idea has failed, because in my case `react-native-safe-area-context` was resolving to other packages (maybe it's the problem with package linking in example app). But this is still not a perfect approach, because I'll force users to install additional package. The **render view wrapper (users can add their own SafeAreaView implementation)** was perspective, but it would be very complex for end users. Mostly because we have to provide additional props, such as `backgroundColor` etc., so it would look like: ```tsx <KeyboardToolbar wrapper={(props) => <SafeAreaView {...props} />} /> ``` And there is still a lot of open topics on how to handle many aspects - we would have to write conditional code when wrapper is not provided and render `View`. So in the end I decided to add `insets` property - and it would be up to lib users to provide it or not. Maybe not ideal solution, but definitely better than having an open issues for 9+ months 🙃 Closes #572 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - added new `insets` property. ## 🤔 How Has This Been Tested? Tested manually on iPhone 15 (iOS 17.5), Fabric. ## 📸 Screenshots (if appropriate): |Before|After| |------|-----| |![image](https://github.com/user-attachments/assets/a90955a5-c33b-44d9-80fb-e4e7e88bc735)|![image](https://github.com/user-attachments/assets/c64a54df-f0e9-4f5a-b62b-26b2f679134b)| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent 9080f31 commit cb8161c

File tree

5 files changed

+46
-1
lines changed
  • FabricExample/src/screens/Examples/Toolbar
  • docs
    • docs/api/components/keyboard-toolbar
    • versioned_docs/version-1.16.0/api/components/keyboard-toolbar
  • example/src/screens/Examples/Toolbar
  • src/components/KeyboardToolbar

5 files changed

+46
-1
lines changed

FabricExample/src/screens/Examples/Toolbar/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
KeyboardAwareScrollView,
77
KeyboardToolbar,
88
} from "react-native-keyboard-controller";
9+
import { useSafeAreaInsets } from "react-native-safe-area-context";
910

1011
import TextInput from "../../../components/TextInput";
1112

@@ -33,6 +34,7 @@ export default function ToolbarExample() {
3334
const onHideAutoFill = useCallback(() => {
3435
setShowAutoFill(false);
3536
}, []);
37+
const insets = useSafeAreaInsets();
3638

3739
return (
3840
<>
@@ -158,6 +160,7 @@ export default function ToolbarExample() {
158160
<AutoFillContacts onContactSelected={onContactSelected} />
159161
) : null
160162
}
163+
insets={insets}
161164
opacity={Platform.OS === "ios" ? "4F" : "DD"}
162165
onDoneCallback={haptic}
163166
onNextCallback={haptic}

docs/docs/api/components/keyboard-toolbar/index.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,20 @@ const Icon: KeyboardToolbarProps["icon"] = ({ type }) => {
144144
<KeyboardToolbar icon={Icon} />;
145145
```
146146

147+
### `insets`
148+
149+
An object containing `left` and `right` properties that define the `KeyboardToolbar` padding. This helps prevent overlap with system UI elements, especially in landscape orientation:
150+
151+
```tsx
152+
import { useSafeAreaInsets } from "react-native-safe-area-context";
153+
154+
// ...
155+
156+
const insets = useSafeAreaInsets();
157+
158+
<KeyboardToolbar insets={insets} />;
159+
```
160+
147161
### `onDoneCallback`
148162

149163
A callback that is called when the user presses the **done** button. The callback receives an instance of `GestureResponderEvent` which can be used to cancel the default action (for advanced use-cases).

docs/versioned_docs/version-1.16.0/api/components/keyboard-toolbar/index.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,20 @@ const Icon: KeyboardToolbarProps["icon"] = ({ type }) => {
144144
<KeyboardToolbar icon={Icon} />;
145145
```
146146

147+
### `insets`
148+
149+
An object containing `left` and `right` properties that define the `KeyboardToolbar` padding. This helps prevent overlap with system UI elements, especially in landscape orientation:
150+
151+
```tsx
152+
import { useSafeAreaInsets } from "react-native-safe-area-context";
153+
154+
// ...
155+
156+
const insets = useSafeAreaInsets();
157+
158+
<KeyboardToolbar insets={insets} />;
159+
```
160+
147161
### `onDoneCallback`
148162

149163
A callback that is called when the user presses the **done** button. The callback receives an instance of `GestureResponderEvent` which can be used to cancel the default action (for advanced use-cases).

example/src/screens/Examples/Toolbar/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
KeyboardAwareScrollView,
77
KeyboardToolbar,
88
} from "react-native-keyboard-controller";
9+
import { useSafeAreaInsets } from "react-native-safe-area-context";
910

1011
import TextInput from "../../../components/TextInput";
1112

@@ -33,6 +34,7 @@ export default function ToolbarExample() {
3334
const onHideAutoFill = useCallback(() => {
3435
setShowAutoFill(false);
3536
}, []);
37+
const insets = useSafeAreaInsets();
3638

3739
return (
3840
<>
@@ -158,6 +160,7 @@ export default function ToolbarExample() {
158160
<AutoFillContacts onContactSelected={onContactSelected} />
159161
) : null
160162
}
163+
insets={insets}
161164
opacity={Platform.OS === "ios" ? "4F" : "DD"}
162165
onDoneCallback={haptic}
163166
onNextCallback={haptic}

src/components/KeyboardToolbar/index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import type { KeyboardStickyViewProps } from "../KeyboardStickyView";
1515
import type { ReactNode } from "react";
1616
import type { GestureResponderEvent, ViewProps } from "react-native";
1717

18+
type SafeAreaInsets = {
19+
left: number;
20+
right: number;
21+
};
22+
1823
export type KeyboardToolbarProps = Omit<
1924
ViewProps,
2025
"style" | "testID" | "children"
@@ -54,6 +59,7 @@ export type KeyboardToolbarProps = Omit<
5459
* A value for container opacity in hexadecimal format (e.g. `ff`). Default value is `ff`.
5560
*/
5661
opacity?: HEX;
62+
insets?: SafeAreaInsets;
5763
} & Pick<KeyboardStickyViewProps, "offset" | "enabled">;
5864

5965
const TEST_ID_KEYBOARD_TOOLBAR = "keyboard.toolbar";
@@ -83,6 +89,7 @@ const KeyboardToolbar: React.FC<KeyboardToolbarProps> = ({
8389
opacity = DEFAULT_OPACITY,
8490
offset: { closed = 0, opened = 0 } = {},
8591
enabled = true,
92+
insets,
8693
...rest
8794
}) => {
8895
const colorScheme = useColorScheme();
@@ -110,8 +117,12 @@ const KeyboardToolbar: React.FC<KeyboardToolbarProps> = ({
110117
{
111118
backgroundColor: `${theme[colorScheme].background}${opacity}`,
112119
},
120+
{
121+
paddingLeft: insets?.left,
122+
paddingRight: insets?.right,
123+
},
113124
],
114-
[colorScheme, opacity, theme],
125+
[colorScheme, opacity, theme, insets],
115126
);
116127
const offset = useMemo(
117128
() => ({ closed: closed + KEYBOARD_TOOLBAR_HEIGHT, opened }),

0 commit comments

Comments
 (0)