Navigation


BottomSheet

A sheet that slides up from the bottom of the screen and snaps to one or more configured heights. Keyboard-aware and backdrop-dismissible.

Key features

  • Snap points — pass an array of heights (numbers in pixels or percentage strings). The first entry is the default resting position.
  • Keyboard awareness — the sheet re-snaps automatically when the software keyboard appears or disappears.
  • Backdrop dismiss — tapping the dimmed overlay behind the sheet calls onClose.

Props

PropTypeDefaultDescription
visiblebooleanControls whether the sheet is open
onClose() => voidCalled when the user dismisses the sheet
snapPointsArray<number | string>Heights the sheet snaps to. Numbers are pixels; strings like '50%' are percentages of screen height
initialSnapIndex?number0Index of the snap point used when the sheet first opens
backdropDismiss?booleantrueTap backdrop to close
backdropOpacity?number0.5Opacity of the dim overlay
childrenReact.ReactNodeSheet content

Hook: useBottomSheet

import { useBottomSheet } from '@objectifthunes/limestone-sdk';

Returns resolved sheetStyle, backdropStyle, and accessibilityProps for the sheet and overlay surfaces.

Usage

import { BottomSheet, Stack, Text, Pressable } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

function FilterSheet() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Pressable onPress={() => setOpen(true)} bg="primary" rounded="md" px="xl" py="md" center>
        <Text color="primaryForeground">Open filters</Text>
      </Pressable>

      <BottomSheet
        visible={open}
        onClose={() => setOpen(false)}
        snapPoints={[300, '60%', '90%']}
        initialSnapIndex={0}
      >
        <Stack p="lg" gap="md">
          <Text variant="heading">Filters</Text>
          <Text variant="body" color="mutedForeground">
            Choose your preferences below.
          </Text>
          {/* filter controls */}
        </Stack>
      </BottomSheet>
    </>
  );
}

Snap points

// Fixed pixel heights
snapPoints={[240, 480]}

// Percentage of screen height
snapPoints={['30%', '60%', '90%']}

// Mixed
snapPoints={[200, '50%', '90%']}

The sheet opens at snapPoints[initialSnapIndex] (default: index 0). The user can drag between snap points.


TabBar

A bottom navigation bar that switches between top-level screens. Supports a default icon+label style and a pill-style active indicator.

Props

PropTypeDefaultDescription
tabsTab[]Tab descriptors (see below)
activeTabstringkey of the currently active tab
onTabPress(key: string) => voidCalled when the user taps a tab
variant?'default' | 'pill''default'Visual style of the active indicator
size?'sm' | 'md' | 'lg''md'Controls icon and label sizes
showLabels?booleantrueHide labels to show icons only

Tab shape:

interface Tab {
  key: string;
  label: string;
  icon: string;          // registered icon name
  badgeCount?: number;   // notification dot/count
}

Hook: useTabBar

import { useTabBar } from '@objectifthunes/limestone-sdk';

Returns tabStyles, activeStyles, badgeStyles, and accessibilityProps per tab.

Usage

import { TabBar } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

const TABS = [
  { key: 'home',     label: 'Home',    icon: 'home' },
  { key: 'explore',  label: 'Explore', icon: 'search' },
  { key: 'inbox',    label: 'Inbox',   icon: 'mail', badgeCount: 3 },
  { key: 'profile',  label: 'Profile', icon: 'user' },
];

function AppTabs() {
  const [active, setActive] = useState('home');

  return (
    <TabBar
      tabs={TABS}
      activeTab={active}
      onTabPress={setActive}
      variant="pill"
    />
  );
}

A navigation bar for the top of a screen. Supports a back button, left/right actions, transparent and blurred variants for use over full-bleed content.

Props

PropTypeDefaultDescription
titlestringPrimary header title
subtitle?stringSecondary line below the title
leftAction?HeaderActionButton or custom element on the left
rightAction?HeaderActionButton or custom element on the right
showBackButton?booleanfalseShow a back chevron on the left
onBackPress?() => voidCalled when the back button is pressed
variant?'default' | 'transparent' | 'blurred''default'Background treatment
size?'sm' | 'md' | 'lg''md'Bar height and font size

HeaderAction shape:

interface HeaderAction {
  icon?: string;
  label?: string;
  onPress: () => void;
  disabled?: boolean;
}

Hook: useHeader

import { useHeader } from '@objectifthunes/limestone-sdk';

Returns containerStyle, titleStyle, subtitleStyle, backButtonStyle, and accessibilityProps.

Usage

import { Header } from '@objectifthunes/limestone-sdk';

function ProfileScreen() {
  return (
    <>
      <Header
        title="Profile"
        showBackButton
        onBackPress={() => router.back()}
        rightAction={{ icon: 'settings', onPress: () => router.push('/settings') }}
        variant="default"
      />
      {/* screen content */}
    </>
  );
}

ActionSheet

A bottom-anchored sheet presenting a list of actions. Actions are typed as default, destructive, or cancel — the sheet styles and orders them automatically.

Props

PropTypeDefaultDescription
visiblebooleanControls visibility
onClose() => voidCalled when the sheet is dismissed
actionsActionSheetAction[]Action descriptors (see below)
title?stringOptional header above the actions
message?stringOptional subtext below the title
backdropDismiss?booleantrueTap backdrop to close

ActionSheetAction shape:

interface ActionSheetAction {
  key: string;
  label: string;
  type?: 'default' | 'destructive' | 'cancel';
  icon?: string;
  onPress: () => void;
}

Hook: useActionSheet

import { useActionSheet } from '@objectifthunes/limestone-sdk';

Returns containerStyle, actionStyle, per-action labelStyle, and accessibilityProps.

Usage

import { ActionSheet } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

function PostActions({ postId }: { postId: string }) {
  const [visible, setVisible] = useState(false);

  return (
    <ActionSheet
      visible={visible}
      onClose={() => setVisible(false)}
      title="Post options"
      actions={[
        { key: 'edit',   label: 'Edit post',   onPress: () => editPost(postId) },
        { key: 'share',  label: 'Share',        onPress: () => sharePost(postId) },
        { key: 'delete', label: 'Delete post',  type: 'destructive', onPress: () => deletePost(postId) },
        { key: 'cancel', label: 'Cancel',       type: 'cancel',      onPress: () => setVisible(false) },
      ]}
    />
  );
}

Drawer

A panel that slides in from the left or right edge of the screen. Use it for side navigation, filter panels, or any persistent off-canvas content.

Props

PropTypeDefaultDescription
visiblebooleanControls whether the drawer is open
onClose() => voidCalled when the drawer should close
side?'left' | 'right''left'Edge the drawer slides in from
width?number | string'80%'Drawer width (pixels or percentage)
backdropDismiss?booleantrueTap backdrop to close
backdropOpacity?number0.5Opacity of the dim overlay
childrenReact.ReactNodeDrawer content

Hook: useDrawer

import { useDrawer } from '@objectifthunes/limestone-sdk';

Returns drawerStyle, backdropStyle, and accessibilityProps.

Usage

import { Drawer, Stack, Text, Pressable } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

function SideNav() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Pressable onPress={() => setOpen(true)} p="md">
        <Text>Open menu</Text>
      </Pressable>

      <Drawer visible={open} onClose={() => setOpen(false)} side="left" width={280}>
        <Stack p="lg" gap="sm">
          <Text variant="heading">Navigation</Text>
          <Pressable onPress={() => { router.push('/home'); setOpen(false); }} p="sm">
            <Text>Home</Text>
          </Pressable>
          <Pressable onPress={() => { router.push('/settings'); setOpen(false); }} p="sm">
            <Text>Settings</Text>
          </Pressable>
        </Stack>
      </Drawer>
    </>
  );
}

FAB

A Floating Action Button that sits above the main content. Supports a compact icon-only form and an extended form with a label. Placement is configurable.

Props

PropTypeDefaultDescription
iconstringRegistered icon name
onPress() => voidCalled when tapped
label?stringWhen provided, renders the extended (wide) form
size?'sm' | 'md' | 'lg''md'Button size
color?ColorProp'primary'Background color token
placement?'bottom-right' | 'bottom-left' | 'bottom-center''bottom-right'Absolute positioning preset
disabled?booleanfalseDisables press and dims the button

Hook: useFAB

import { useFAB } from '@objectifthunes/limestone-sdk';

Returns containerStyle, iconStyle, labelStyle, and accessibilityProps.

Usage

import { FAB } from '@objectifthunes/limestone-sdk';

function FeedScreen() {
  return (
    <>
      {/* feed content */}
      <FAB
        icon="plus"
        label="New post"
        placement="bottom-right"
        onPress={() => router.push('/posts/new')}
      />
    </>
  );
}

PageIndicator

A row of dots (or dashes) indicating the current page in a paginated view. Works with Carousel, OnboardingScreen, or any custom swipeable list.

Props

PropTypeDefaultDescription
countnumberTotal number of pages
activeIndexnumberZero-based index of the current page
variant?'dot' | 'dash''dot'Shape of the indicators
size?'xs' | 'sm' | 'md' | 'lg''sm'Indicator size
activeColor?ColorProp'primary'Color of the active indicator
inactiveColor?ColorProp'muted'Color of inactive indicators
gap?SpacingProp'xs'Gap between indicators
accessibilityLabel?stringAccessibility label
testID?stringTest identifier

Hook: usePageIndicator

import { usePageIndicator } from '@objectifthunes/limestone-sdk';

Returns containerStyle and per-index dotStyle arrays.

Usage

import { PageIndicator } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

function SlideShow() {
  const [page, setPage] = useState(0);

  return (
    <>
      {/* ... swipeable slide content */}
      <PageIndicator count={5} activeIndex={page} variant="dot" />
    </>
  );
}

CollapsibleHeader

A screen header that shrinks or fades as the user scrolls down, and expands when they scroll back up. Pass the scroll offset from a ScrollView or FlatList to drive the animation.

Props

PropTypeDefaultDescription
titlestringHeader title shown in both expanded and collapsed states
expandedHeight?number120Header height when fully expanded (pixels)
collapsedHeight?number56Header height when fully collapsed (pixels)
scrollYAnimated.Value | SharedValue<number>Animated scroll offset from the associated scroll view
threshold?number60Scroll distance at which the header fully collapses
leftAction?HeaderActionButton on the left (same shape as Header)
rightAction?HeaderActionButton on the right
variant?'default' | 'transparent' | 'blurred''default'Background treatment
children?React.ReactNodeExtra content shown only in the expanded state
accessibilityLabel?stringAccessibility label
testID?stringTest identifier

Hook: useCollapsibleHeader

import { useCollapsibleHeader } from '@objectifthunes/limestone-sdk';

Returns containerStyle, titleStyle, expandedContentStyle, and accessibilityProps.

Usage

import { CollapsibleHeader } from '@objectifthunes/limestone-sdk';
import { useRef } from 'react';
import { Animated } from 'react-native';

function FeedScreen() {
  const scrollY = useRef(new Animated.Value(0)).current;

  return (
    <>
      <CollapsibleHeader
        title="Feed"
        scrollY={scrollY}
        expandedHeight={120}
        collapsedHeight={56}
        threshold={64}
        rightAction={{ icon: 'search', onPress: openSearch }}
      />
      <Animated.FlatList
        data={posts}
        onScroll={Animated.event(
          [{ nativeEvent: { contentOffset: { y: scrollY } } }],
          { useNativeDriver: true },
        )}
        renderItem={({ item }) => <PostCard post={item} />}
        keyExtractor={(item) => item.id}
      />
    </>
  );
}

Tabs

A top-of-screen or inline tab bar for switching between sibling content panels. Unlike TabBar (which is for primary app navigation), Tabs is for in-page content switching.

Props

PropTypeDefaultDescription
tabsTab[]Tab descriptors: { key, label, badge? }
activeTabstringkey of the currently visible tab
onTabPress(key: string) => voidCalled when the user taps a tab
variant?'underline' | 'pill' | 'filled''underline'Visual style of the active indicator
size?'sm' | 'md' | 'lg''md'Tab height and font size
scrollable?booleanfalseAllow the tab bar to scroll horizontally when there are many tabs
accessibilityLabel?stringAccessibility label for the tab bar
testID?stringTest identifier

Hook: useTabs

import { useTabs } from '@objectifthunes/limestone-sdk';

Returns tabStyles, activeStyles, indicatorStyle, and accessibilityProps per tab.

Usage

import { Tabs } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

const TABS = [
  { key: 'posts',    label: 'Posts'    },
  { key: 'media',   label: 'Media'    },
  { key: 'likes',   label: 'Likes'    },
  { key: 'replies', label: 'Replies'  },
];

function ProfileTabs() {
  const [active, setActive] = useState('posts');

  return (
    <>
      <Tabs
        tabs={TABS}
        activeTab={active}
        onTabPress={setActive}
        variant="underline"
        scrollable
      />
      {active === 'posts'  && <PostsList />}
      {active === 'media'  && <MediaGrid />}
      {active === 'likes'  && <LikedPosts />}
      {active === 'replies' && <RepliesList />}
    </>
  );
}