Offline-First
SyncIndicator
Displays the current synchronisation state of the app. Maps a status value to a themed icon and label. Animates the syncing state with a rotation loop so users get clear visual feedback while data is being pushed.
| Prop | Type | Default | Description |
|---|---|---|---|
status | 'synced' | 'syncing' | 'pending' | 'error' | 'offline' | — | Current sync state |
label? | string | auto | Override the default label for the given status |
size? | 'sm' | 'md' | 'lg' | 'md' | Size of the icon and label text |
showLabel? | boolean | true | Whether to render the status label beside the icon |
accessibilityLabel? | string | — | Accessibility label for the container |
testID? | string | — | Test identifier |
import { SyncIndicator } from '@objectifthunes/limestone-sdk';
<SyncIndicator status="syncing" size="sm" />
<SyncIndicator status="error" label="Failed — tap to retry" />
<SyncIndicator status="synced" showLabel={false} />
ConflictCard
Renders a structured conflict-resolution UI when the same record has been mutated both locally and on the server. Presents two version panels — local and remote — with a diff-style highlight on differing fields, and two action buttons for accepting either version.
| Prop | Type | Default | Description |
|---|---|---|---|
localVersion | Record<string, unknown> | — | The local (pending) version of the record |
remoteVersion | Record<string, unknown> | — | The server-side version of the record |
fields | ConflictField[] | — | Field descriptors: { key: string; label: string; format?: (v: unknown) => string } |
onKeepLocal | () => void | — | Called when the user chooses the local version |
onKeepRemote | () => void | — | Called when the user chooses the remote version |
title? | string | 'Conflict detected' | Card heading |
localLabel? | string | 'Your version' | Label for the local column |
remoteLabel? | string | 'Server version' | Label for the remote column |
variant? | 'default' | 'compact' | 'default' | 'compact' hides field labels and reduces padding |
accessibilityLabel? | string | — | Accessibility label |
testID? | string | — | Test identifier |
import { ConflictCard } from '@objectifthunes/limestone-sdk';
<ConflictCard
title="Budget conflict"
localVersion={{ name: 'Q2 Budget', amount: 12000 }}
remoteVersion={{ name: 'Q2 Budget', amount: 11500 }}
fields={[
{ key: 'name', label: 'Name' },
{ key: 'amount', label: 'Amount', format: (v) => `$${v}` },
]}
onKeepLocal={() => resolveConflict('local')}
onKeepRemote={() => resolveConflict('remote')}
/>
OfflineQueueList
Visualises the queue of mutations waiting to be synced to the server. Each item displays the operation type (create / update / delete), the resource name, a relative timestamp, and an optional retry or discard action.
| Prop | Type | Default | Description |
|---|---|---|---|
items | QueuedItem[] | — | Array of queued mutations to display |
onRetry? | (id: string) => void | — | Called when the user taps retry on an item |
onDiscard? | (id: string) => void | — | Called when the user taps discard on an item |
emptyMessage? | string | 'No pending changes' | Message shown when items is empty |
showTimestamp? | boolean | true | Render relative time (e.g. “2 minutes ago”) beside each item |
size? | 'sm' | 'md' | 'lg' | 'md' | Controls row height and font sizes |
accessibilityLabel? | string | — | Accessibility label for the list |
testID? | string | — | Test identifier |
QueuedItem shape:
interface QueuedItem {
id: string;
operation: 'create' | 'update' | 'delete';
resource: string; // e.g. 'invoice', 'user', 'comment'
label?: string; // human-readable description of the record
status: 'pending' | 'retrying' | 'failed';
queuedAt: Date;
retryCount?: number;
}
import { OfflineQueueList } from '@objectifthunes/limestone-sdk';
<OfflineQueueList
items={pendingMutations}
onRetry={(id) => queue.retryItem(id)}
onDiscard={(id) => queue.discardItem(id)}
emptyMessage="All changes synced"
/>
CachedImage
A drop-in replacement for Image that stores downloaded assets in the device’s cache directory. Falls back to a Skeleton placeholder while loading and to an error state (icon + optional message) on failure. Cache hits are served synchronously with no visible flash.
| Prop | Type | Default | Description |
|---|---|---|---|
uri | string | — | Remote URL to download and cache |
cacheKey? | string | SHA-256 of uri | Explicit cache key; use to invalidate a specific entry |
fallbackUri? | string | — | Secondary URI to try if the primary fails |
resizeMode? | 'cover' | 'contain' | 'stretch' | 'center' | 'cover' | Image resize mode |
aspectRatio? | number | — | Aspect ratio constraint applied to the wrapper |
showLoadingIndicator? | boolean | true | Show a Skeleton pulse while loading |
errorIcon? | string | — | Icon name from the registered set to display on error |
errorMessage? | string | — | Text to show below the error icon |
rounded? | RadiiProp | — | Border radius token applied to the image |
accessibilityLabel? | string | — | Accessibility label for the image |
testID? | string | — | Test identifier |
All BoxStyleProps | — | — | Width, height, margin, etc. |
import { CachedImage } from '@objectifthunes/limestone-sdk';
<CachedImage
uri="https://cdn.example.com/products/widget.jpg"
cacheKey="product-widget-v2"
aspectRatio={16 / 9}
rounded="md"
w="100%"
errorMessage="Image unavailable"
/>
PendingBadge
An overlay badge that indicates an item has local changes not yet synced to the server. Designed to be composed on top of any other component (avatar, card, list item) via absolute positioning. Supports a numeric count for stacked pending operations and a pulsing animation while sync is in progress.
| Prop | Type | Default | Description |
|---|---|---|---|
count? | number | — | Number of pending operations; omit for a plain dot badge |
status? | 'pending' | 'syncing' | 'error' | 'pending' | Controls the colour: warning / primary / destructive |
pulse? | boolean | auto (true when status === 'syncing') | Animate a pulsing ring around the badge |
size? | 'sm' | 'md' | 'lg' | 'md' | Badge size |
placement? | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-right' | Corner to anchor the badge to |
children | React.ReactNode | — | The content to overlay the badge on |
accessibilityLabel? | string | — | Accessibility label for the badge |
testID? | string | — | Test identifier |
import { PendingBadge, Avatar } from '@objectifthunes/limestone-sdk';
// Plain dot — one unsaved change
<PendingBadge status="pending">
<Avatar name="Alice Martin" />
</PendingBadge>
// Count + sync-in-progress animation
<PendingBadge count={3} status="syncing">
<Card variant="outlined">
<Text>Invoice #1042</Text>
</Card>
</PendingBadge>
// Error state — sync failed
<PendingBadge status="error" placement="top-left">
<ListItem title="Budget Q2" />
</PendingBadge>