Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/docs/api/safe-area-listener.mdx
Original file line number Diff line number Diff line change
@@ -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 (
<SafeAreaListener
onChange={({ insets, frame }) => {
console.log('Safe area changed:', { insets, frame });
}}
>
{/* Your content here */}
</SafeAreaListener>
);
}
```

### 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.
8 changes: 8 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -13,6 +14,7 @@ type Example =
| 'safe-area-view'
| 'safe-area-view-edges'
| 'hooks'
| 'listener'
| 'react-navigation'
| 'native-stack';

Expand Down Expand Up @@ -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');
});
Expand All @@ -71,6 +76,9 @@ export default function App() {
case 'hooks':
content = <HooksExample />;
break;
case 'listener':
content = <ListenerExample />;
break;
// case 'react-navigation':
// content = <ReactNavigationExample />;
// break;
Expand Down
36 changes: 3 additions & 33 deletions example/src/HooksExample.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,14 @@
import * as React from 'react';
import { View, Text, StatusBar, ScrollView, TextInput } from 'react-native';
import { View, StatusBar, ScrollView, TextInput } from 'react-native';

import {
SafeAreaProvider,
useSafeAreaInsets,
initialWindowMetrics,
useSafeAreaFrame,
} from 'react-native-safe-area-context';

const DataView = ({ data }: { data: object | null | undefined }) => (
<Text style={{ fontSize: 16, lineHeight: 24, color: '#292929' }}>
{data == null
? 'null'
: Object.entries(data)
.map(([key, value]) => `${key}: ${value}`)
.join('\n')}
</Text>
);

const Card = ({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) => (
<View style={{ padding: 16, backgroundColor: 'white', marginBottom: 4 }}>
<Text
style={{
fontSize: 20,
fontWeight: 'bold',
marginBottom: 16,
color: '#292929',
}}
>
{title}
</Text>
{children}
</View>
);
import { DataView } from './components/DataView';
import { Card } from './components/Card';

const BORDER_WIDTH = 8;

Expand Down
35 changes: 35 additions & 0 deletions example/src/ListenerExample.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<SafeAreaListener onChange={setData}>
<StatusBar barStyle="dark-content" backgroundColor="transparent" />
<SafeAreaView style={{ flex: 1, backgroundColor: '#eee' }}>
<Card title="Input">
<TextInput style={{ backgroundColor: '#eee', borderRadius: 3 }} />
</Card>
<Card title="Provider insets">
<DataView data={data?.insets} />
</Card>
<Card title="Provider frame">
<DataView data={data?.frame} />
</Card>
</SafeAreaView>
</SafeAreaListener>
);
}
26 changes: 26 additions & 0 deletions example/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View style={{ padding: 16, backgroundColor: 'white', marginBottom: 4 }}>
<Text
style={{
fontSize: 20,
fontWeight: 'bold',
marginBottom: 16,
color: '#292929',
}}
>
{title}
</Text>
{children}
</View>
);
}
26 changes: 26 additions & 0 deletions src/SafeAreaContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<NativeSafeAreaProvider
{...others}
style={[styles.fill, style]}
onInsetsChange={(e) => {
onChange({
insets: e.nativeEvent.insets,
frame: e.nativeEvent.frame,
});
}}
>
{children}
</NativeSafeAreaProvider>
);
}

const styles = StyleSheet.create({
fill: { flex: 1 },
});
Expand Down