diff --git a/FabricExample/src/screens/Examples/AwareScrollView/styles.ts b/FabricExample/src/screens/Examples/AwareScrollView/styles.ts
index 404787843d..5d9febb505 100644
--- a/FabricExample/src/screens/Examples/AwareScrollView/styles.ts
+++ b/FabricExample/src/screens/Examples/AwareScrollView/styles.ts
@@ -38,4 +38,7 @@ export const styles = StyleSheet.create({
borderRadius: 8,
paddingHorizontal: 12,
},
+ bottomSheetContent: {
+ flex: 1,
+ },
});
diff --git a/FabricExample/src/screens/Examples/Toolbar/index.tsx b/FabricExample/src/screens/Examples/Toolbar/index.tsx
index a8d9d2b324..c4b719da0b 100644
--- a/FabricExample/src/screens/Examples/Toolbar/index.tsx
+++ b/FabricExample/src/screens/Examples/Toolbar/index.tsx
@@ -156,18 +156,26 @@ function Form() {
/>
- ) : null
- }
insets={insets}
opacity={Platform.OS === "ios" ? "4F" : "DD"}
- onDoneCallback={haptic}
- onNextCallback={haptic}
- onPrevCallback={haptic}
- />
+ >
+
+
+
+
+ {showAutoFill ? (
+
+ ) : null}
+
+
+
+
+
>
);
}
@@ -238,12 +246,3 @@ const styles = StyleSheet.create({
marginTop: 32,
},
});
-
-const blur = (
-
-);
diff --git a/docs/docs/api/components/keyboard-toolbar/index.mdx b/docs/docs/api/components/keyboard-toolbar/index.mdx
index d0fbaee09a..bf0d023949 100644
--- a/docs/docs/api/components/keyboard-toolbar/index.mdx
+++ b/docs/docs/api/components/keyboard-toolbar/index.mdx
@@ -37,20 +37,60 @@ import toolbar from "./toolbar.lottie.json";
- **Extended accessibility support** 🔍: Ensures that all users, including those with disabilities, can navigate through inputs effectively.
- **Full control over the buttons behavior** 🔧: Customize the actions triggered by the next, previous, and done buttons according to your needs.
- **Extends ViewProps** 📜: Supports all the props that `View` component has.
+- **Compound component pattern** 🔌: Mix and match sub-components for granular control over the toolbar's structure.
-## Props
+## Compound Components
-### [`View Props`](https://reactnative.dev/docs/view#props)
+The new API uses sub-components as children of `KeyboardToolbar`. These allow for precise customization, such as conditional rendering of buttons or injecting custom elements.
-Inherits [View Props](https://reactnative.dev/docs/view#props).
+### ``
-### [`KeyboardStickyViewProps`](./keyboard-sticky-view)
+Renders a custom background (e.g., blur effect) that overlays the entire toolbar. Accepts any React node as children.
-Inherits [KeyboardStickyViewProps](./keyboard-sticky-view).
+```tsx
+import { Platform } from "react-native";
+import { KeyboardToolbar } from "react-native-keyboard-controller";
+import { BlurView } from "@react-native-community/blur";
+
+
+
+
+
+ {/* Other sub-components */}
+;
+```
+
+:::warning
+Please, note, that you need to specify `opacity` prop for this prop to work. Because otherwise you will not see a blur effect.
+:::
+
+### ``
+
+Renders a custom content (e.g., yours UI elements) in the middle of the toolbar. Accepts any React node as children.
+
+```tsx
+import { KeyboardToolbar } from "react-native-keyboard-controller";
+
+
+
+ {showAutoFill ? (
+
+ ) : null}
+
+ {/* Other sub-components */}
+;
+```
+
+### ``
-### `button`
+#### `button`
-This property allows to render custom touchable component for next, previous and done button.
+This property allows to render custom touchable component.
```tsx
import { TouchableOpacity } from "react-native-gesture-handler";
@@ -66,101 +106,122 @@ const CustomButton: KeyboardToolbarProps["button"] = ({
// ...
-;
+
+
+;
```
-### `blur`
-
-This property allows to render custom blur effect for the toolbar (by default iOS keyboard is opaque and it blurs the content underneath, so if you want to follow **HIG** ([_Human Interface Guidelines_](https://developer.apple.com/design/human-interface-guidelines/materials)) properly - consider to add this effect).
+#### `icon`
-By default it is `null` and will not render any blur effect, because it's not a responsibility of this library to provide a blur effect. Instead it provides a property where you can specify your own blur effect and its provider, i. e. `@react-native-community/blur`, `expo-blur` or maybe even `react-native-skia` (based on your project preferences of course).
-
-:::warning
-Please, note, that you need to specify `opacity` prop for this prop to work. Because otherwise you will not see a blur effect.
-:::
+`icon` property allows to render custom icons.
```tsx
-import { BlurView } from "@react-native-community/blur";
+import { Text } from "react-native";
import {
KeyboardToolbar,
KeyboardToolbarProps,
} from "react-native-keyboard-controller";
-const CustomBlur: KeyboardToolbarProps["blur"] = ({ children }) => (
-
- {children}
-
-);
+const Icon: KeyboardToolbarProps["icon"] = ({ type }) => {
+ return {type === "next" ? "⬇️" : "⬆️"};
+};
// ...
-;
+
+
+;
```
-### `content`
+#### `onPress`
-This property allows you to show a custom content in the middle of the toolbar. It accepts JSX element. Default value is `null`.
+A callback that is called when the user presses the **previous** button. The callback receives an instance of `GestureResponderEvent` which can be used to cancel the default action (for advanced use-cases).
```tsx
-
- ) : null
- }
-/>
-```
+import { Platform } from "react-native";
+import { KeyboardToolbar } from "react-native-keyboard-controller";
+import { trigger } from "react-native-haptic-feedback";
+
+const options = {
+ enableVibrateFallback: true,
+ ignoreAndroidSystemSettings: false,
+};
+const haptic = () =>
+ trigger(Platform.OS === "ios" ? "impactLight" : "keyboardTap", options);
-### `doneText`
+// ...
-The property that allows to specify custom text for `Done` button.
+
+
+;
+```
+
+:::tip Prevent Default Action
+To prevent the default action, call `e.preventDefault()` inside the callback:
```tsx
-
+
+ {
+ // the focus will not be moved to the prev input
+ e.preventDefault();
+ }}
+ />
+
```
-### `icon`
+:::
+
+### ``
-`icon` property allows to render custom icons for prev and next buttons.
+#### `button`
+
+This property allows to render custom touchable component.
```tsx
-import { Text } from "react-native";
+import { TouchableOpacity } from "react-native-gesture-handler";
import {
KeyboardToolbar,
KeyboardToolbarProps,
} from "react-native-keyboard-controller";
-const Icon: KeyboardToolbarProps["icon"] = ({ type }) => {
- return {type === "next" ? "⬇️" : "⬆️"};
-};
+const CustomButton: KeyboardToolbarProps["button"] = ({
+ children,
+ onPress,
+}) => {children};
// ...
-;
+
+
+;
```
-### `insets`
+#### `icon`
-An object containing `left` and `right` properties that define the `KeyboardToolbar` padding. This helps prevent overlap with system UI elements, especially in landscape orientation:
+`icon` property allows to render custom icons.
```tsx
-import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { Text } from "react-native";
+import {
+ KeyboardToolbar,
+ KeyboardToolbarProps,
+} from "react-native-keyboard-controller";
-// ...
+const Icon: KeyboardToolbarProps["icon"] = ({ type }) => {
+ return {type === "next" ? "⬇️" : "⬆️"};
+};
-const insets = useSafeAreaInsets();
+// ...
-;
+
+
+;
```
-### `onDoneCallback`
+#### `onPress`
-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).
+A callback that is called when the user presses the **next** button. The callback receives an instance of `GestureResponderEvent` which can be used to cancel the default action (for advanced use-cases).
```tsx
import { Platform } from "react-native";
@@ -176,25 +237,55 @@ const haptic = () =>
// ...
-;
+
+
+;
```
:::tip Prevent Default Action
To prevent the default action, call `e.preventDefault()` inside the callback:
```tsx
- {
- e.preventDefault(); // keyboard will not be dismissed, since we cancelled the default action
- }}
-/>
+
+ {
+ // the focus will not be moved to the next input
+ e.preventDefault();
+ }}
+ />
+
```
:::
-### `onNextCallback`
+### ``
-A callback that is called when the user presses the **next** button. The callback receives an instance of `GestureResponderEvent` which can be used to cancel the default action (for advanced use-cases).
+#### `button`
+
+This property allows to render custom touchable component.
+
+```tsx
+import { TouchableOpacity } from "react-native-gesture-handler";
+import {
+ KeyboardToolbar,
+ KeyboardToolbarProps,
+} from "react-native-keyboard-controller";
+
+const CustomButton: KeyboardToolbarProps["button"] = ({
+ children,
+ onPress,
+}) => {children};
+
+// ...
+
+
+
+;
+```
+
+#### `onPress`
+
+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).
```tsx
import { Platform } from "react-native";
@@ -210,55 +301,60 @@ const haptic = () =>
// ...
-;
+
+
+;
```
:::tip Prevent Default Action
To prevent the default action, call `e.preventDefault()` inside the callback:
```tsx
- {
- e.preventDefault(); // the focus will not be moved to the next input
- }}
-/>
+
+ {
+ // keyboard will not be dismissed, since we cancelled the default action
+ e.preventDefault();
+ }}
+ />
+
```
:::
-### `onPrevCallback`
+#### `text`
-A callback that is called when the user presses the **previous** button. The callback receives an instance of `GestureResponderEvent` which can be used to cancel the default action (for advanced use-cases).
+The property that allows to specify custom text for `Done` button.
```tsx
-import { Platform } from "react-native";
-import { KeyboardToolbar } from "react-native-keyboard-controller";
-import { trigger } from "react-native-haptic-feedback";
+
+
+
+```
-const options = {
- enableVibrateFallback: true,
- ignoreAndroidSystemSettings: false,
-};
-const haptic = () =>
- trigger(Platform.OS === "ios" ? "impactLight" : "keyboardTap", options);
+## Props
-// ...
+### [`View Props`](https://reactnative.dev/docs/view#props)
-;
-```
+Inherits [View Props](https://reactnative.dev/docs/view#props).
-:::tip Prevent Default Action
-To prevent the default action, call `e.preventDefault()` inside the callback:
+### [`KeyboardStickyViewProps`](./keyboard-sticky-view)
+
+Inherits [KeyboardStickyViewProps](./keyboard-sticky-view).
+
+### `insets`
+
+An object containing `left` and `right` properties that define the `KeyboardToolbar` padding. This helps prevent overlap with system UI elements, especially in landscape orientation:
```tsx
- {
- e.preventDefault(); // the focus will not be moved to the prev input
- }}
-/>
-```
+import { useSafeAreaInsets } from "react-native-safe-area-context";
-:::
+// ...
+
+const insets = useSafeAreaInsets();
+
+;
+```
### `opacity`
@@ -268,10 +364,6 @@ This property allows to specify the opacity of the toolbar container. The value
```
-### `showArrows`
-
-A boolean prop indicating whether to show `next` and `prev` buttons. Can be useful to set it to `false` if you have only one input and want to show only `Done` button. Default to `true`.
-
### `theme`
Prop allowing you to specify the brand colors of your application for `KeyboardToolbar` component. If you want to re-use already platform specific colors you can import `DefaultKeyboardToolbarTheme` object and override colors only necessary colors:
@@ -345,7 +437,11 @@ export default function ToolbarExample() {
-
+
+
+
+
+
>
);
}
@@ -444,6 +540,79 @@ const textInputStyles = StyleSheet.create({
For more comprehensive usage that covers more complex interactions please check [example](https://github.com/kirillzyusko/react-native-keyboard-controller/tree/main/example) app.
:::
+## Migration to compound component
+
+To migrate from the legacy prop-based API to the compound API:
+
+1. Add elements that you want to render in the toolbar (e.g., `Prev`, `Next`, `Done`, `Content`, `Background`).
+
+```tsx
+// Old:
+
+
+// New:
+
+
+
+
+
+```
+
+2. Move props like `content`, `blur`, `doneText` into dedicated sub-components:
+
+```tsx
+// Old:
+} blur={} doneText="Close" />
+
+// New:
+
+
+
+
+
+
+
+
+
+```
+
+3. If you used button callbacks, move them into dedicated sub-components:
+
+```tsx
+// Old:
+
+
+// New:
+
+
+
+
+
+```
+
+4. If you used `showArrows` prop, move it into conditional rendering:
+
+```tsx
+// Old:
+
+
+// New:
+
+ {showArrows ? : null}
+ {showArrows ? : null}
+
+```
+
+:::info Struggle to migrate?
+
+If you found any bugs or inconsistent behavior comparing to old implementation and can not migrate to new compound API - don't hesitate to open an [issue](https://github.com/kirillzyusko/react-native-keyboard-controller/issues/new?assignees=kirillzyusko&labels=bug&template=bug_report.md&title=). It will help the project 🙏
+
+:::
+
## Limitations
- By default `TextInput` search happens within `UIViewController`/`FragmentActivity` (current screen if you are using `react-native-screens`)
diff --git a/example/src/screens/Examples/Toolbar/index.tsx b/example/src/screens/Examples/Toolbar/index.tsx
index a8d9d2b324..c4b719da0b 100644
--- a/example/src/screens/Examples/Toolbar/index.tsx
+++ b/example/src/screens/Examples/Toolbar/index.tsx
@@ -156,18 +156,26 @@ function Form() {
/>
- ) : null
- }
insets={insets}
opacity={Platform.OS === "ios" ? "4F" : "DD"}
- onDoneCallback={haptic}
- onNextCallback={haptic}
- onPrevCallback={haptic}
- />
+ >
+
+
+
+
+ {showAutoFill ? (
+
+ ) : null}
+
+
+
+
+
>
);
}
@@ -238,12 +246,3 @@ const styles = StyleSheet.create({
marginTop: 32,
},
});
-
-const blur = (
-
-);
diff --git a/src/components/KeyboardToolbar/compound/components/Background.tsx b/src/components/KeyboardToolbar/compound/components/Background.tsx
new file mode 100644
index 0000000000..ea256fdb78
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/Background.tsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+import type { ReactNode } from "react";
+
+const Background: React.FC<{ children: ReactNode }> = ({ children }) => (
+ <>{children}>
+);
+
+export default Background;
diff --git a/src/components/KeyboardToolbar/compound/components/Content.tsx b/src/components/KeyboardToolbar/compound/components/Content.tsx
new file mode 100644
index 0000000000..577c03232e
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/Content.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { StyleSheet, View } from "react-native";
+
+import { TEST_ID_KEYBOARD_TOOLBAR_CONTENT } from "../../constants";
+
+import type { ReactNode } from "react";
+import type { ViewProps } from "react-native";
+
+const Content: React.FC = ({
+ children,
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ flex: {
+ flex: 1,
+ },
+});
+
+export default Content;
diff --git a/src/components/KeyboardToolbar/compound/components/Done.tsx b/src/components/KeyboardToolbar/compound/components/Done.tsx
new file mode 100644
index 0000000000..bf9c2ad815
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/Done.tsx
@@ -0,0 +1,70 @@
+import React from "react";
+import { useCallback, useMemo } from "react";
+import { StyleSheet, Text } from "react-native";
+
+import { useKeyboardState } from "../../../../hooks";
+import { KeyboardController } from "../../../../module";
+import Button from "../../Button";
+import { TEST_ID_KEYBOARD_TOOLBAR_DONE } from "../../constants";
+import { useToolbarContext } from "../context";
+
+import type { ButtonSubProps } from "./types";
+import type { ReactNode } from "react";
+import type { GestureResponderEvent } from "react-native";
+
+const Done: React.FC & { text?: ReactNode }> = ({
+ children,
+ onPress,
+ rippleRadius = 28,
+ text,
+ button: ButtonContainer = Button,
+}) => {
+ const colorScheme = useKeyboardState((state) => state.appearance);
+ const context = useToolbarContext();
+ const { theme } = context;
+
+ const doneStyle = useMemo(
+ () => [styles.doneButton, { color: theme[colorScheme].primary }],
+ [colorScheme, theme],
+ );
+
+ const onPressDone = useCallback(
+ (event: GestureResponderEvent) => {
+ onPress?.(event);
+
+ if (!event.isDefaultPrevented()) {
+ KeyboardController.dismiss();
+ }
+ },
+ [onPress],
+ );
+
+ return (
+
+
+ {children ?? text ?? "Done"}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ doneButton: {
+ fontWeight: "600",
+ fontSize: 15,
+ },
+ doneButtonContainer: {
+ marginRight: 16,
+ marginLeft: 8,
+ },
+});
+
+export default Done;
diff --git a/src/components/KeyboardToolbar/compound/components/Next.tsx b/src/components/KeyboardToolbar/compound/components/Next.tsx
new file mode 100644
index 0000000000..13243b625b
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/Next.tsx
@@ -0,0 +1,55 @@
+import React, { useCallback } from "react";
+
+import { KeyboardController } from "../../../../module";
+import Arrow from "../../Arrow";
+import Button from "../../Button";
+import { TEST_ID_KEYBOARD_TOOLBAR_NEXT } from "../../constants";
+import { useToolbarContext } from "../context";
+
+import type { ButtonSubProps } from "./types";
+import type { GestureResponderEvent } from "react-native";
+
+const Next: React.FC = ({
+ children,
+ onPress,
+ disabled,
+ rippleRadius,
+ style,
+ button: ButtonContainer = Button,
+ icon: IconContainer = Arrow,
+}) => {
+ const context = useToolbarContext();
+ const { theme, isNextDisabled } = context;
+
+ const isDisabled = disabled ?? isNextDisabled;
+
+ const onPressNext = useCallback(
+ (event: GestureResponderEvent) => {
+ onPress?.(event);
+
+ if (!event.isDefaultPrevented()) {
+ KeyboardController.setFocusTo("next");
+ }
+ },
+ [onPress],
+ );
+
+ return (
+
+ {children ?? (
+
+ )}
+
+ );
+};
+
+export default Next;
diff --git a/src/components/KeyboardToolbar/compound/components/Prev.tsx b/src/components/KeyboardToolbar/compound/components/Prev.tsx
new file mode 100644
index 0000000000..894944c573
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/Prev.tsx
@@ -0,0 +1,56 @@
+import React from "react";
+import { useCallback } from "react";
+
+import { KeyboardController } from "../../../../module";
+import Arrow from "../../Arrow";
+import Button from "../../Button";
+import { TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS } from "../../constants";
+import { useToolbarContext } from "../context";
+
+import type { ButtonSubProps } from "./types";
+import type { GestureResponderEvent } from "react-native";
+
+const Prev: React.FC = ({
+ children,
+ onPress: onPressCallback,
+ disabled,
+ rippleRadius,
+ style,
+ button: ButtonContainer = Button,
+ icon: IconContainer = Arrow,
+}) => {
+ const context = useToolbarContext();
+ const { theme, isPrevDisabled } = context;
+
+ const isDisabled = disabled ?? isPrevDisabled;
+
+ const onPressPrev = useCallback(
+ (event: GestureResponderEvent) => {
+ onPressCallback?.(event);
+
+ if (!event.isDefaultPrevented()) {
+ KeyboardController.setFocusTo("prev");
+ }
+ },
+ [onPressCallback],
+ );
+
+ return (
+
+ {children ?? (
+
+ )}
+
+ );
+};
+
+export default Prev;
diff --git a/src/components/KeyboardToolbar/compound/components/index.ts b/src/components/KeyboardToolbar/compound/components/index.ts
new file mode 100644
index 0000000000..82a324edb0
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/index.ts
@@ -0,0 +1,5 @@
+export { default as Background } from "./Background";
+export { default as Content } from "./Content";
+export { default as Done } from "./Done";
+export { default as Next } from "./Next";
+export { default as Prev } from "./Prev";
diff --git a/src/components/KeyboardToolbar/compound/components/types.ts b/src/components/KeyboardToolbar/compound/components/types.ts
new file mode 100644
index 0000000000..9dcb6b5a13
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/components/types.ts
@@ -0,0 +1,15 @@
+import type Arrow from "../../Arrow";
+import type Button from "../../Button";
+import type { ReactNode } from "react";
+import type { GestureResponderEvent, ViewStyle } from "react-native";
+
+export type ButtonSubProps = {
+ children?: ReactNode;
+ onPress?: (event: GestureResponderEvent) => void;
+ disabled?: boolean;
+ testID?: string;
+ rippleRadius?: number;
+ style?: ViewStyle;
+ button?: typeof Button;
+ icon?: typeof Arrow;
+};
diff --git a/src/components/KeyboardToolbar/compound/context.ts b/src/components/KeyboardToolbar/compound/context.ts
new file mode 100644
index 0000000000..6ac025bda7
--- /dev/null
+++ b/src/components/KeyboardToolbar/compound/context.ts
@@ -0,0 +1,25 @@
+import { createContext, useContext } from "react";
+
+import type { KeyboardToolbarTheme } from "../types";
+
+type ToolbarContextType = {
+ theme: KeyboardToolbarTheme;
+ isPrevDisabled: boolean;
+ isNextDisabled: boolean;
+};
+
+export const ToolbarContext = createContext(
+ undefined,
+);
+
+export const useToolbarContext = () => {
+ const context = useContext(ToolbarContext);
+
+ if (!context) {
+ throw new Error(
+ "KeyboardToolbar.* component must be used inside ",
+ );
+ }
+
+ return context;
+};
diff --git a/src/components/KeyboardToolbar/index.tsx b/src/components/KeyboardToolbar/index.tsx
index 741b230ef6..bc2a807fe7 100644
--- a/src/components/KeyboardToolbar/index.tsx
+++ b/src/components/KeyboardToolbar/index.tsx
@@ -1,80 +1,25 @@
-import React, { useCallback, useEffect, useMemo, useState } from "react";
-import { StyleSheet, Text, View } from "react-native";
+import React, { useEffect, useMemo, useState } from "react";
+import { StyleSheet, View } from "react-native";
import { FocusedInputEvents } from "../../bindings";
import { useKeyboardState } from "../../hooks";
-import { KeyboardController } from "../../module";
import KeyboardStickyView from "../KeyboardStickyView";
import Arrow from "./Arrow";
import Button from "./Button";
import { colors } from "./colors";
+import { Background, Content, Done, Next, Prev } from "./compound/components";
+import { ToolbarContext } from "./compound/context";
import {
DEFAULT_OPACITY,
KEYBOARD_HAS_ROUNDED_CORNERS,
KEYBOARD_TOOLBAR_HEIGHT,
OPENED_OFFSET,
TEST_ID_KEYBOARD_TOOLBAR,
- TEST_ID_KEYBOARD_TOOLBAR_CONTENT,
- TEST_ID_KEYBOARD_TOOLBAR_DONE,
- TEST_ID_KEYBOARD_TOOLBAR_NEXT,
- TEST_ID_KEYBOARD_TOOLBAR_PREVIOUS,
} from "./constants";
-import type { HEX, KeyboardToolbarTheme } from "./types";
-import type { KeyboardStickyViewProps } from "../KeyboardStickyView";
+import type { KeyboardToolbarProps } from "./types";
import type { ReactNode } from "react";
-import type { GestureResponderEvent, ViewProps } from "react-native";
-
-type SafeAreaInsets = {
- left: number;
- right: number;
-};
-
-export type KeyboardToolbarProps = Omit<
- ViewProps,
- "style" | "testID" | "children"
-> & {
- /** An element that is shown in the middle of the toolbar. */
- content?: React.JSX.Element | null;
- /** A set of dark/light colors consumed by toolbar component. */
- theme?: KeyboardToolbarTheme;
- /** Custom text for done button. */
- doneText?: ReactNode;
- /** Custom touchable component for toolbar (used for prev/next/done buttons). */
- button?: typeof Button;
- /** Custom icon component used to display next/prev buttons. */
- icon?: typeof Arrow;
- /**
- * Whether to show next and previous buttons. Can be useful to set it to `false` if you have only one input
- * and want to show only `Done` button. Default to `true`.
- */
- showArrows?: boolean;
- /**
- * A callback that is called when the user presses the next button along with the default action.
- */
- onNextCallback?: (event: GestureResponderEvent) => void;
- /**
- * A callback that is called when the user presses the previous button along with the default action.
- */
- onPrevCallback?: (event: GestureResponderEvent) => void;
- /**
- * A callback that is called when the user presses the done button along with the default action.
- */
- onDoneCallback?: (event: GestureResponderEvent) => void;
- /**
- * A component that applies blur effect to the toolbar.
- */
- blur?: React.JSX.Element | null;
- /**
- * A value for container opacity in hexadecimal format (e.g. `ff`). Default value is `ff`.
- */
- opacity?: HEX;
- /**
- * A object containing `left`/`right` properties. Used to specify proper container padding in landscape mode.
- */
- insets?: SafeAreaInsets;
-} & Pick;
/**
* `KeyboardToolbar` is a component that is shown above the keyboard with `Prev`/`Next` buttons from left and
@@ -85,11 +30,20 @@ export type KeyboardToolbarProps = Omit<
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-toolbar|Documentation} page for more details.
* @example
* ```tsx
- *
+ *
+ *
+ *
* ```
*/
-const KeyboardToolbar: React.FC = (props) => {
+const KeyboardToolbar: React.FC & {
+ Background: typeof Background;
+ Content: typeof Content;
+ Prev: typeof Prev;
+ Next: typeof Next;
+ Done: typeof Done;
+} = (props) => {
const {
+ children,
content,
theme = colors,
doneText = "Done",
@@ -113,6 +67,8 @@ const KeyboardToolbar: React.FC = (props) => {
});
const isPrevDisabled = inputs.current === 0;
const isNextDisabled = inputs.current === inputs.count - 1;
+ const buttonContainer = button ?? Button;
+ const iconContainer = icon ?? Arrow;
useEffect(() => {
const subscription = FocusedInputEvents.addListener("focusDidSet", (e) => {
@@ -121,10 +77,6 @@ const KeyboardToolbar: React.FC = (props) => {
return subscription.remove;
}, []);
- const doneStyle = useMemo(
- () => [styles.doneButton, { color: theme[colorScheme].primary }],
- [colorScheme, theme],
- );
const toolbarStyle = useMemo(
() => [
styles.toolbar,
@@ -159,108 +111,98 @@ const KeyboardToolbar: React.FC = (props) => {
}),
[closed, opened],
);
- const ButtonContainer = button || Button;
- const IconContainer = icon || Arrow;
-
- const onPressNext = useCallback(
- (event: GestureResponderEvent) => {
- onNextCallback?.(event);
- if (!event.isDefaultPrevented()) {
- KeyboardController.setFocusTo("next");
+ let backgroundElement: ReactNode = null;
+ let arrowsElement: ReactNode = null;
+ let contentContainer: ReactNode = null;
+ let doneElement: ReactNode = null;
+
+ if (children) {
+ let prevChild: ReactNode = null;
+ let nextChild: ReactNode = null;
+ let contentChild: ReactNode = null;
+ let doneChild: ReactNode = null;
+ let backgroundChild: ReactNode = null;
+
+ React.Children.forEach(children, (child) => {
+ if (!React.isValidElement(child)) {
+ return;
}
- },
- [onNextCallback],
- );
- const onPressPrev = useCallback(
- (event: GestureResponderEvent) => {
- onPrevCallback?.(event);
-
- if (!event.isDefaultPrevented()) {
- KeyboardController.setFocusTo("prev");
+ const type = child.type;
+
+ if (type === Background) {
+ backgroundChild = child;
+ } else if (type === Content) {
+ contentChild = child;
+ } else if (type === Prev) {
+ prevChild = child;
+ } else if (type === Next) {
+ nextChild = child;
+ } else if (type === Done) {
+ doneChild = child;
}
- },
- [onPrevCallback],
- );
- const onPressDone = useCallback(
- (event: GestureResponderEvent) => {
- onDoneCallback?.(event);
+ });
- if (!event.isDefaultPrevented()) {
- KeyboardController.dismiss();
- }
- },
- [onDoneCallback],
+ backgroundElement = backgroundChild;
+ doneElement = doneChild;
+ arrowsElement =
+ prevChild || nextChild ? (
+
+ {prevChild}
+ {nextChild}
+
+ ) : null;
+ contentContainer = contentChild ?? {contentChild};
+ } else {
+ backgroundElement = blur;
+ arrowsElement = showArrows ? (
+
+
+
+
+ ) : null;
+ contentContainer = {content};
+ doneElement = doneText ? (
+
+ ) : null;
+ }
+
+ const contextValue = useMemo(
+ () => ({
+ theme,
+ isPrevDisabled,
+ isNextDisabled,
+ }),
+ [theme, isPrevDisabled, isNextDisabled],
);
return (
-
-
- {blur}
- {showArrows && (
-
-
-
-
-
-
-
-
- )}
-
-
- {content}
+
+
+
+ {backgroundElement}
+ {arrowsElement}
+ {contentContainer}
+ {doneElement}
- {doneText && (
-
-
- {doneText}
-
-
- )}
-
-
+
+
);
};
const styles = StyleSheet.create({
- flex: {
- flex: 1,
- },
toolbar: {
position: "absolute",
bottom: 0,
@@ -273,14 +215,6 @@ const styles = StyleSheet.create({
flexDirection: "row",
paddingLeft: 8,
},
- doneButton: {
- fontWeight: "600",
- fontSize: 15,
- },
- doneButtonContainer: {
- marginRight: 16,
- marginLeft: 8,
- },
floating: {
alignSelf: "center",
borderRadius: 20,
@@ -288,5 +222,11 @@ const styles = StyleSheet.create({
},
});
-export { colors as DefaultKeyboardToolbarTheme };
+KeyboardToolbar.Background = Background;
+KeyboardToolbar.Content = Content;
+KeyboardToolbar.Prev = Prev;
+KeyboardToolbar.Next = Next;
+KeyboardToolbar.Done = Done;
+
+export { colors as DefaultKeyboardToolbarTheme, KeyboardToolbarProps };
export default KeyboardToolbar;
diff --git a/src/components/KeyboardToolbar/types.ts b/src/components/KeyboardToolbar/types.ts
index 2f0f659006..8164f22c00 100644
--- a/src/components/KeyboardToolbar/types.ts
+++ b/src/components/KeyboardToolbar/types.ts
@@ -1,13 +1,21 @@
-import type { ColorValue } from "react-native";
+import type Arrow from "./Arrow";
+import type Button from "./Button";
+import type { KeyboardStickyViewProps } from "../KeyboardStickyView";
+import type { ReactNode } from "react";
+import type {
+ ColorValue,
+ GestureResponderEvent,
+ ViewProps,
+} from "react-native";
type Theme = {
- /** Color for arrow when it's enabled */
+ /** Color for arrow when it's enabled. */
primary: ColorValue;
- /** Color for arrow when it's disabled */
+ /** Color for arrow when it's disabled. */
disabled: ColorValue;
- /** Keyboard toolbar background color */
+ /** Keyboard toolbar background color. */
background: string;
- /** Color for ripple effect (on button touch) on Android */
+ /** Color for ripple effect (on button touch) on Android. */
ripple: ColorValue;
};
export type KeyboardToolbarTheme = {
@@ -38,3 +46,91 @@ type HexSymbol =
| "e"
| "f";
export type HEX = `${HexSymbol}${HexSymbol}`;
+
+type SafeAreaInsets = {
+ left: number;
+ right: number;
+};
+
+export type KeyboardToolbarProps = Omit<
+ ViewProps,
+ "style" | "testID" | "children"
+> & {
+ /**
+ * An element that is shown in the middle of the toolbar.
+ *
+ * @deprecated Use compound API with `` component instead.
+ */
+ content?: React.JSX.Element | null;
+ /** A set of dark/light colors consumed by toolbar component. */
+ theme?: KeyboardToolbarTheme;
+ /**
+ * Custom text for done button.
+ *
+ * @deprecated Use compound API with `` component and `text` prop instead.
+ */
+ doneText?: ReactNode;
+ /**
+ * Custom touchable component for toolbar (used for prev/next/done buttons).
+ *
+ * @deprecated Use `button` property for corresponding element instead:
+ * ```tsx
+ *
+ *
+ *
+ * ```.
+ */
+ button?: typeof Button;
+ /**
+ * Custom icon component used to display next/prev buttons.
+ *
+ * @deprecated Use `icon` property for corresponding element instead:
+ * ```tsx
+ *
+ *
+ *
+ * ```.
+ */
+ icon?: typeof Arrow;
+ /**
+ * Whether to show next and previous buttons. Can be useful to set it to `false` if you have only one input
+ * and want to show only `Done` button. Default to `true`.
+ *
+ * @deprecated Use compound API and conditional rendering for `` and ``.
+ */
+ showArrows?: boolean;
+ /**
+ * A callback that is called when the user presses the next button along with the default action.
+ *
+ * @deprecated Use compound API with `` and `onPress` callback instead.
+ */
+ onNextCallback?: (event: GestureResponderEvent) => void;
+ /**
+ * A callback that is called when the user presses the previous button along with the default action.
+ *
+ * @deprecated Use compound API with `` and `onPress` callback instead.
+ */
+ onPrevCallback?: (event: GestureResponderEvent) => void;
+ /**
+ * A callback that is called when the user presses the done button along with the default action.
+ *
+ * @deprecated Use compound API with `` and `onPress` callback instead.
+ */
+ onDoneCallback?: (event: GestureResponderEvent) => void;
+ /**
+ * A component that applies blur effect to the toolbar.
+ *
+ * @deprecated Use compound API and `` instead.
+ */
+ blur?: React.JSX.Element | null;
+ /**
+ * A value for container opacity in hexadecimal format (e.g. `ff`). Default value is `ff`.
+ */
+ opacity?: HEX;
+ /**
+ * A object containing `left`/`right` properties. Used to specify proper container padding in landscape mode.
+ */
+ insets?: SafeAreaInsets;
+ /** JSX children in case if compound API is used. */
+ children?: ReactNode;
+} & Pick;