@papyrus-sdk/ui-react-native
v0.2.42
Published
React Native UI components for Papyrus viewers.
Maintainers
Readme
Papyrus UI React Native
React Native UI components for Papyrus viewers.
Install
npm install @papyrus-sdk/ui-react-native @papyrus-sdk/engine-native @papyrus-sdk/core @papyrus-sdk/types react-native-gesture-handler react-native-reanimated@^3.19.5@papyrus-sdk/core, @papyrus-sdk/types, react-native-gesture-handler, and react-native-reanimated (3.19.5+) are required peer dependencies.
Configure Reanimated 3 in the host app, with react-native-reanimated/plugin last in the Babel plugin list. The native iOS PDF annotation mode animates the Topbar and dock; it respects the system Reduce Motion setting. While drawing, Concluir/Done closes the ink session through the existing store/native flush flow. The first Back press finishes drawing without leaving the reader; the next press navigates back.
Wrap the app root with GestureHandlerRootView before rendering Papyrus components:
import { GestureHandlerRootView } from 'react-native-gesture-handler';
<GestureHandlerRootView style={{ flex: 1 }}>
<App />
</GestureHandlerRootView>For EPUB/TXT/CBZ previews on mobile:
npm install react-native-webviewMobileDocumentEngine recognizes .cbz as comic and renders it in the
same WebView runtime used by the mobile reader: continuous scrolling, lazy
page extraction, bounded object-URL cache, and on-demand page previews in the
navigation sheet. CBR is intentionally kept out of the base mobile runtime;
RAR support needs the optional libarchive worker/WASM payload.
CBR mobile support is experimental and requires
@papyrus-sdk/engine-cbr-mobile. Its Android WebView flow is not a substitute
for device validation on iOS or Android, especially for picker-provided
content:// and file:// sources.
Usage
import { Viewer, CoverPreview } from '@papyrus-sdk/ui-react-native';
import { MobileDocumentEngine } from '@papyrus-sdk/engine-native';
const engine = new MobileDocumentEngine();
await engine.load({ type: 'pdf', source: { uri: 'https://example.com/book.pdf' } });
<Viewer engine={engine} />
<CoverPreview
source={{ uri: 'https://example.com/book.epub' }}
type="epub"
style={{ width: 120, height: 180 }}
/>Shared reader chrome
ReaderHeaderView, ReaderBottomControlsView, ReaderSettingsView, and
ReaderContentsView are controlled presentation components. They accept the
display state and callbacks as props and do not read a Papyrus store or create a
document engine. Existing Topbar, BottomBar, SettingsSheet, and
RightSheet remain Papyrus-backed compatibility components and reuse the same
presentation layer for their mobile EPUB chrome.
Use these views when an application hosts a different reading engine but wants to keep Papyrus's reader controls. Keep engine state, capability checks, navigation, and modal sequencing in the host adapter:
import {
ReaderHeaderView,
ReaderBottomControlsView,
ReaderSettingsView,
ReaderContentsView,
} from '@papyrus-sdk/ui-react-native';
<ReaderHeaderView
title={book.title}
isDark={theme === 'dark'}
leadingAccessory={<BackButton onPress={goBack} />}
trailingAccessory={<SettingsButton onPress={openSettings} />}
/>The bottom controls accept capability-filtered action lists and optional
navigation callbacks. Settings expose only supplied theme/font choices and
actions. Contents accept flattened entries with a depth value for hierarchy;
an empty list renders the supplied empty message and leaves the sheet closable.
Readium support should not expose annotation or freehand controls unless that
engine implements those capabilities.
EPUB renderers can share the same localized text-selection actions through
getReaderSelectionActionOptions(locale). The stable IDs are copy,
highlight, underline, note, and consult; normalizeReaderSelectionActionId
also accepts Papyrus's older comment and define IDs. A host can pass these
options to a native selection menu and route the selected text/locator to its
annotation store without translating one engine's locator into another format.
Topbar Customization
Topbar supports replacing the default "Papyrus" text and logo.
<Topbar
engine={engine}
title="My Reader"
logo={<MyLogoIcon />}
onLogoPress={() => navigation.goBack()}
onOpenSettings={() => setSettingsOpen(true)}
/>title: replaces the default brand text.logo: custom logo node (can be icon, image, or even aPressable).onLogoPress: optional callback to make the logo area act like a button.
Icon Guidelines
- UI icons for this package live in
packages/ui-react-native/icons.tsx. - Prefer inline SVG paths implemented with
react-native-svginstead of adding a new icon runtime dependency. - When a product icon does not already exist in the file, prefer using the outline SVG from Tabler Icons as the visual baseline and adapt it to the local
IconPropspattern. - Keep icons stroke-based when possible, preserve
color/strokeWidthas props, and simplify paths only when needed for better balance in mobile chrome. - If an icon is intentionally derived from Tabler, keep that shape direction consistent for nearby controls so the toolbar/bottom-bar language stays coherent.
Mobile Tuning Flags
Layout and performance options are available on Viewer and RightSheet:
<Viewer
engine={engine}
maxPageWidth={760}
fitPageToViewportHeight
virtualWindowSize={8}
maxToRenderPerBatch={6}
removeClippedSubviews
/>
<RightSheet engine={engine} thumbsInitialCount={4} />virtualWindowSize(Viewer): FlatList window size. Default:8.maxPageWidth(Viewer): optional fit-zoom width limit in React Native points. A narrower page is centered in the viewport; pinch zoom may enlarge it. Omitted by default.fitPageToViewportHeight(Viewer): opt-in page fitting to the measured viewer height while preserving each page's aspect ratio; pinch zoom may enlarge it. Defaults tofalse. This option is only supported by the React Native compatibility renderer; it is not applied by the WebView or dedicated Android PDF renderer.maxToRenderPerBatch(Viewer): max items per render batch. Default:6.removeClippedSubviews(Viewer): detach offscreen rows. Default:falsefor AndroidviewerMode="compat"andtrueotherwise. An explicit value is always respected.thumbsInitialCount(RightSheet): initial thumbnails rendered when opening pages tab. Default:4.
Recommended starting points:
- Mid-range Android + large docs (500+ pages):
virtualWindowSize={6},maxToRenderPerBatch={4},thumbsInitialCount={3}. - High-end devices: keep defaults (
8,6,4) and tune only if you observe jank or memory pressure.
Performance Tips (Mobile)
- Keep
Viewermounted before callingengine.load(...)for EPUB/TXT, so the WebView runtime is already ready. - Prefer URI loads (
{ uri }) for local files instead of pre-converting content to base64. - For long documents, reduce
virtualWindowSizeandmaxToRenderPerBatchfirst; then tune thumbnail pre-render count. - On Android mid-tier devices (around 4 GB RAM), expect temporary memory spikes while parsing EPUB archives and generating first render.
Mobile Performance Baseline (Audit Step)
Enable runtime diagnostics before rendering the viewer:
(globalThis as any).__PAPYRUS_MOBILE_PERF__ = {
enabled: true,
sampleMemory: true,
};With diagnostics enabled, the native viewer emits structured logs such as:
[Papyrus Perf][Viewer] document.readywith initial load time.[Papyrus Perf][Viewer] scroll.*with frame-gap and dropped-frame estimates.[Papyrus Perf][RightSheet] memory.thumbnails.*for memory snapshots when opening thumbnails.[Papyrus Perf][CoreStore] setDocumentState.burstwhen state updates happen in bursts.
Recommended baseline run:
- Load a large PDF (1000+ pages).
- Record
document.readyfor initial load. - Scroll quickly for 10-15 seconds and capture
scroll.*logs. - Open thumbnails and capture
memory.thumbnails.*. - Jump between distant pages and check for
setDocumentState.burst.
Large Files (EPUB/PDF) Troubleshooting
If large files fail to load or crash on mobile:
- Avoid RN bridge base64 conversion for large local files (OOM risk). Use
{ uri: 'file:///...' }. - EPUB on mobile WebView now fetches local URI content inside the runtime and opens from
ArrayBuffer. - If you see timeout errors:
- WebView engine request timeout for
load:180000ms - Runtime EPUB open/ready timeout:
180000ms - Runtime EPUB display timeout:
30000ms
- WebView engine request timeout for
- For very large EPUBs, first-open can take longer on mid-tier Android. This is expected due to zip parsing and spine bootstrapping.
Native ink motion packaging check
Run pnpm --filter @papyrus-sdk/ui-react-native check:ink-worklets before publishing. This builds and verifies all three ink chrome callbacks through the Reanimated Babel plugin in both CJS and ESM artifacts. Keep explicit "worklet" directives: bundler aliases such as useAnimatedStyle2 are not automatically recognized by the plugin.
