diff --git a/docs/docs/api/safe-area-listener.mdx b/docs/docs/api/safe-area-listener.mdx new file mode 100644 index 00000000..90196560 --- /dev/null +++ b/docs/docs/api/safe-area-listener.mdx @@ -0,0 +1,35 @@ +--- +sidebar_position: 2 +title: SafeAreaListener +sidebar_label: SafeAreaListener +--- + +Component that lets you listen to safe area insets and frame changes at the position where it is rendered. + +This is an alternative to using the `useSafeAreaInsets` and `useSafeAreaFrame` hooks in combinations with `SafeAreaProvider`. Unlike the hooks, this notifies about changes with the `onChange` prop and doesn't trigger re-renders when the insets or frame change. + +### Example + +```tsx +import { SafeAreaListener } from 'react-native-safe-area-context'; + +function SomeComponent() { + return ( + { + console.log('Safe area changed:', { insets, frame }); + }} + > + {/* Your content here */} + + ); +} +``` + +### Props + +Accepts all [View](https://reactnative.dev/view#props) props. + +#### `onChange` + +Required, a function that receives an object with `insets` and `frame` properties. The `insets` property contains the safe area insets, and the `frame` property contains the frame of the component. diff --git a/example/src/App.tsx b/example/src/App.tsx index aa1e26cb..2dc28436 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { DevSettings, View, Text, StatusBar } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import HooksExample from './HooksExample'; +import ListenerExample from './ListenerExample'; import SafeAreaViewExample from './SafeAreaViewExample'; import SafeAreaViewEdgesExample from './SafeAreaViewEdgesExample'; // import ReactNavigationExample from './ReactNavigationExample'; @@ -13,6 +14,7 @@ type Example = | 'safe-area-view' | 'safe-area-view-edges' | 'hooks' + | 'listener' | 'react-navigation' | 'native-stack'; @@ -52,6 +54,9 @@ export default function App() { DevSettings.addMenuItem('Show Hooks Example', () => { setCurrentExample('hooks'); }); + DevSettings.addMenuItem('Show Listener Example', () => { + setCurrentExample('listener'); + }); DevSettings.addMenuItem('Show React Navigation Example', () => { setCurrentExample('react-navigation'); }); @@ -71,6 +76,9 @@ export default function App() { case 'hooks': content = ; break; + case 'listener': + content = ; + break; // case 'react-navigation': // content = ; // break; diff --git a/example/src/HooksExample.tsx b/example/src/HooksExample.tsx index 3fdd8599..69c6ce85 100644 --- a/example/src/HooksExample.tsx +++ b/example/src/HooksExample.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { View, Text, StatusBar, ScrollView, TextInput } from 'react-native'; +import { View, StatusBar, ScrollView, TextInput } from 'react-native'; import { SafeAreaProvider, @@ -7,38 +7,8 @@ import { initialWindowMetrics, useSafeAreaFrame, } from 'react-native-safe-area-context'; - -const DataView = ({ data }: { data: object | null | undefined }) => ( - - {data == null - ? 'null' - : Object.entries(data) - .map(([key, value]) => `${key}: ${value}`) - .join('\n')} - -); - -const Card = ({ - title, - children, -}: { - title: string; - children: React.ReactNode; -}) => ( - - - {title} - - {children} - -); +import { DataView } from './components/DataView'; +import { Card } from './components/Card'; const BORDER_WIDTH = 8; diff --git a/example/src/ListenerExample.tsx b/example/src/ListenerExample.tsx new file mode 100644 index 00000000..4be9ebd0 --- /dev/null +++ b/example/src/ListenerExample.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { StatusBar, TextInput } from 'react-native'; + +import { + EdgeInsets, + Rect, + SafeAreaListener, + SafeAreaView, +} from 'react-native-safe-area-context'; +import { DataView } from './components/DataView'; +import { Card } from './components/Card'; + +export default function ListenerExample() { + const [data, setData] = React.useState<{ + insets: EdgeInsets; + frame: Rect; + } | null>(null); + + return ( + + + + + + + + + + + + + + + ); +} diff --git a/example/src/components/Card.tsx b/example/src/components/Card.tsx new file mode 100644 index 00000000..ba2da544 --- /dev/null +++ b/example/src/components/Card.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { Text, View } from 'react-native'; + +export function Card({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( + + + {title} + + {children} + + ); +} diff --git a/src/SafeAreaContext.tsx b/src/SafeAreaContext.tsx index 87ad0553..ed192d76 100644 --- a/src/SafeAreaContext.tsx +++ b/src/SafeAreaContext.tsx @@ -105,6 +105,32 @@ export function SafeAreaProvider({ ); } +export interface SafeAreaListenerProps extends ViewProps { + onChange: (data: { insets: EdgeInsets; frame: Rect }) => void; +} + +export function SafeAreaListener({ + onChange, + style, + children, + ...others +}: SafeAreaListenerProps) { + return ( + { + onChange({ + insets: e.nativeEvent.insets, + frame: e.nativeEvent.frame, + }); + }} + > + {children} + + ); +} + const styles = StyleSheet.create({ fill: { flex: 1 }, });