Interactive
SwipeableRow
A list row that exposes swipe-to-reveal action buttons on one or both sides. The leading (left) and trailing (right) action slots accept any React content — commonly icon+label buttons.
| Prop | Type | Default | Description |
|---|---|---|---|
leadingActions? | React.ReactNode | — | Content revealed when the row is swiped right |
trailingActions? | React.ReactNode | — | Content revealed when the row is swiped left |
friction? | number | 2 | Resistance multiplier applied to the swipe gesture |
overshootLeft? | boolean | false | Allow the row to travel past the leading action width |
overshootRight? | boolean | false | Allow the row to travel past the trailing action width |
onSwipeLeft? | () => void | — | Called when the row is fully swiped to the left |
onSwipeRight? | () => void | — | Called when the row is fully swiped to the right |
children | React.ReactNode | — | The row content displayed in the default (resting) position |
accessibilityLabel? | string | — | Accessibility label for the row |
testID? | string | — | Test identifier |
import { SwipeableRow, Box, Text, Pressable, Icon } from '@objectifthunes/limestone-sdk';
function MessageRow({ message, onDelete, onArchive }: {
message: { id: string; subject: string };
onDelete: () => void;
onArchive: () => void;
}) {
return (
<SwipeableRow
trailingActions={
<>
<Pressable onPress={onArchive} bg="primary" px="lg" center>
<Icon name="archive" size="md" color="primaryForeground" />
</Pressable>
<Pressable onPress={onDelete} bg="destructive" px="lg" center>
<Icon name="trash" size="md" color="destructiveForeground" />
</Pressable>
</>
}
>
<Box py="md" px="lg" bg="card">
<Text variant="body">{message.subject}</Text>
</Box>
</SwipeableRow>
);
}
DragToReorder
A list container that lets users long-press and drag items to rearrange them. Backed by react-native-gesture-handler and react-native-reanimated for fluid 60 fps reordering.
| Prop | Type | Default | Description |
|---|---|---|---|
data | T[] | — | Controlled array of items |
onReorder | (reordered: T[]) => void | — | Called with the new order after a drag completes |
renderItem | (item: T, index: number, isDragging: boolean) => React.ReactNode | — | Row renderer; isDragging is true while the item is held |
keyExtractor | (item: T) => string | — | Unique key per item |
itemHeight | number | — | Fixed row height required for layout calculations |
dragHandlePosition? | 'left' | 'right' | 'right' | Side where the drag handle icon appears |
hapticFeedback? | boolean | true | Trigger a haptic impact when a drag begins |
accessibilityLabel? | string | — | Accessibility label for the list |
testID? | string | — | Test identifier |
import { DragToReorder } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';
interface Task { id: string; title: string; }
function TaskList({ initialTasks }: { initialTasks: Task[] }) {
const [tasks, setTasks] = useState(initialTasks);
return (
<DragToReorder
data={tasks}
onReorder={setTasks}
keyExtractor={(t) => t.id}
itemHeight={56}
renderItem={(task, _, isDragging) => (
<Box
py="md" px="lg" bg={isDragging ? 'muted' : 'card'}
style={{ opacity: isDragging ? 0.85 : 1 }}
>
<Text variant="body">{task.title}</Text>
</Box>
)}
/>
);
}
PullToRefresh
A scroll container with a custom animated pull-to-refresh indicator. Wraps React Native ScrollView and exposes the same refresh lifecycle as List, but with a themed spinner instead of the native platform indicator.
| Prop | Type | Default | Description |
|---|---|---|---|
refreshing | boolean | — | Whether a refresh is currently in progress |
onRefresh | () => void | — | Called when the user has pulled past the threshold |
threshold? | number | 80 | Pull distance in pixels before onRefresh fires |
indicatorColor? | ColorProp | 'primary' | Color of the animated refresh indicator |
indicatorSize? | 'sm' | 'md' | 'lg' | 'md' | Indicator size |
children | React.ReactNode | — | Scrollable content |
contentPadding? | SpacingProp | — | Padding applied to the scroll content container |
accessibilityLabel? | string | — | Accessibility label |
testID? | string | — | Test identifier |
import { PullToRefresh, Stack, Text } from '@objectifthunes/limestone-sdk';
function FeedScreen() {
const [refreshing, setRefreshing] = useState(false);
const handleRefresh = async () => {
setRefreshing(true);
await fetchFeed();
setRefreshing(false);
};
return (
<PullToRefresh
refreshing={refreshing}
onRefresh={handleRefresh}
indicatorColor="primary"
>
<Stack gap="md" p="lg">
{posts.map((post) => <PostCard key={post.id} post={post} />)}
</Stack>
</PullToRefresh>
);
}
PinchToZoom
A container that adds pinch-to-zoom and pan gestures to any content. Uses react-native-gesture-handler for gesture recognition and react-native-reanimated for smooth transforms.
| Prop | Type | Default | Description |
|---|---|---|---|
minScale? | number | 1 | Minimum zoom scale |
maxScale? | number | 5 | Maximum zoom scale |
doubleTapToZoom? | boolean | true | Double-tap cycles between 1× and 2× |
onScaleChange? | (scale: number) => void | — | Called when the zoom level changes |
onPanChange? | (x: number, y: number) => void | — | Called when the pan offset changes |
resetOnRelease? | boolean | false | Animate back to 1× and center on gesture end |
children | React.ReactNode | — | Content to zoom |
style? | ViewStyle | — | Outer container style |
accessibilityLabel? | string | — | Accessibility label |
import { PinchToZoom, Image } from '@objectifthunes/limestone-sdk';
function ZoomablePhoto({ uri }: { uri: string }) {
return (
<PinchToZoom maxScale={6} doubleTapToZoom>
<Image src={uri} alt="Photo" w={400} h={300} resizeMode="contain" />
</PinchToZoom>
);
}
ParallaxHeader
A scroll container whose header image scrolls at a slower rate than the content, creating a parallax depth effect. The header collapses to a sticky navigation bar as the user scrolls down.
| Prop | Type | Default | Description |
|---|---|---|---|
headerImage | string | number | — | URI or local require for the header image |
headerHeight? | number | 280 | Expanded header height in pixels |
collapsedHeight? | number | 60 | Collapsed (sticky) header height |
parallaxFactor? | number | 0.4 | Speed ratio of header vs content scroll (0–1) |
stickyContent? | React.ReactNode | — | Content rendered in the collapsed header bar |
children | React.ReactNode | — | Scrollable page body |
headerOverlay? | React.ReactNode | — | Overlay content rendered on top of the header image |
fadeHeader? | boolean | true | Fade the header image out as it collapses |
accessibilityLabel? | string | — | Accessibility label |
testID? | string | — | Test identifier |
import { ParallaxHeader, Stack, Text, Avatar } from '@objectifthunes/limestone-sdk';
function ProfileDetailScreen({ user }: { user: User }) {
return (
<ParallaxHeader
headerImage={{ uri: user.coverPhotoUrl }}
headerHeight={320}
stickyContent={
<Text variant="heading" color="foreground">{user.name}</Text>
}
headerOverlay={
<Avatar src={user.avatarUrl} name={user.name} size="xl" />
}
>
<Stack gap="md" p="lg">
<Text variant="body">{user.bio}</Text>
</Stack>
</ParallaxHeader>
);
}
PageView
A full-screen horizontal pager with optional dot indicators. Each page occupies the full container width and snaps on swipe. Backed by React Native’s ScrollView with pagingEnabled.
| Prop | Type | Default | Description |
|---|---|---|---|
pages | React.ReactNode[] | — | Array of page content elements |
initialPage? | number | 0 | Zero-based index of the page shown on first render |
onPageChange? | (index: number) => void | — | Called when the visible page changes |
showDots? | boolean | true | Render dot indicators below the pager |
dotColor? | ColorProp | 'primary' | Active dot color |
dotInactiveColor? | ColorProp | 'muted' | Inactive dot color |
dotSize? | number | 8 | Dot diameter in pixels |
scrollEnabled? | boolean | true | Allow swipe navigation |
loop? | boolean | false | Wrap around from last page to first |
accessibilityLabel? | string | — | Accessibility label |
testID? | string | — | Test identifier |
import { PageView, Stack, Text, Image } from '@objectifthunes/limestone-sdk';
const onboardingPages = [
<Stack center p="xl" key="p1">
<Image src={require('./assets/welcome.png')} alt="Welcome" w={240} h={240} />
<Text variant="display" align="center">Welcome to the app</Text>
</Stack>,
<Stack center p="xl" key="p2">
<Image src={require('./assets/features.png')} alt="Features" w={240} h={240} />
<Text variant="display" align="center">Powerful features</Text>
</Stack>,
<Stack center p="xl" key="p3">
<Image src={require('./assets/start.png')} alt="Get started" w={240} h={240} />
<Text variant="display" align="center">Let's get started</Text>
</Stack>,
];
function OnboardingPager({ onComplete }: { onComplete: () => void }) {
return (
<PageView
pages={onboardingPages}
onPageChange={(i) => { if (i === 2) onComplete(); }}
showDots
/>
);
}