From 88db37a989a006e62058360a6f984e039e94e485 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 17 Sep 2025 19:08:58 +0200 Subject: [PATCH 1/8] feat: compound `KeyboardToolbar` --- .../compound/components/Blur.tsx | 9 + .../compound/components/Content.tsx | 25 ++ .../compound/components/Done.tsx | 66 +++++ .../compound/components/Next.tsx | 56 +++++ .../compound/components/Prev.tsx | 57 +++++ .../compound/components/index.ts | 5 + .../compound/components/types.ts | 11 + .../KeyboardToolbar/compound/context.ts | 30 +++ src/components/KeyboardToolbar/index.tsx | 228 +++++++++--------- 9 files changed, 376 insertions(+), 111 deletions(-) create mode 100644 src/components/KeyboardToolbar/compound/components/Blur.tsx create mode 100644 src/components/KeyboardToolbar/compound/components/Content.tsx create mode 100644 src/components/KeyboardToolbar/compound/components/Done.tsx create mode 100644 src/components/KeyboardToolbar/compound/components/Next.tsx create mode 100644 src/components/KeyboardToolbar/compound/components/Prev.tsx create mode 100644 src/components/KeyboardToolbar/compound/components/index.ts create mode 100644 src/components/KeyboardToolbar/compound/components/types.ts create mode 100644 src/components/KeyboardToolbar/compound/context.ts diff --git a/src/components/KeyboardToolbar/compound/components/Blur.tsx b/src/components/KeyboardToolbar/compound/components/Blur.tsx new file mode 100644 index 0000000000..b128418f98 --- /dev/null +++ b/src/components/KeyboardToolbar/compound/components/Blur.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +import type { ReactNode } from "react"; + +const Blur: React.FC<{ children: ReactNode }> = ({ children }) => ( + <>{children} +); + +export default Blur; 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..6dd5cbab49 --- /dev/null +++ b/src/components/KeyboardToolbar/compound/components/Done.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { useCallback, useMemo } from "react"; +import { StyleSheet, Text } from "react-native"; + +import { KeyboardController } from "../../../../module"; +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 = ({ + children, + onPress, + rippleRadius = 28, + text, +}) => { + const context = useToolbarContext(); + const { theme, colorScheme, buttonContainer: ButtonContainer } = 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..ca6fa89625 --- /dev/null +++ b/src/components/KeyboardToolbar/compound/components/Next.tsx @@ -0,0 +1,56 @@ +import React, { useCallback } from "react"; + +import { KeyboardController } from "../../../../module"; +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, +}) => { + const context = useToolbarContext(); + const { + theme, + buttonContainer: ButtonContainer, + iconContainer: IconContainer, + 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..a399f09c0a --- /dev/null +++ b/src/components/KeyboardToolbar/compound/components/Prev.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { useCallback } from "react"; + +import { KeyboardController } from "../../../../module"; +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, +}) => { + const context = useToolbarContext(); + const { + theme, + buttonContainer: ButtonContainer, + iconContainer: IconContainer, + 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..36eaf6b4ab --- /dev/null +++ b/src/components/KeyboardToolbar/compound/components/index.ts @@ -0,0 +1,5 @@ +export { default as Blur } from "./Blur"; +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..ef09f9313d --- /dev/null +++ b/src/components/KeyboardToolbar/compound/components/types.ts @@ -0,0 +1,11 @@ +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; +}; diff --git a/src/components/KeyboardToolbar/compound/context.ts b/src/components/KeyboardToolbar/compound/context.ts new file mode 100644 index 0000000000..69b316b72e --- /dev/null +++ b/src/components/KeyboardToolbar/compound/context.ts @@ -0,0 +1,30 @@ +import { createContext, useContext } from "react"; + +import type Arrow from "../Arrow"; +import type Button from "../Button"; +import type { KeyboardToolbarTheme } from "../types"; + +type ToolbarContextType = { + theme: KeyboardToolbarTheme; + colorScheme: "light" | "dark"; + buttonContainer: typeof Button; + iconContainer: typeof Arrow; + 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..b3d76e3b73 100644 --- a/src/components/KeyboardToolbar/index.tsx +++ b/src/components/KeyboardToolbar/index.tsx @@ -1,24 +1,21 @@ -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 { Blur, 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"; @@ -35,11 +32,19 @@ export type KeyboardToolbarProps = Omit< ViewProps, "style" | "testID" | "children" > & { - /** An element that is shown in the middle of the toolbar. */ + /** + * 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. */ + /** + * 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). */ button?: typeof Button; @@ -48,22 +53,32 @@ export type KeyboardToolbarProps = Omit< /** * 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; /** @@ -74,6 +89,8 @@ export type KeyboardToolbarProps = Omit< * 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; /** @@ -85,11 +102,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 & { + Blur: typeof Blur; + Content: typeof Content; + Prev: typeof Prev; + Next: typeof Next; + Done: typeof Done; +} = (props) => { const { + children, content, theme = colors, doneText = "Done", @@ -121,10 +147,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 +181,94 @@ const KeyboardToolbar: React.FC = (props) => { }), [closed, opened], ); - const ButtonContainer = button || Button; - const IconContainer = icon || Arrow; - const onPressNext = useCallback( - (event: GestureResponderEvent) => { - onNextCallback?.(event); + let blurElement: ReactNode = null; + let arrowsElement: ReactNode = null; + let contentContainer: ReactNode = null; + let doneElement: ReactNode = null; - if (!event.isDefaultPrevented()) { - KeyboardController.setFocusTo("next"); - } - }, - [onNextCallback], - ); - const onPressPrev = useCallback( - (event: GestureResponderEvent) => { - onPrevCallback?.(event); + if (children) { + let prevChild: ReactNode = null; + let nextChild: ReactNode = null; + let contentChild: ReactNode = null; + let doneChild: ReactNode = null; + let blurChild: ReactNode = null; - if (!event.isDefaultPrevented()) { - KeyboardController.setFocusTo("prev"); + React.Children.forEach(children, (child) => { + if (!React.isValidElement(child)) { + return; } - }, - [onPrevCallback], - ); - const onPressDone = useCallback( - (event: GestureResponderEvent) => { - onDoneCallback?.(event); + const type = child.type; - if (!event.isDefaultPrevented()) { - KeyboardController.dismiss(); + if (type === Blur) { + // @ts-expect-error props are untyped + blurChild = child.props.children; + } else if (type === Content) { + contentChild = child; + } else if (type === Prev) { + prevChild = child; + } else if (type === Next) { + nextChild = child; + } else if (type === Done) { + doneChild = child; } - }, - [onDoneCallback], + }); + + blurElement = blurChild; + doneElement = doneChild; + arrowsElement = + prevChild || nextChild ? ( + + {prevChild} + {nextChild} + + ) : null; + contentContainer = contentChild ?? {contentChild}; + } else { + blurElement = blur; + arrowsElement = showArrows ? ( + + + + + ) : null; + contentContainer = {content}; + doneElement = doneText ? ( + + ) : null; + } + + const contextValue = useMemo( + () => ({ + theme, + colorScheme, + buttonContainer: button ?? Button, + iconContainer: icon ?? Arrow, + isPrevDisabled, + isNextDisabled, + }), + [theme, colorScheme, button, icon, isPrevDisabled, isNextDisabled], ); return ( - - - {blur} - {showArrows && ( - - - - - - - - - )} - - - {content} + + + + {blurElement} + {arrowsElement} + {contentContainer} + {doneElement} - {doneText && ( - - - {doneText} - - - )} - - + + ); }; const styles = StyleSheet.create({ - flex: { - flex: 1, - }, toolbar: { position: "absolute", bottom: 0, @@ -273,14 +281,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 +288,11 @@ const styles = StyleSheet.create({ }, }); +KeyboardToolbar.Blur = Blur; +KeyboardToolbar.Content = Content; +KeyboardToolbar.Prev = Prev; +KeyboardToolbar.Next = Next; +KeyboardToolbar.Done = Done; + export { colors as DefaultKeyboardToolbarTheme }; export default KeyboardToolbar; From 3f9260e0d9aaefe68e18b295ff5e24db28e1d3d3 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 18 Sep 2025 12:29:07 +0200 Subject: [PATCH 2/8] feat: rename components, move prop types into types file, update documentation --- .../api/components/keyboard-toolbar/index.mdx | 170 +++++++++++++----- .../compound/components/Background.tsx | 9 + .../compound/components/Blur.tsx | 9 - .../compound/components/index.ts | 2 +- src/components/KeyboardToolbar/index.tsx | 85 +-------- src/components/KeyboardToolbar/types.ts | 88 ++++++++- 6 files changed, 221 insertions(+), 142 deletions(-) create mode 100644 src/components/KeyboardToolbar/compound/components/Background.tsx delete mode 100644 src/components/KeyboardToolbar/compound/components/Blur.tsx diff --git a/docs/docs/api/components/keyboard-toolbar/index.mdx b/docs/docs/api/components/keyboard-toolbar/index.mdx index d0fbaee09a..a0ec1a7059 100644 --- a/docs/docs/api/components/keyboard-toolbar/index.mdx +++ b/docs/docs/api/components/keyboard-toolbar/index.mdx @@ -37,6 +37,56 @@ 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. + +## Compound Components + +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. + +### `` + +Renders a custom background (e.g., blur effect) that overlays the entire toolbar. Accepts any React node as children. + +```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 */} +; +``` + +### `` ## Props @@ -69,53 +119,6 @@ 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). - -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. -::: - -```tsx -import { BlurView } from "@react-native-community/blur"; -import { - KeyboardToolbar, - KeyboardToolbarProps, -} from "react-native-keyboard-controller"; - -const CustomBlur: KeyboardToolbarProps["blur"] = ({ children }) => ( - - {children} - -); - -// ... - -; -``` - -### `content` - -This property allows you to show a custom content in the middle of the toolbar. It accepts JSX element. Default value is `null`. - -```tsx - - ) : null - } -/> -``` - ### `doneText` The property that allows to specify custom text for `Done` button. @@ -345,7 +348,11 @@ export default function ToolbarExample() { - + + + + + ); } @@ -444,6 +451,73 @@ 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={} /> + +// 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} + +``` + ## Limitations - By default `TextInput` search happens within `UIViewController`/`FragmentActivity` (current screen if you are using `react-native-screens`) 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/Blur.tsx b/src/components/KeyboardToolbar/compound/components/Blur.tsx deleted file mode 100644 index b128418f98..0000000000 --- a/src/components/KeyboardToolbar/compound/components/Blur.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -import type { ReactNode } from "react"; - -const Blur: React.FC<{ children: ReactNode }> = ({ children }) => ( - <>{children} -); - -export default Blur; diff --git a/src/components/KeyboardToolbar/compound/components/index.ts b/src/components/KeyboardToolbar/compound/components/index.ts index 36eaf6b4ab..82a324edb0 100644 --- a/src/components/KeyboardToolbar/compound/components/index.ts +++ b/src/components/KeyboardToolbar/compound/components/index.ts @@ -1,4 +1,4 @@ -export { default as Blur } from "./Blur"; +export { default as Background } from "./Background"; export { default as Content } from "./Content"; export { default as Done } from "./Done"; export { default as Next } from "./Next"; diff --git a/src/components/KeyboardToolbar/index.tsx b/src/components/KeyboardToolbar/index.tsx index b3d76e3b73..e8ebdc4a5a 100644 --- a/src/components/KeyboardToolbar/index.tsx +++ b/src/components/KeyboardToolbar/index.tsx @@ -8,7 +8,7 @@ import KeyboardStickyView from "../KeyboardStickyView"; import Arrow from "./Arrow"; import Button from "./Button"; import { colors } from "./colors"; -import { Blur, Content, Done, Next, Prev } from "./compound/components"; +import { Background, Content, Done, Next, Prev } from "./compound/components"; import { ToolbarContext } from "./compound/context"; import { DEFAULT_OPACITY, @@ -18,80 +18,8 @@ import { TEST_ID_KEYBOARD_TOOLBAR, } 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. - * - * @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). */ - 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`. - * - * @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; /** * `KeyboardToolbar` is a component that is shown above the keyboard with `Prev`/`Next` buttons from left and @@ -108,7 +36,7 @@ export type KeyboardToolbarProps = Omit< * ``` */ const KeyboardToolbar: React.FC & { - Blur: typeof Blur; + Background: typeof Background; Content: typeof Content; Prev: typeof Prev; Next: typeof Next; @@ -200,9 +128,8 @@ const KeyboardToolbar: React.FC & { } const type = child.type; - if (type === Blur) { - // @ts-expect-error props are untyped - blurChild = child.props.children; + if (type === Background) { + blurChild = child; } else if (type === Content) { contentChild = child; } else if (type === Prev) { @@ -288,7 +215,7 @@ const styles = StyleSheet.create({ }, }); -KeyboardToolbar.Blur = Blur; +KeyboardToolbar.Background = Background; KeyboardToolbar.Content = Content; KeyboardToolbar.Prev = Prev; KeyboardToolbar.Next = Next; diff --git a/src/components/KeyboardToolbar/types.ts b/src/components/KeyboardToolbar/types.ts index 2f0f659006..910c821734 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,73 @@ 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). */ + 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`. + * + * @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; From 2e95499e56d120096b50b70d3d35460da49148c6 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 18 Sep 2025 12:33:15 +0200 Subject: [PATCH 3/8] fix: TS compilation issues --- src/components/KeyboardToolbar/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/KeyboardToolbar/index.tsx b/src/components/KeyboardToolbar/index.tsx index e8ebdc4a5a..7fb2ab5d33 100644 --- a/src/components/KeyboardToolbar/index.tsx +++ b/src/components/KeyboardToolbar/index.tsx @@ -221,5 +221,5 @@ KeyboardToolbar.Prev = Prev; KeyboardToolbar.Next = Next; KeyboardToolbar.Done = Done; -export { colors as DefaultKeyboardToolbarTheme }; +export { colors as DefaultKeyboardToolbarTheme, KeyboardToolbarProps }; export default KeyboardToolbar; From 86698b380dc11d70a46fcbbb7fae90ef04b350f4 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 22 Sep 2025 10:44:38 +0200 Subject: [PATCH 4/8] feat: button/icon on per element specification --- .../compound/components/Done.tsx | 6 ++-- .../compound/components/Next.tsx | 11 ++++--- .../compound/components/Prev.tsx | 11 ++++--- .../compound/components/types.ts | 4 +++ .../KeyboardToolbar/compound/context.ts | 4 --- src/components/KeyboardToolbar/index.tsx | 30 ++++++++++++------- src/components/KeyboardToolbar/types.ts | 22 ++++++++++++-- 7 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/components/KeyboardToolbar/compound/components/Done.tsx b/src/components/KeyboardToolbar/compound/components/Done.tsx index 6dd5cbab49..9bce93fc38 100644 --- a/src/components/KeyboardToolbar/compound/components/Done.tsx +++ b/src/components/KeyboardToolbar/compound/components/Done.tsx @@ -3,6 +3,7 @@ import { useCallback, useMemo } from "react"; import { StyleSheet, Text } from "react-native"; import { KeyboardController } from "../../../../module"; +import Button from "../../Button"; import { TEST_ID_KEYBOARD_TOOLBAR_DONE } from "../../constants"; import { useToolbarContext } from "../context"; @@ -10,14 +11,15 @@ import type { ButtonSubProps } from "./types"; import type { ReactNode } from "react"; import type { GestureResponderEvent } from "react-native"; -const Done: React.FC = ({ +const Done: React.FC & { text?: ReactNode }> = ({ children, onPress, rippleRadius = 28, text, + button: ButtonContainer = Button, }) => { const context = useToolbarContext(); - const { theme, colorScheme, buttonContainer: ButtonContainer } = context; + const { theme, colorScheme } = context; const doneStyle = useMemo( () => [styles.doneButton, { color: theme[colorScheme].primary }], diff --git a/src/components/KeyboardToolbar/compound/components/Next.tsx b/src/components/KeyboardToolbar/compound/components/Next.tsx index ca6fa89625..13243b625b 100644 --- a/src/components/KeyboardToolbar/compound/components/Next.tsx +++ b/src/components/KeyboardToolbar/compound/components/Next.tsx @@ -1,6 +1,8 @@ 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"; @@ -13,14 +15,11 @@ const Next: React.FC = ({ disabled, rippleRadius, style, + button: ButtonContainer = Button, + icon: IconContainer = Arrow, }) => { const context = useToolbarContext(); - const { - theme, - buttonContainer: ButtonContainer, - iconContainer: IconContainer, - isNextDisabled, - } = context; + const { theme, isNextDisabled } = context; const isDisabled = disabled ?? isNextDisabled; diff --git a/src/components/KeyboardToolbar/compound/components/Prev.tsx b/src/components/KeyboardToolbar/compound/components/Prev.tsx index a399f09c0a..894944c573 100644 --- a/src/components/KeyboardToolbar/compound/components/Prev.tsx +++ b/src/components/KeyboardToolbar/compound/components/Prev.tsx @@ -2,6 +2,8 @@ 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"; @@ -14,14 +16,11 @@ const Prev: React.FC = ({ disabled, rippleRadius, style, + button: ButtonContainer = Button, + icon: IconContainer = Arrow, }) => { const context = useToolbarContext(); - const { - theme, - buttonContainer: ButtonContainer, - iconContainer: IconContainer, - isPrevDisabled, - } = context; + const { theme, isPrevDisabled } = context; const isDisabled = disabled ?? isPrevDisabled; diff --git a/src/components/KeyboardToolbar/compound/components/types.ts b/src/components/KeyboardToolbar/compound/components/types.ts index ef09f9313d..9dcb6b5a13 100644 --- a/src/components/KeyboardToolbar/compound/components/types.ts +++ b/src/components/KeyboardToolbar/compound/components/types.ts @@ -1,3 +1,5 @@ +import type Arrow from "../../Arrow"; +import type Button from "../../Button"; import type { ReactNode } from "react"; import type { GestureResponderEvent, ViewStyle } from "react-native"; @@ -8,4 +10,6 @@ export type ButtonSubProps = { 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 index 69b316b72e..1af818aa56 100644 --- a/src/components/KeyboardToolbar/compound/context.ts +++ b/src/components/KeyboardToolbar/compound/context.ts @@ -1,14 +1,10 @@ import { createContext, useContext } from "react"; -import type Arrow from "../Arrow"; -import type Button from "../Button"; import type { KeyboardToolbarTheme } from "../types"; type ToolbarContextType = { theme: KeyboardToolbarTheme; colorScheme: "light" | "dark"; - buttonContainer: typeof Button; - iconContainer: typeof Arrow; isPrevDisabled: boolean; isNextDisabled: boolean; }; diff --git a/src/components/KeyboardToolbar/index.tsx b/src/components/KeyboardToolbar/index.tsx index 7fb2ab5d33..7845b02162 100644 --- a/src/components/KeyboardToolbar/index.tsx +++ b/src/components/KeyboardToolbar/index.tsx @@ -67,6 +67,8 @@ const KeyboardToolbar: React.FC & { }); 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) => { @@ -110,7 +112,7 @@ const KeyboardToolbar: React.FC & { [closed, opened], ); - let blurElement: ReactNode = null; + let backgroundElement: ReactNode = null; let arrowsElement: ReactNode = null; let contentContainer: ReactNode = null; let doneElement: ReactNode = null; @@ -120,7 +122,7 @@ const KeyboardToolbar: React.FC & { let nextChild: ReactNode = null; let contentChild: ReactNode = null; let doneChild: ReactNode = null; - let blurChild: ReactNode = null; + let backgroundChild: ReactNode = null; React.Children.forEach(children, (child) => { if (!React.isValidElement(child)) { @@ -129,7 +131,7 @@ const KeyboardToolbar: React.FC & { const type = child.type; if (type === Background) { - blurChild = child; + backgroundChild = child; } else if (type === Content) { contentChild = child; } else if (type === Prev) { @@ -141,7 +143,7 @@ const KeyboardToolbar: React.FC & { } }); - blurElement = blurChild; + backgroundElement = backgroundChild; doneElement = doneChild; arrowsElement = prevChild || nextChild ? ( @@ -152,16 +154,24 @@ const KeyboardToolbar: React.FC & { ) : null; contentContainer = contentChild ?? {contentChild}; } else { - blurElement = blur; + backgroundElement = blur; arrowsElement = showArrows ? ( - - + + ) : null; contentContainer = {content}; doneElement = doneText ? ( - + ) : null; } @@ -169,8 +179,6 @@ const KeyboardToolbar: React.FC & { () => ({ theme, colorScheme, - buttonContainer: button ?? Button, - iconContainer: icon ?? Arrow, isPrevDisabled, isNextDisabled, }), @@ -185,7 +193,7 @@ const KeyboardToolbar: React.FC & { style={containerStyle} > - {blurElement} + {backgroundElement} {arrowsElement} {contentContainer} {doneElement} diff --git a/src/components/KeyboardToolbar/types.ts b/src/components/KeyboardToolbar/types.ts index 910c821734..8164f22c00 100644 --- a/src/components/KeyboardToolbar/types.ts +++ b/src/components/KeyboardToolbar/types.ts @@ -70,9 +70,27 @@ export type KeyboardToolbarProps = Omit< * @deprecated Use compound API with `` component and `text` prop instead. */ doneText?: ReactNode; - /** Custom touchable component for toolbar (used for prev/next/done buttons). */ + /** + * 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. */ + /** + * 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 From f470b198d1ea00a65491e9b2d923dbb250a7bdb0 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 22 Sep 2025 11:21:22 +0200 Subject: [PATCH 5/8] docs: migrate more props to a new API --- .../api/components/keyboard-toolbar/index.mdx | 215 ++++++++++-------- 1 file changed, 116 insertions(+), 99 deletions(-) diff --git a/docs/docs/api/components/keyboard-toolbar/index.mdx b/docs/docs/api/components/keyboard-toolbar/index.mdx index a0ec1a7059..d2ba548881 100644 --- a/docs/docs/api/components/keyboard-toolbar/index.mdx +++ b/docs/docs/api/components/keyboard-toolbar/index.mdx @@ -88,82 +88,50 @@ import { KeyboardToolbar } from "react-native-keyboard-controller"; ### `` -## Props - -### [`View Props`](https://reactnative.dev/docs/view#props) - -Inherits [View Props](https://reactnative.dev/docs/view#props). - -### [`KeyboardStickyViewProps`](./keyboard-sticky-view) - -Inherits [KeyboardStickyViewProps](./keyboard-sticky-view). - -### `button` - -This property allows to render custom touchable component for next, previous and done button. - -```tsx -import { TouchableOpacity } from "react-native-gesture-handler"; -import { - KeyboardToolbar, - KeyboardToolbarProps, -} from "react-native-keyboard-controller"; - -const CustomButton: KeyboardToolbarProps["button"] = ({ - children, - onPress, -}) => {children}; - -// ... - -; -``` - -### `doneText` - -The property that allows to specify custom text for `Done` button. +#### `onPress` -```tsx - -``` - -### `icon` - -`icon` property allows to render custom icons for prev and next buttons. +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 -import { Text } from "react-native"; -import { - KeyboardToolbar, - KeyboardToolbarProps, -} from "react-native-keyboard-controller"; +import { Platform } from "react-native"; +import { KeyboardToolbar } from "react-native-keyboard-controller"; +import { trigger } from "react-native-haptic-feedback"; -const Icon: KeyboardToolbarProps["icon"] = ({ type }) => { - return {type === "next" ? "⬇️" : "⬆️"}; +const options = { + enableVibrateFallback: true, + ignoreAndroidSystemSettings: false, }; +const haptic = () => + trigger(Platform.OS === "ios" ? "impactLight" : "keyboardTap", options); // ... -; + + +; ``` -### `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: +:::tip Prevent Default Action +To prevent the default action, call `e.preventDefault()` inside the callback: ```tsx -import { useSafeAreaInsets } from "react-native-safe-area-context"; - -// ... + + { + // the focus will not be moved to the prev input + e.preventDefault(); + }} + /> + +``` -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"; @@ -179,25 +147,42 @@ 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). +#### `text` + +The property that allows to specify custom text for `Done` button. + +```tsx + + + +``` + +#### `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"; @@ -213,55 +198,91 @@ 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` +## Props -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). +### [`View Props`](https://reactnative.dev/docs/view#props) + +Inherits [View Props](https://reactnative.dev/docs/view#props). + +### [`KeyboardStickyViewProps`](./keyboard-sticky-view) + +Inherits [KeyboardStickyViewProps](./keyboard-sticky-view). + +### `button` + +This property allows to render custom touchable component for next, previous and done button. ```tsx -import { Platform } from "react-native"; -import { KeyboardToolbar } from "react-native-keyboard-controller"; -import { trigger } from "react-native-haptic-feedback"; +import { TouchableOpacity } from "react-native-gesture-handler"; +import { + KeyboardToolbar, + KeyboardToolbarProps, +} from "react-native-keyboard-controller"; -const options = { - enableVibrateFallback: true, - ignoreAndroidSystemSettings: false, -}; -const haptic = () => - trigger(Platform.OS === "ios" ? "impactLight" : "keyboardTap", options); +const CustomButton: KeyboardToolbarProps["button"] = ({ + children, + onPress, +}) => {children}; // ... -; +; ``` -:::tip Prevent Default Action -To prevent the default action, call `e.preventDefault()` inside the callback: +### `icon` + +`icon` property allows to render custom icons for prev and next buttons. ```tsx - { - e.preventDefault(); // the focus will not be moved to the prev input - }} -/> +import { Text } from "react-native"; +import { + KeyboardToolbar, + KeyboardToolbarProps, +} from "react-native-keyboard-controller"; + +const Icon: KeyboardToolbarProps["icon"] = ({ type }) => { + return {type === "next" ? "⬇️" : "⬆️"}; +}; + +// ... + +; ``` -::: +### `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 +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +// ... + +const insets = useSafeAreaInsets(); + +; +``` ### `opacity` @@ -271,10 +292,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: @@ -473,7 +490,7 @@ To migrate from the legacy prop-based API to the compound API: ```tsx // Old: -} blur={} /> +} blur={} doneText="Close" /> // New: From 3313cb4fd57b5608f507b357f1a67d70b832ff68 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 23 Sep 2025 10:23:32 +0200 Subject: [PATCH 6/8] refactor: revision after self review --- .../src/screens/Examples/Toolbar/index.tsx | 37 +++++++++---------- .../api/components/keyboard-toolbar/index.mdx | 6 +++ .../src/screens/Examples/Toolbar/index.tsx | 37 +++++++++---------- .../compound/components/Done.tsx | 4 +- .../KeyboardToolbar/compound/context.ts | 1 - src/components/KeyboardToolbar/index.tsx | 3 +- 6 files changed, 46 insertions(+), 42 deletions(-) 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 d2ba548881..87c5fc844e 100644 --- a/docs/docs/api/components/keyboard-toolbar/index.mdx +++ b/docs/docs/api/components/keyboard-toolbar/index.mdx @@ -535,6 +535,12 @@ To migrate from the legacy prop-based API to the compound API: ``` +:::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/Done.tsx b/src/components/KeyboardToolbar/compound/components/Done.tsx index 9bce93fc38..bf9c2ad815 100644 --- a/src/components/KeyboardToolbar/compound/components/Done.tsx +++ b/src/components/KeyboardToolbar/compound/components/Done.tsx @@ -2,6 +2,7 @@ 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"; @@ -18,8 +19,9 @@ const Done: React.FC & { text?: ReactNode }> = ({ text, button: ButtonContainer = Button, }) => { + const colorScheme = useKeyboardState((state) => state.appearance); const context = useToolbarContext(); - const { theme, colorScheme } = context; + const { theme } = context; const doneStyle = useMemo( () => [styles.doneButton, { color: theme[colorScheme].primary }], diff --git a/src/components/KeyboardToolbar/compound/context.ts b/src/components/KeyboardToolbar/compound/context.ts index 1af818aa56..6ac025bda7 100644 --- a/src/components/KeyboardToolbar/compound/context.ts +++ b/src/components/KeyboardToolbar/compound/context.ts @@ -4,7 +4,6 @@ import type { KeyboardToolbarTheme } from "../types"; type ToolbarContextType = { theme: KeyboardToolbarTheme; - colorScheme: "light" | "dark"; isPrevDisabled: boolean; isNextDisabled: boolean; }; diff --git a/src/components/KeyboardToolbar/index.tsx b/src/components/KeyboardToolbar/index.tsx index 7845b02162..bc2a807fe7 100644 --- a/src/components/KeyboardToolbar/index.tsx +++ b/src/components/KeyboardToolbar/index.tsx @@ -178,11 +178,10 @@ const KeyboardToolbar: React.FC & { const contextValue = useMemo( () => ({ theme, - colorScheme, isPrevDisabled, isNextDisabled, }), - [theme, colorScheme, button, icon, isPrevDisabled, isNextDisabled], + [theme, isPrevDisabled, isNextDisabled], ); return ( From 1c0b4d9fad3c8f0c2319efe1e78e3bc711ca8558 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 23 Sep 2025 10:24:37 +0200 Subject: [PATCH 7/8] fix: added forgotten committed styles for bottom sheet (in previous PR) --- FabricExample/src/screens/Examples/AwareScrollView/styles.ts | 3 +++ 1 file changed, 3 insertions(+) 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, + }, }); From 2c3d7f3fd547313e37ca8d688f7ed3fb6555a0da Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 23 Sep 2025 11:05:33 +0200 Subject: [PATCH 8/8] docs: remove all deprecated props --- .../api/components/keyboard-toolbar/index.mdx | 162 +++++++++++++----- 1 file changed, 117 insertions(+), 45 deletions(-) diff --git a/docs/docs/api/components/keyboard-toolbar/index.mdx b/docs/docs/api/components/keyboard-toolbar/index.mdx index 87c5fc844e..bf0d023949 100644 --- a/docs/docs/api/components/keyboard-toolbar/index.mdx +++ b/docs/docs/api/components/keyboard-toolbar/index.mdx @@ -88,6 +88,51 @@ import { KeyboardToolbar } from "react-native-keyboard-controller"; ### `` +#### `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}; + +// ... + + + +; +``` + +#### `icon` + +`icon` property allows to render custom icons. + +```tsx +import { Text } from "react-native"; +import { + KeyboardToolbar, + KeyboardToolbarProps, +} from "react-native-keyboard-controller"; + +const Icon: KeyboardToolbarProps["icon"] = ({ type }) => { + return {type === "next" ? "⬇️" : "⬆️"}; +}; + +// ... + + + +; +``` + #### `onPress` 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). @@ -129,6 +174,51 @@ To prevent the default action, call `e.preventDefault()` inside the callback: ### `` +#### `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}; + +// ... + + + +; +``` + +#### `icon` + +`icon` property allows to render custom icons. + +```tsx +import { Text } from "react-native"; +import { + KeyboardToolbar, + KeyboardToolbarProps, +} from "react-native-keyboard-controller"; + +const Icon: KeyboardToolbarProps["icon"] = ({ type }) => { + return {type === "next" ? "⬇️" : "⬆️"}; +}; + +// ... + + + +; +``` + #### `onPress` 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). @@ -170,14 +260,27 @@ To prevent the default action, call `e.preventDefault()` inside the callback: ### `` -#### `text` +#### `button` -The property that allows to specify custom text for `Done` 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` @@ -219,56 +322,25 @@ To prevent the default action, call `e.preventDefault()` inside the callback: ::: -## Props - -### [`View Props`](https://reactnative.dev/docs/view#props) - -Inherits [View Props](https://reactnative.dev/docs/view#props). - -### [`KeyboardStickyViewProps`](./keyboard-sticky-view) - -Inherits [KeyboardStickyViewProps](./keyboard-sticky-view). - -### `button` +#### `text` -This property allows to render custom touchable component for next, previous and done button. +The property that allows to specify custom text for `Done` button. ```tsx -import { TouchableOpacity } from "react-native-gesture-handler"; -import { - KeyboardToolbar, - KeyboardToolbarProps, -} from "react-native-keyboard-controller"; - -const CustomButton: KeyboardToolbarProps["button"] = ({ - children, - onPress, -}) => {children}; - -// ... - -; + + + ``` -### `icon` - -`icon` property allows to render custom icons for prev and next buttons. +## Props -```tsx -import { Text } from "react-native"; -import { - KeyboardToolbar, - KeyboardToolbarProps, -} from "react-native-keyboard-controller"; +### [`View Props`](https://reactnative.dev/docs/view#props) -const Icon: KeyboardToolbarProps["icon"] = ({ type }) => { - return {type === "next" ? "⬇️" : "⬆️"}; -}; +Inherits [View Props](https://reactnative.dev/docs/view#props). -// ... +### [`KeyboardStickyViewProps`](./keyboard-sticky-view) -; -``` +Inherits [KeyboardStickyViewProps](./keyboard-sticky-view). ### `insets`