React Native SDK
Embed Retor in a React Native / Expo app — composable bottom sheets built on @gorhom/bottom-sheet.
Installation
Install peer dependencies:
# Expo
npx expo install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg expo-blur
npm install @gorhom/bottom-sheet lucide-react-native @retor/react-native
# bare React Native
npm install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg @gorhom/bottom-sheet lucide-react-native expo-blur @retor/react-native
cd ios && pod installexpo-blur is optional — used for the default blurred sheet background. Pass a custom backgroundComponent to any sheet if you don't want it.
Wrap your app root in GestureHandlerRootView (per @gorhom/bottom-sheet requirements):
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<YourApp />
</GestureHandlerRootView>
);
}Quick Start — Default UI
import { View } from "react-native";
import { Viewer, Hud, ProjectSheet, LineDetailSheet, AddNoteSheet } from "@retor/react-native";
export default function Scene() {
return (
<View style={{ flex: 1 }}>
<Viewer projectId="abc123">
<Hud>
<ProjectSheet />
<LineDetailSheet />
<AddNoteSheet />
</Hud>
</Viewer>
</View>
);
}Customising the visuals
Each sheet accepts snapPoints, renderHeader, and a children slot. Use <LinesCarousel> and <LineTagList> with render-prop children to swap in your own native components.
import { Pressable, Text } from "react-native";
import { ProjectSheet, LinesCarousel, LineDetailSheet, LineTagList, useViewer, type RetorLine } from "@retor/react-native";
function MyLineCard({ line }: { line: RetorLine }) {
const { openLine } = useViewer();
return (
<Pressable
onPress={() => openLine(line._id)}
style={{ width: 200, padding: 16, backgroundColor: "#222", borderRadius: 16 }}
>
<Text style={{ color: "white", fontWeight: "600" }}>{line.name}</Text>
</Pressable>
);
}
<ProjectSheet snapPoints={["20%", "50%"]}>
<LinesCarousel>
{(line) => <MyLineCard line={line} />}
</LinesCarousel>
</ProjectSheet>
<LineDetailSheet>
<LineTagList>
{(tag, isActive) => (
<Pressable style={{ padding: 12 }}>
<Text style={{ color: isActive ? "white" : "gray" }}>{tag.name}</Text>
</Pressable>
)}
</LineTagList>
</LineDetailSheet>Notes
Open <AddNoteSheet> by calling useAddNote().open(tagId?) — typically from a "+" button on the active tag in your LineTagList. The form collects text + private/public, then fires onNoteSubmit on the parent <Viewer>. Persistence is your responsibility — re-pass updated notes back via <Notes>.
import { useState } from "react";
import { View, Pressable, Text } from "react-native";
import {
Viewer, Hud, ProjectSheet, LineDetailSheet, LineTagList, AddNoteSheet, Notes,
useAddNote, type RetorTag,
} from "@retor/react-native";
function AddButton() {
const { open } = useAddNote();
return <Pressable onPress={() => open()}><Text>+</Text></Pressable>;
}
export default function Scene() {
const [notes, setNotes] = useState<RetorTag[]>([]);
return (
<View style={{ flex: 1 }}>
<Viewer
projectId="abc123"
onNoteSubmit={({ text, tagId, lineId, position }) => {
if (!position) return;
setNotes((prev) => [
...prev,
{ _id: `note-${Date.now()}`, name: text, position, objectId: lineId ?? undefined },
]);
}}
>
<Notes notes={notes} />
<Hud>
<ProjectSheet />
<LineDetailSheet>
<LineTagList>
{(tag, isActive) => (
<View style={{ flexDirection: "row", padding: 12 }}>
<Text style={{ flex: 1, color: isActive ? "white" : "gray" }}>{tag.name}</Text>
{isActive && <AddButton />}
</View>
)}
</LineTagList>
</LineDetailSheet>
<AddNoteSheet />
</Hud>
</Viewer>
</View>
);
}Hooks
Identical to the React SDK — same API, same return types.
CoverPhoto
import { CoverPhoto } from "@retor/react-native";
<CoverPhoto projectId="abc123" style={{ width: 200, height: 120 }} />