Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6757a8b
[docs][expo-observe] Update observe docs with new ObserveRoot and use…
Ubax May 20, 2026
db00101
bump expo go version [skip ci]
alanjhughes May 20, 2026
61448c8
[expo-type-information] add `preprocess-file` CLI command (#45849)
HubertBer May 20, 2026
0594092
fix(expo/fetch): Thread `Request#body` (from `whatwg-fetch`) through …
kitten May 20, 2026
d9ad415
[docs] Update Convex guide for EAS integration CLI (#45970)
FiberJW May 20, 2026
8027318
feat(require-utils): Extend `resolveFrom` support for more cases (#45…
kitten May 20, 2026
e94ee96
feat(config,config-plugins): Align config plugin resolution/loading w…
kitten May 20, 2026
e232bf2
chore(config-plugins): Drop `resolve-from` for `@expo/require-utils` …
kitten May 20, 2026
9f5a3f8
[dev-launcher][dev-menu] Apply iOS dev menu setting toggles immediate…
gabrieldonadel May 20, 2026
40f210e
[ui] Add `textStyle` option to ios `font` modifier for Dynamic Type (…
ramonclaudio May 20, 2026
f57ef10
[ui] Universal BottomSheet fixes (#46031)
intergalacticspacehighway May 20, 2026
021cef8
[docs] Update bun init command in monorepos guide (#46028)
amandeepmittal May 20, 2026
efd6359
[docs] Update EAS CLI version to 19 (#46001)
amandeepmittal May 20, 2026
50a308c
[docs] Update `expo-iap` link and description (#45985)
amandeepmittal May 20, 2026
35d1413
[guide] Document missing TypeDoc tags in style guide (#46036)
amandeepmittal May 20, 2026
2fac320
[@expo/cli][expo-doctor] switch Directory requests to GET (#45673)
Simek May 20, 2026
6dcb3d4
[expo][Android] Add missing `OptimizedRecord` annotation (#46040)
lukmccall May 20, 2026
d8767bf
[expo-observe] use routePattern instead of pathname as routeName for …
Ubax May 20, 2026
1847eb2
[ui] add community pager view replacement (#45828)
vonovak May 20, 2026
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
2 changes: 1 addition & 1 deletion apps/expo-go/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 229
versionName '56.0.0'
versionName '56.0.1'

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Deprecated. Used by net.openid:appauth
Expand Down
4 changes: 2 additions & 2 deletions apps/expo-go/ios/Exponent/Supporting/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>56.0.0</string>
<string>56.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
Expand Down Expand Up @@ -61,7 +61,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>56.0.0</string>
<string>56.0.1</string>
<key>FacebookAdvertiserIDCollectionEnabled</key>
<false/>
<key>FacebookAppID</key>
Expand Down
290 changes: 290 additions & 0 deletions apps/native-component-list/src/screens/UI/CommunityPagerViewScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import PagerView from '@expo/ui/community/pager-view';
import * as React from 'react';
import { Button, ScrollView, StyleSheet, Text, View, type DimensionValue } from 'react-native';
import Reanimated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated';

// Adapted from https://github.com/callstack/react-native-pager-view/tree/master/example

const PAGE_COLORS = ['#6200EE', '#03DAC5', '#FF5722', '#E91E63', '#3F51B5'];

function ColorPage({ index, label }: { index: number; label?: string }) {
return (
<View style={[styles.page, { backgroundColor: PAGE_COLORS[index % PAGE_COLORS.length] }]}>
<Text style={styles.pageText}>{label ?? `Page ${index + 1}`}</Text>
</View>
);
}

function Section({
title,
hint,
children,
}: {
title: string;
hint?: string;
children: React.ReactNode;
}) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text>
{hint ? <Text style={styles.sectionHint}>{hint}</Text> : null}
{children}
</View>
);
}

function BasicSection() {
const pagerRef = React.useRef<React.ComponentRef<typeof PagerView>>(null);
const [page, setPage] = React.useState(0);

return (
<Section
title="Basic — imperative navigation"
hint="setPage animates; setPageWithoutAnimation jumps.">
<PagerView
ref={pagerRef}
style={styles.pager}
initialPage={0}
onPageSelected={(e) => setPage(e.nativeEvent.position)}>
<ColorPage key="1" index={0} />
<ColorPage key="2" index={1} />
<ColorPage key="3" index={2} />
</PagerView>
<View style={styles.row}>
<Button title="Prev" onPress={() => pagerRef.current?.setPage(Math.max(0, page - 1))} />
<Text style={styles.label}>Page {page + 1} / 3</Text>
<Button title="Next" onPress={() => pagerRef.current?.setPage(Math.min(2, page + 1))} />
</View>
<Button
title="Jump to last (no anim)"
onPress={() => pagerRef.current?.setPageWithoutAnimation(2)}
/>
</Section>
);
}

function ScrollProgressSection() {
const progress = useSharedValue(0);
const [state, setState] = React.useState<'idle' | 'dragging' | 'settling'>('idle');
const [page, setPage] = React.useState(0);
const pageCount = 4;

const onPageScroll = (e: { nativeEvent: { position: number; offset: number } }) => {
'worklet';
progress.value = e.nativeEvent.position + e.nativeEvent.offset;
};

const progressBarFill = useAnimatedStyle(() => ({
width: `${Math.max(0, Math.min(1, progress.value / (pageCount - 1))) * 100}%`,
}));

const blockJSFor = (ms: number) => {
const end = Date.now() + ms;
// eslint-disable-next-line no-empty
while (Date.now() < end) {}
};

return (
<Section
title="Continuous scroll progress (worklet)"
hint="onPageScroll runs as a worklet on the UI thread; the bar tracks the swipe even while the JS thread is blocked. iOS 18+ only.">
<View style={styles.progressBarTrack}>
<Reanimated.View style={[styles.progressBarFill, progressBarFill]} />
</View>
<PagerView
style={styles.pager}
onPageScroll={onPageScroll}
onPageScrollStateChanged={(e) => setState(e.nativeEvent.pageScrollState)}
onPageSelected={(e) => setPage(e.nativeEvent.position)}>
<ColorPage key="1" index={0} label="Swipe →" />
<ColorPage key="2" index={1} />
<ColorPage key="3" index={2} />
<ColorPage key="4" index={3} label="Last" />
</PagerView>
<View style={styles.row}>
<View style={[styles.stateBadge, stateBadgeStyle(state)]}>
<Text style={styles.stateBadgeText}>{state}</Text>
</View>
<Text style={styles.label}>
Page {page + 1} / {pageCount}
</Text>
</View>
<Button title="Block JS for 4 seconds" onPress={() => blockJSFor(4000)} />
</Section>
);
}

function stateBadgeStyle(state: 'idle' | 'dragging' | 'settling') {
switch (state) {
case 'dragging':
return { backgroundColor: '#03DAC5' };
case 'settling':
return { backgroundColor: '#FF9800' };
case 'idle':
default:
return { backgroundColor: '#9E9E9E' };
}
}

function ToggleScrollSection() {
const pagerRef = React.useRef<React.ComponentRef<typeof PagerView>>(null);
const [enabled, setEnabled] = React.useState(true);
const [page, setPage] = React.useState(0);

return (
<Section
title="Toggle scroll"
hint="setScrollEnabled disables user swipes; programmatic navigation still works.">
<PagerView
ref={pagerRef}
style={styles.pager}
scrollEnabled={enabled}
onPageSelected={(e) => setPage(e.nativeEvent.position)}>
<ColorPage key="1" index={0} label={enabled ? 'Swipe enabled' : 'Swipe disabled'} />
<ColorPage key="2" index={1} label="Page 2" />
<ColorPage key="3" index={2} label="Page 3" />
</PagerView>
<View style={styles.row}>
<Button
title={enabled ? 'Disable swipe' : 'Enable swipe'}
onPress={() => {
const next = !enabled;
setEnabled(next);
pagerRef.current?.setScrollEnabled(next);
}}
/>
<Text style={styles.label}>Page {page + 1} / 3</Text>
<Button title="Go to page 3" onPress={() => pagerRef.current?.setPage(2)} />
</View>
</Section>
);
}

function DynamicPagesSection() {
const [pages, setPages] = React.useState([0, 1, 2]);
const pagerRef = React.useRef<React.ComponentRef<typeof PagerView>>(null);
const [current, setCurrent] = React.useState(0);

return (
<Section title="Dynamic pages" hint="Add or remove pages while the pager stays mounted.">
<PagerView
ref={pagerRef}
style={styles.pager}
onPageSelected={(e) => setCurrent(e.nativeEvent.position)}>
{pages.map((id, i) => (
<ColorPage key={String(id)} index={id} label={`Page ${i + 1} / ${pages.length}`} />
))}
</PagerView>
<View style={styles.row}>
<Button
title="Add"
onPress={() =>
setPages((prev) => [...prev, prev.length === 0 ? 0 : prev[prev.length - 1] + 1])
}
/>
<Text style={styles.label}>
Showing {current + 1} / {pages.length}
</Text>
<Button
title="Remove last"
onPress={() => setPages((prev) => (prev.length > 1 ? prev.slice(0, -1) : prev))}
/>
</View>
</Section>
);
}

function InitialPageSection() {
const initialPage = 2;
const pageCount = 4;
const [page, setPage] = React.useState(initialPage);
const [progress, setProgress] = React.useState(initialPage);
const fillWidth: DimensionValue = `${Math.max(0, Math.min(1, progress / (pageCount - 1))) * 100}%`;

return (
<Section
title="Initial page"
hint="Same indicator as above, but driven by React state from a plain (non-worklet) onPageScroll. Block JS and the bar freezes.">
<View style={styles.progressBarTrack}>
<View style={[styles.progressBarFill, { width: fillWidth }]} />
</View>
<PagerView
style={styles.pager}
initialPage={initialPage}
onPageScroll={(e) => setProgress(e.nativeEvent.position + e.nativeEvent.offset)}
onPageSelected={(e) => setPage(e.nativeEvent.position)}>
<ColorPage key="1" index={0} />
<ColorPage key="2" index={1} />
<ColorPage key="3" index={2} label="Page 3 (initial)" />
<ColorPage key="4" index={3} />
</PagerView>
<Text style={[styles.label, styles.labelAlign]}>
Page {page + 1} / {pageCount}
</Text>
</Section>
);
}

function RTLSection() {
const [rtl, setRtl] = React.useState(false);
const [page, setPage] = React.useState(0);

return (
<Section title="Layout direction" hint="Toggle between LTR and RTL paging (Android only).">
<PagerView
key={rtl ? 'rtl' : 'ltr'}
style={styles.pager}
layoutDirection={rtl ? 'rtl' : 'ltr'}
onPageSelected={(e) => setPage(e.nativeEvent.position)}>
<ColorPage key="1" index={0} label="First" />
<ColorPage key="2" index={1} label="Second" />
<ColorPage key="3" index={2} label="Third" />
</PagerView>
<View style={styles.row}>
<Button title={rtl ? 'Switch to LTR' : 'Switch to RTL'} onPress={() => setRtl((v) => !v)} />
<Text style={styles.label}>Page {page + 1} / 3</Text>
</View>
</Section>
);
}

export default function CommunityPagerViewScreen() {
return (
<ScrollView contentContainerStyle={styles.scroll}>
<ScrollProgressSection />
<InitialPageSection />
<BasicSection />
<ToggleScrollSection />
<DynamicPagesSection />
<RTLSection />
</ScrollView>
);
}

CommunityPagerViewScreen.navigationOptions = {
title: 'Community PagerView replacement',
};

const styles = StyleSheet.create({
scroll: { padding: 16, gap: 24 },
section: { gap: 12 },
sectionTitle: { fontSize: 16, fontWeight: '600' },
sectionHint: { fontSize: 12, color: '#666' },
pager: { height: 240, borderRadius: 12, overflow: 'hidden' },
page: { flex: 1, alignItems: 'center', justifyContent: 'center' },
pageText: { color: '#FFFFFF', fontSize: 22, fontWeight: 'bold' },
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 12,
},
label: { fontSize: 15, fontWeight: '600' },
labelAlign: { alignSelf: 'flex-start' },
progressBarTrack: { height: 6, borderRadius: 3, backgroundColor: '#EEE', overflow: 'hidden' },
progressBarFill: { height: '100%', backgroundColor: '#6200EE' },
stateBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 },
stateBadgeText: { color: '#FFF', fontSize: 12, fontWeight: '600' },
refDemoHost: { height: 240, borderRadius: 12, overflow: 'hidden' },
refTile: { flex: 1, alignItems: 'center', justifyContent: 'center' },
});
12 changes: 12 additions & 0 deletions apps/native-component-list/src/screens/UI/ModifiersScreen.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ export default function ModifiersScreen() {
</VStack>
</HStack>

{/* Dynamic Type */}
<VStack alignment="leading" spacing={8}>
<Text modifiers={[font({ size: 12 })]}>
Dynamic Type (try Settings &gt; Accessibility &gt; Larger Text)
</Text>
<Text modifiers={[font({ textStyle: 'largeTitle', weight: 'bold' })]}>
largeTitle scales
</Text>
<Text modifiers={[font({ textStyle: 'body' })]}>body scales</Text>
<Text modifiers={[font({ textStyle: 'caption' })]}>caption scales</Text>
</VStack>

<HStack spacing={20}>
<Text modifiers={[font({ size: 14 }), textCase('lowercase')]}>lowercase</Text>
<Text modifiers={[font({ size: 14 }), textCase('uppercase')]}>uppercase</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ export const UIScreens = [
return optionalRequire(() => require('./CommunityMenuScreen'));
},
},
{
name: 'Community PagerView replacement',
route: 'ui/community-pager-view',
options: {},
getComponent() {
return optionalRequire(() => require('./CommunityPagerViewScreen'));
},
},
{
name: 'Switch component',
route: 'ui/switch',
Expand Down
8 changes: 8 additions & 0 deletions apps/native-component-list/src/screens/UI/UIScreen.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ export const UIScreens = [
return optionalRequire(() => require('./CommunityMenuScreen'));
},
},
{
name: 'Community PagerView replacement',
route: 'ui/community-pager-view',
options: {},
getComponent() {
return optionalRequire(() => require('./CommunityPagerViewScreen'));
},
},
{
name: 'TabView component',
route: 'ui/tabview',
Expand Down
6 changes: 3 additions & 3 deletions apps/notification-tester/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
"react-native-gesture-handler": "~2.30.0",
"react": "19.2.3",
"react-native": "0.85.3",
"react-native-reanimated": "4.3.0",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.25.1"
"react-native-screens": "4.25.1",
"react-native-worklets": "0.8.3"
},
"devDependencies": {
"@types/jest": "^29.5.12",
Expand All @@ -63,11 +65,9 @@
"react-native-keyboard-controller",
"react-native-maps",
"react-native-pager-view",
"react-native-reanimated",
"react-native-svg",
"react-native-view-shot",
"react-native-webview",
"react-native-worklets",
"@expo/app-integrity",
"expo-age-range",
"expo-apple-authentication",
Expand Down
4 changes: 2 additions & 2 deletions apps/notification-tester/src/app/expo-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import HorizontalPagerScreen from 'native-component-list/src/screens/UI/HorizontalPagerScreen';
import CommunityPagerViewScreen from 'native-component-list/src/screens/UI/CommunityPagerViewScreen';

export default HorizontalPagerScreen;
export default CommunityPagerViewScreen;
Loading
Loading