Skip to content
Draft
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
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ Yarn:

Autolinking will just do the job.

### Web

Web support works out of the box when using `react-native-web`. No additional configuration needed — the bundler automatically resolves the web implementation.

```js
// Works on iOS, Android, AND Web
import PagerView from 'react-native-pager-view';
```

### < 0.60

#### Mostly automatic
Expand Down Expand Up @@ -147,24 +156,24 @@ For advanced usage please take a look into our [example project](https://github.

| Prop | Description | Platform |
| -------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| `initialPage` | Index of initial page that should be selected | both |
| `scrollEnabled: boolean` | Should pager view scroll, when scroll enabled | both |
| `onPageScroll: (e: PageScrollEvent) => void` | Executed when transitioning between pages (either because the animation for the requested page has changed or when the user is swiping/dragging between pages) | both |
| `onPageScrollStateChanged: (e: PageScrollStateChangedEvent) => void` | Function called when the page scrolling state has changed | both |
| `onPageSelected: (e: PageSelectedEvent) => void` | This callback will be called once the ViewPager finishes navigating to the selected page | both |
| `pageMargin: number` | Blank space to be shown between pages | both |
| `keyboardDismissMode: ('none' / 'on-drag')` | Determines whether the keyboard gets dismissed in response to a drag | both |
| `orientation: Orientation` | Set `horizontal` or `vertical` scrolling orientation (it does **not** work dynamically) | both |
| `initialPage` | Index of initial page that should be selected | all |
| `scrollEnabled: boolean` | Should pager view scroll, when scroll enabled | all |
| `onPageScroll: (e: PageScrollEvent) => void` | Executed when transitioning between pages (either because the animation for the requested page has changed or when the user is swiping/dragging between pages) | all |
| `onPageScrollStateChanged: (e: PageScrollStateChangedEvent) => void` | Function called when the page scrolling state has changed | all |
| `onPageSelected: (e: PageSelectedEvent) => void` | This callback will be called once the ViewPager finishes navigating to the selected page | all |
| `pageMargin: number` | Blank space to be shown between pages | all |
| `keyboardDismissMode: ('none' / 'on-drag')` | Determines whether the keyboard gets dismissed in response to a drag | all |
| `orientation: Orientation` | Set `horizontal` or `vertical` scrolling orientation (it does **not** work dynamically) | all |
| `overScrollMode: OverScrollMode` | Used to override default value of overScroll mode. Can be `auto`, `always` or `never`. Defaults to `auto` | Android |
| `offscreenPageLimit: number` | Set the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed. Defaults to RecyclerView's caching strategy. The given value must either be larger than 0. | Android |
| `overdrag: boolean` | Allows for overscrolling after reaching the end or very beginning or pages. Defaults to `false` | iOS |
| `layoutDirection: ('ltr' / 'rtl' / 'locale')` | Specifies layout direction. Use `ltr` or `rtl` to set explicitly or `locale` to deduce from the default language script of a locale. Defaults to `locale` | both |
| `layoutDirection: ('ltr' / 'rtl' / 'locale')` | Specifies layout direction. Use `ltr` or `rtl` to set explicitly or `locale` to deduce from the default language script of a locale. Defaults to `locale` | all |

| Method | Description | Platform |
| ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both |
| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both |
| `setScrollEnabled(scrollEnabled: boolean)` | A helper function to enable/disable scroll imperatively. The recommended way is using the scrollEnabled prop, however, there might be a case where a imperative solution is more useful (e.g. for not blocking an animation) | both |
| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | all |
| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | all |
| `setScrollEnabled(scrollEnabled: boolean)` | A helper function to enable/disable scroll imperatively. The recommended way is using the scrollEnabled prop, however, there might be a case where a imperative solution is more useful (e.g. for not blocking an animation) | all |

## Contributing

Expand Down
Binary file removed bun.lockb
Binary file not shown.
13 changes: 13 additions & 0 deletions example-web/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"expo": {
"name": "PagerView Web Example",
"slug": "pager-view-web-example",
"version": "1.0.0",
"scheme": "pager-view-web-example",
"web": {
"bundler": "metro",
"output": "single"
},
"plugins": ["expo-router"]
}
}
18 changes: 18 additions & 0 deletions example-web/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Tabs } from 'expo-router';

export default function Layout() {
return (
<Tabs
screenOptions={{
headerStyle: { backgroundColor: '#6200ee' },
headerTintColor: '#fff',
tabBarActiveTintColor: '#6200ee',
}}
>
<Tabs.Screen name="index" options={{ title: 'Basic' }} />
<Tabs.Screen name="imperative" options={{ title: 'Imperative' }} />
<Tabs.Screen name="events" options={{ title: 'Events' }} />
<Tabs.Screen name="hook" options={{ title: 'Hook' }} />
</Tabs>
);
}
96 changes: 96 additions & 0 deletions example-web/app/events.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import PagerView from 'react-native-pager-view';

const PAGES = [
{ key: '1', title: 'Page 1', color: '#ff6b6b' },
{ key: '2', title: 'Page 2', color: '#4ecdc4' },
{ key: '3', title: 'Page 3', color: '#45b7d1' },
];

export default function EventsExample() {
const [scrollState, setScrollState] = useState('idle');
const [selectedPage, setSelectedPage] = useState(0);
const [scrollProgress, setScrollProgress] = useState({
position: 0,
offset: 0,
});

return (
<View style={styles.container}>
<PagerView
style={styles.pager}
initialPage={0}
onPageScroll={(e) =>
setScrollProgress({
position: e.nativeEvent.position,
offset: e.nativeEvent.offset,
})
}
onPageSelected={(e) => setSelectedPage(e.nativeEvent.position)}
onPageScrollStateChanged={(e) =>
setScrollState(e.nativeEvent.pageScrollState)
}
>
{PAGES.map((page) => (
<View
key={page.key}
style={[styles.page, { backgroundColor: page.color }]}
>
<Text style={styles.title}>{page.title}</Text>
</View>
))}
</PagerView>
<View style={styles.events}>
<Text style={styles.label}>onPageScroll</Text>
<Text style={styles.value}>
position: {scrollProgress.position} | offset:{' '}
{scrollProgress.offset.toFixed(4)}
</Text>

<Text style={styles.label}>onPageSelected</Text>
<Text style={styles.value}>position: {selectedPage}</Text>

<Text style={styles.label}>onPageScrollStateChanged</Text>
<Text
style={[
styles.value,
scrollState === 'dragging' && styles.dragging,
scrollState === 'settling' && styles.settling,
]}
>
{scrollState}
</Text>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1 },
pager: { flex: 1 },
page: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: { fontSize: 32, fontWeight: 'bold', color: '#fff' },
events: {
padding: 16,
backgroundColor: '#1a1a2e',
},
label: {
fontSize: 12,
fontWeight: '600',
color: '#888',
marginTop: 8,
fontFamily: 'monospace',
},
value: {
fontSize: 16,
color: '#fff',
fontFamily: 'monospace',
},
dragging: { color: '#ff6b6b' },
settling: { color: '#ffd93d' },
});
80 changes: 80 additions & 0 deletions example-web/app/hook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { usePagerView } from 'react-native-pager-view';

const PAGES = [
{ key: '1', title: 'Page 1', color: '#ff6b6b' },
{ key: '2', title: 'Page 2', color: '#4ecdc4' },
{ key: '3', title: 'Page 3', color: '#45b7d1' },
];

export default function HookExample() {
const {
AnimatedPagerView,
ref,
activePage,
onPageScroll,
onPageSelected,
onPageScrollStateChanged,
scrollState,
progress,
} = usePagerView({ pagesAmount: PAGES.length });

return (
<View style={styles.container}>
<AnimatedPagerView
ref={ref}
style={styles.pager}
initialPage={0}
onPageScroll={onPageScroll}
onPageSelected={onPageSelected}
onPageScrollStateChanged={onPageScrollStateChanged}
>
{PAGES.map((page) => (
<View
key={page.key}
style={[styles.page, { backgroundColor: page.color }]}
>
<Text style={styles.title}>{page.title}</Text>
</View>
))}
</AnimatedPagerView>
<View style={styles.info}>
<Text style={styles.label}>usePagerView state:</Text>
<Text style={styles.value}>
activePage: {activePage} | scrollState: {scrollState}
</Text>
<Text style={styles.value}>
progress: pos={progress.position} off={progress.offset.toFixed(4)}
</Text>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1 },
pager: { flex: 1 },
page: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: { fontSize: 32, fontWeight: 'bold', color: '#fff' },
info: {
padding: 16,
backgroundColor: '#1a1a2e',
},
label: {
fontSize: 12,
fontWeight: '600',
color: '#888',
fontFamily: 'monospace',
},
value: {
fontSize: 14,
color: '#fff',
fontFamily: 'monospace',
marginTop: 4,
},
});
89 changes: 89 additions & 0 deletions example-web/app/imperative.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { useRef, useState } from 'react';
import { StyleSheet, Text, View, Pressable } from 'react-native';
import PagerView from 'react-native-pager-view';

const PAGES = [
{ key: '1', title: 'Page 1', color: '#ff6b6b' },
{ key: '2', title: 'Page 2', color: '#4ecdc4' },
{ key: '3', title: 'Page 3', color: '#45b7d1' },
];

export default function ImperativeExample() {
const pagerRef = useRef<PagerView>(null);
const [scrollEnabled, setScrollEnabled] = useState(true);

return (
<View style={styles.container}>
<PagerView
ref={pagerRef}
style={styles.pager}
initialPage={0}
scrollEnabled={scrollEnabled}
>
{PAGES.map((page) => (
<View
key={page.key}
style={[styles.page, { backgroundColor: page.color }]}
>
<Text style={styles.title}>{page.title}</Text>
</View>
))}
</PagerView>
<View style={styles.controls}>
{PAGES.map((page, index) => (
<Pressable
key={page.key}
style={styles.button}
onPress={() => pagerRef.current?.setPage(index)}
>
<Text style={styles.buttonText}>Go to {index}</Text>
</Pressable>
))}
<Pressable
style={styles.button}
onPress={() => pagerRef.current?.setPageWithoutAnimation(0)}
>
<Text style={styles.buttonText}>Jump to 0 (no animation)</Text>
</Pressable>
<Pressable
style={[styles.button, !scrollEnabled && styles.buttonDisabled]}
onPress={() => {
const next = !scrollEnabled;
setScrollEnabled(next);
pagerRef.current?.setScrollEnabled(next);
}}
>
<Text style={styles.buttonText}>
Scroll: {scrollEnabled ? 'ON' : 'OFF'}
</Text>
</Pressable>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1 },
pager: { flex: 1 },
page: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: { fontSize: 32, fontWeight: 'bold', color: '#fff' },
controls: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
padding: 16,
gap: 8,
},
button: {
backgroundColor: '#6200ee',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 8,
},
buttonDisabled: { backgroundColor: '#999' },
buttonText: { color: '#fff', fontWeight: '600' },
});
Loading
Loading