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.

PropTypeDefaultDescription
leadingActions?React.ReactNodeContent revealed when the row is swiped right
trailingActions?React.ReactNodeContent revealed when the row is swiped left
friction?number2Resistance multiplier applied to the swipe gesture
overshootLeft?booleanfalseAllow the row to travel past the leading action width
overshootRight?booleanfalseAllow the row to travel past the trailing action width
onSwipeLeft?() => voidCalled when the row is fully swiped to the left
onSwipeRight?() => voidCalled when the row is fully swiped to the right
childrenReact.ReactNodeThe row content displayed in the default (resting) position
accessibilityLabel?stringAccessibility label for the row
testID?stringTest 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.

PropTypeDefaultDescription
dataT[]Controlled array of items
onReorder(reordered: T[]) => voidCalled with the new order after a drag completes
renderItem(item: T, index: number, isDragging: boolean) => React.ReactNodeRow renderer; isDragging is true while the item is held
keyExtractor(item: T) => stringUnique key per item
itemHeightnumberFixed row height required for layout calculations
dragHandlePosition?'left' | 'right''right'Side where the drag handle icon appears
hapticFeedback?booleantrueTrigger a haptic impact when a drag begins
accessibilityLabel?stringAccessibility label for the list
testID?stringTest 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.

PropTypeDefaultDescription
refreshingbooleanWhether a refresh is currently in progress
onRefresh() => voidCalled when the user has pulled past the threshold
threshold?number80Pull distance in pixels before onRefresh fires
indicatorColor?ColorProp'primary'Color of the animated refresh indicator
indicatorSize?'sm' | 'md' | 'lg''md'Indicator size
childrenReact.ReactNodeScrollable content
contentPadding?SpacingPropPadding applied to the scroll content container
accessibilityLabel?stringAccessibility label
testID?stringTest 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.

PropTypeDefaultDescription
minScale?number1Minimum zoom scale
maxScale?number5Maximum zoom scale
doubleTapToZoom?booleantrueDouble-tap cycles between 1× and 2×
onScaleChange?(scale: number) => voidCalled when the zoom level changes
onPanChange?(x: number, y: number) => voidCalled when the pan offset changes
resetOnRelease?booleanfalseAnimate back to 1× and center on gesture end
childrenReact.ReactNodeContent to zoom
style?ViewStyleOuter container style
accessibilityLabel?stringAccessibility 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.

PropTypeDefaultDescription
headerImagestring | numberURI or local require for the header image
headerHeight?number280Expanded header height in pixels
collapsedHeight?number60Collapsed (sticky) header height
parallaxFactor?number0.4Speed ratio of header vs content scroll (0–1)
stickyContent?React.ReactNodeContent rendered in the collapsed header bar
childrenReact.ReactNodeScrollable page body
headerOverlay?React.ReactNodeOverlay content rendered on top of the header image
fadeHeader?booleantrueFade the header image out as it collapses
accessibilityLabel?stringAccessibility label
testID?stringTest 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.

PropTypeDefaultDescription
pagesReact.ReactNode[]Array of page content elements
initialPage?number0Zero-based index of the page shown on first render
onPageChange?(index: number) => voidCalled when the visible page changes
showDots?booleantrueRender dot indicators below the pager
dotColor?ColorProp'primary'Active dot color
dotInactiveColor?ColorProp'muted'Inactive dot color
dotSize?number8Dot diameter in pixels
scrollEnabled?booleantrueAllow swipe navigation
loop?booleanfalseWrap around from last page to first
accessibilityLabel?stringAccessibility label
testID?stringTest 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
    />
  );
}