Skip to content

feat: components #229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Sep 7, 2023
Merged
1 change: 1 addition & 0 deletions FabricExample/src/constants/screenNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export enum ScreenNames {
INTERACTIVE_KEYBOARD = 'INTERACTIVE_KEYBOARD',
INTERACTIVE_KEYBOARD_IOS = 'INTERACTIVE_KEYBOARD_IOS',
NATIVE_STACK = 'NATIVE_STACK',
KEYBOARD_AVOIDING_VIEW = 'KEYBOARD_AVOIDING_VIEW',
}
10 changes: 10 additions & 0 deletions FabricExample/src/navigation/ExamplesStack/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import NonUIProps from '../../screens/Examples/NonUIProps';
import InteractiveKeyboard from '../../screens/Examples/InteractiveKeyboard';
import InteractiveKeyboardIOS from '../../screens/Examples/InteractiveKeyboardIOS';
import NativeStack from '../NestedStack';
import KeyboardAvoidingViewExample from '../../screens/Examples/KeyboardAvoidingView';

export type ExamplesStackParamList = {
[ScreenNames.ANIMATED_EXAMPLE]: undefined;
Expand All @@ -25,6 +26,7 @@ export type ExamplesStackParamList = {
[ScreenNames.INTERACTIVE_KEYBOARD]: undefined;
[ScreenNames.INTERACTIVE_KEYBOARD_IOS]: undefined;
[ScreenNames.NATIVE_STACK]: undefined;
[ScreenNames.KEYBOARD_AVOIDING_VIEW]: undefined;
};

const Stack = createStackNavigator<ExamplesStackParamList>();
Expand Down Expand Up @@ -61,6 +63,9 @@ const options = {
[ScreenNames.NATIVE_STACK]: {
title: 'Native stack',
},
[ScreenNames.KEYBOARD_AVOIDING_VIEW]: {
title: 'KeyboardAvoidingView',
},
};

const ExamplesStack = () => (
Expand Down Expand Up @@ -115,6 +120,11 @@ const ExamplesStack = () => (
component={NativeStack}
options={options[ScreenNames.NATIVE_STACK]}
/>
<Stack.Screen
name={ScreenNames.KEYBOARD_AVOIDING_VIEW}
component={KeyboardAvoidingViewExample}
options={options[ScreenNames.KEYBOARD_AVOIDING_VIEW]}
/>
</Stack.Navigator>
);

Expand Down
56 changes: 56 additions & 0 deletions FabricExample/src/screens/Examples/KeyboardAvoidingView/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState, useEffect } from 'react';
import {
Platform,
KeyboardAvoidingView as RNKeyboardAvoidingView,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
import { StackScreenProps } from '@react-navigation/stack';
import { ExamplesStackParamList } from '../../../navigation/ExamplesStack';
import styles from './styles';

type Props = StackScreenProps<ExamplesStackParamList>;

export default function KeyboardAvoidingViewExample({ navigation }: Props) {
const [isPackageImplementation, setPackageImplementation] = useState(true);

useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Text
style={styles.header}
onPress={() => setPackageImplementation((value) => !value)}
>
{isPackageImplementation ? 'Package' : 'RN'}
</Text>
),
});
}, [isPackageImplementation]);

const Container = isPackageImplementation
? KeyboardAvoidingView
: RNKeyboardAvoidingView;

return (
<Container
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
contentContainerStyle={styles.container}
keyboardVerticalOffset={100}
style={styles.content}
>
<View style={styles.inner}>
<Text style={styles.heading}>Header</Text>
<View>
<TextInput placeholder="Username" style={styles.textInput} />
<TextInput placeholder="Password" style={styles.textInput} />
</View>
<TouchableOpacity style={styles.button}>
<Text style={styles.text}>Submit</Text>
</TouchableOpacity>
</View>
</Container>
);
}
47 changes: 47 additions & 0 deletions FabricExample/src/screens/Examples/KeyboardAvoidingView/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { StyleSheet } from 'react-native';

export default StyleSheet.create({
header: {
color: 'black',
marginRight: 12,
},
container: {
flex: 1,
},
content: {
flex: 1,
maxHeight: 600,
},
heading: {
color: 'black',
fontSize: 36,
marginBottom: 48,
fontWeight: '600',
},
inner: {
padding: 24,
flex: 1,
justifyContent: 'space-between',
},
textInput: {
height: 45,
borderColor: '#000000',
borderWidth: 1,
borderRadius: 10,
marginBottom: 36,
paddingLeft: 10,
},
button: {
marginTop: 12,
height: 45,
borderRadius: 10,
backgroundColor: 'rgb(40, 64, 147)',
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontWeight: '500',
fontSize: 16,
color: 'white',
},
});
5 changes: 5 additions & 0 deletions FabricExample/src/screens/Examples/Main/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ export const examples: Example[] = [
info: ScreenNames.NATIVE_STACK,
icons: '⚛️',
},
{
title: 'KeyboardAvoidingView',
info: ScreenNames.KEYBOARD_AVOIDING_VIEW,
icons: '😶',
},
];
1 change: 0 additions & 1 deletion FabricExample/src/screens/Examples/Main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { RootStackParamList } from '../../../navigation/RootStack';

const styles = StyleSheet.create({
scrollViewContainer: {
flex: 1,
paddingVertical: 10,
paddingHorizontal: 8,
},
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/api/components/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "📚 Components",
"position": 2
}
124 changes: 124 additions & 0 deletions docs/docs/api/components/keyboard-avoiding-view.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
keywords: [react-native-keyboard-controller, KeyboardAvoidingView, keyboard avoiding view, avoid keyboard, android]
---

# KeyboardAvoidingView

This component will automatically adjust its height, position, or bottom padding based on the keyboard height to remain visible while the virtual keyboard is displayed.

## Why another `KeyboardAvoidingView` is needed?

This new `KeyboardAvoidingView` maintains the familiar React Native [API](https://reactnative.dev/docs/keyboardavoidingview) but ensures consistent behavior and animations on both `iOS` and `Android` platforms. Unlike the existing solution, which primarily caters to `iOS`, this component eliminates platform discrepancies, providing a unified user experience. By reproducing the same animations and behaviors on both platforms, it simplifies cross-platform development, meets user expectations for consistency, and enhances code maintainability. Ultimately, it addresses the need for a reliable and uniform keyboard interaction solution across different devices.

Below is a visual difference between the implementations (the animation is _**4x**_ times slower for better visual perception).

import KeyboardAvoidingViewComparison from '../../../src/components/KeyboardAvoidingViewComparison'

<KeyboardAvoidingViewComparison />

:::info Found a bug? Help the project and report it!

If you found any bugs or inconsistent behavior comparing to `react-native` implementation - 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 🙏

Also if there is any well-known problems in original `react-native` implementation which can not be fixed for a long time and they are present in this implementation as well - also feel free to submit an [issue](https://github.com/kirillzyusko/react-native-keyboard-controller/issues/new?assignees=kirillzyusko&labels=bug&template=bug_report.md&title=). Let's make this world better together 😎

:::

## Example

```tsx
import React from 'react';
import {
Text,
TextInput,
TouchableOpacity,
View,
StyleSheet,
} from 'react-native';
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';

export default function KeyboardAvoidingViewExample() {
return (
<KeyboardAvoidingView
behavior={'padding'}
contentContainerStyle={styles.container}
keyboardVerticalOffset={100}
style={styles.content}
>
<View style={styles.inner}>
<Text style={styles.heading}>Header</Text>
<View>
<TextInput placeholder="Username" style={styles.textInput} />
<TextInput placeholder="Password" style={styles.textInput} />
</View>
<TouchableOpacity style={styles.button}>
<Text style={styles.text}>Submit</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
maxHeight: 600,
},
heading: {
fontSize: 36,
marginBottom: 48,
fontWeight: '600',
},
inner: {
padding: 24,
flex: 1,
justifyContent: 'space-between',
},
textInput: {
height: 45,
borderColor: '#000000',
borderWidth: 1,
borderRadius: 10,
marginBottom: 36,
paddingLeft: 10,
},
button: {
marginTop: 12,
height: 45,
borderRadius: 10,
backgroundColor: 'rgb(40, 64, 147)',
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontWeight: '500',
fontSize: 16,
color: 'white',
},
});
```

## Props

### `behavior`

Specify how to react to the presence of the keyboard. Could be one value of:

- `position`
- `padding`
- `height`

### `contentContainerStyle`

The style of the content container (View) when behavior is `position`.

### `enabled`

A boolean prop indicating whether `KeyboardAvoidingView` is enabled or disabled. Default is `true`.

### `keyboardVerticalOffset`

This is the distance between the top of the user screen and the react native view, may be non-zero in some use cases. Default is `0`.
2 changes: 1 addition & 1 deletion docs/docs/api/hooks/_category_.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"label": "Hooks",
"label": "🎣 Hooks",
"position": 1
}
47 changes: 47 additions & 0 deletions docs/src/components/KeyboardAvoidingViewComparison/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { CSSProperties } from 'react';
import Lottie from 'lottie-react';

import before from './kav.lottie.json';
import after from './kav-animated.lottie.json';

const withoutBorders = { border: 'none' };
const lottieView = { paddingLeft: '20%', paddingRight: '20%' };
const label: CSSProperties = {
...withoutBorders,
maxWidth: 400,
textAlign: 'center',
};
const labels = {
...withoutBorders,
backgroundColor: '#00000000',
};

export default function KeyboardAvoidingViewComparison(): JSX.Element {
return (
<table>
<tbody>
<tr style={withoutBorders}>
<td style={withoutBorders}>
<Lottie animationData={before} style={lottieView} loop />
</td>
<td style={withoutBorders}>
<Lottie animationData={after} style={lottieView} loop />
</td>
</tr>
<tr style={labels}>
<td style={label}>
<i>
Default <code>react-native</code> implementation on Android
</i>
</td>
<td style={label}>
<i>
Implementation from <code>react-native-keyboard-controller</code>{' '}
with better animations
</i>
</td>
</tr>
</tbody>
</table>
);
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions example/src/constants/screenNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export enum ScreenNames {
INTERACTIVE_KEYBOARD = 'INTERACTIVE_KEYBOARD',
INTERACTIVE_KEYBOARD_IOS = 'INTERACTIVE_KEYBOARD_IOS',
NATIVE_STACK = 'NATIVE_STACK',
KEYBOARD_AVOIDING_VIEW = 'KEYBOARD_AVOIDING_VIEW',
}
10 changes: 10 additions & 0 deletions example/src/navigation/ExamplesStack/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import NonUIProps from '../../screens/Examples/NonUIProps';
import InteractiveKeyboard from '../../screens/Examples/InteractiveKeyboard';
import InteractiveKeyboardIOS from '../../screens/Examples/InteractiveKeyboardIOS';
import NativeStack from '../NestedStack';
import KeyboardAvoidingViewExample from '../../screens/Examples/KeyboardAvoidingView';

export type ExamplesStackParamList = {
[ScreenNames.ANIMATED_EXAMPLE]: undefined;
Expand All @@ -25,6 +26,7 @@ export type ExamplesStackParamList = {
[ScreenNames.INTERACTIVE_KEYBOARD]: undefined;
[ScreenNames.INTERACTIVE_KEYBOARD_IOS]: undefined;
[ScreenNames.NATIVE_STACK]: undefined;
[ScreenNames.KEYBOARD_AVOIDING_VIEW]: undefined;
};

const Stack = createStackNavigator<ExamplesStackParamList>();
Expand Down Expand Up @@ -61,6 +63,9 @@ const options = {
[ScreenNames.NATIVE_STACK]: {
title: 'Native stack',
},
[ScreenNames.KEYBOARD_AVOIDING_VIEW]: {
title: 'KeyboardAvoidingView',
},
};

const ExamplesStack = () => (
Expand Down Expand Up @@ -115,6 +120,11 @@ const ExamplesStack = () => (
component={NativeStack}
options={options[ScreenNames.NATIVE_STACK]}
/>
<Stack.Screen
name={ScreenNames.KEYBOARD_AVOIDING_VIEW}
component={KeyboardAvoidingViewExample}
options={options[ScreenNames.KEYBOARD_AVOIDING_VIEW]}
/>
</Stack.Navigator>
);

Expand Down
Loading