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
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | — | Controls whether the sheet is open |
onClose | () => void | — | Called when the user dismisses the sheet |
snapPoints | Array<number | string> | — | Heights the sheet snaps to. Numbers are pixels; strings like '50%' are percentages of screen height |
initialSnapIndex? | number | 0 | Index of the snap point used when the sheet first opens |
backdropDismiss? | boolean | true | Tap backdrop to close |
backdropOpacity? | number | 0.5 | Opacity of the dim overlay |
children | React.ReactNode | — | Sheet 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
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | Tab[] | — | Tab descriptors (see below) |
activeTab | string | — | key of the currently active tab |
onTabPress | (key: string) => void | — | Called 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? | boolean | true | Hide 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"
/>
);
}
Header
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
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Primary header title |
subtitle? | string | — | Secondary line below the title |
leftAction? | HeaderAction | — | Button or custom element on the left |
rightAction? | HeaderAction | — | Button or custom element on the right |
showBackButton? | boolean | false | Show a back chevron on the left |
onBackPress? | () => void | — | Called 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
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | — | Controls visibility |
onClose | () => void | — | Called when the sheet is dismissed |
actions | ActionSheetAction[] | — | Action descriptors (see below) |
title? | string | — | Optional header above the actions |
message? | string | — | Optional subtext below the title |
backdropDismiss? | boolean | true | Tap 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
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | — | Controls whether the drawer is open |
onClose | () => void | — | Called 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? | boolean | true | Tap backdrop to close |
backdropOpacity? | number | 0.5 | Opacity of the dim overlay |
children | React.ReactNode | — | Drawer 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
| Prop | Type | Default | Description |
|---|---|---|---|
icon | string | — | Registered icon name |
onPress | () => void | — | Called when tapped |
label? | string | — | When 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? | boolean | false | Disables 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
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | — | Total number of pages |
activeIndex | number | — | Zero-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? | string | — | Accessibility label |
testID? | string | — | Test 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
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Header title shown in both expanded and collapsed states |
expandedHeight? | number | 120 | Header height when fully expanded (pixels) |
collapsedHeight? | number | 56 | Header height when fully collapsed (pixels) |
scrollY | Animated.Value | SharedValue<number> | — | Animated scroll offset from the associated scroll view |
threshold? | number | 60 | Scroll distance at which the header fully collapses |
leftAction? | HeaderAction | — | Button on the left (same shape as Header) |
rightAction? | HeaderAction | — | Button on the right |
variant? | 'default' | 'transparent' | 'blurred' | 'default' | Background treatment |
children? | React.ReactNode | — | Extra content shown only in the expanded state |
accessibilityLabel? | string | — | Accessibility label |
testID? | string | — | Test 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
| Prop | Type | Default | Description |
|---|---|---|---|
tabs | Tab[] | — | Tab descriptors: { key, label, badge? } |
activeTab | string | — | key of the currently visible tab |
onTabPress | (key: string) => void | — | Called 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? | boolean | false | Allow the tab bar to scroll horizontally when there are many tabs |
accessibilityLabel? | string | — | Accessibility label for the tab bar |
testID? | string | — | Test 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 />}
</>
);
}