Social


StoryRow

A horizontally scrolling row of user story bubbles. Each bubble shows an avatar with an optional unread indicator ring. Tapping a story calls onStoryPress.

PropTypeDefaultDescription
storiesStory[]Array of story descriptors (see below)
onStoryPress?(story: Story) => voidCalled when the user taps a story bubble
size?'sm' | 'md' | 'lg''md'Avatar diameter
gap?SpacingProp'md'Gap between story bubbles
showAddButton?booleanfalsePrepend an “add story” button before the list
onAddPress?() => voidCalled when the add button is tapped
accessibilityLabel?stringAccessibility label
testID?stringTest identifier

Story shape:

interface Story {
  id: string;
  username: string;
  avatarUri?: string;
  seen?: boolean;       // When false, ring is highlighted to show unseen content
}
import { StoryRow } from '@objectifthunes/limestone-sdk';

const stories = [
  { id: '1', username: 'alice', avatarUri: 'https://example.com/alice.jpg', seen: false },
  { id: '2', username: 'bob',   avatarUri: 'https://example.com/bob.jpg',   seen: true },
  { id: '3', username: 'carol', seen: false },
];

<StoryRow
  stories={stories}
  onStoryPress={(story) => openStory(story.id)}
  showAddButton
  onAddPress={openCamera}
/>

ReactionPicker

A horizontal emoji reaction bar with a count display per reaction. Tapping an active reaction un-reacts; tapping an inactive one reacts. A long-press or ”+” button opens the full emoji picker.

PropTypeDefaultDescription
reactionsReaction[]Available reactions and their current counts
onReact?(emoji: string) => voidCalled when the user selects an emoji
onUnreact?(emoji: string) => voidCalled when the user taps an already-selected emoji
userReaction?stringEmoji the current user has already reacted with
showPicker?booleantrueShow the ”+” button to open the full emoji picker
pickerEmojis?string[]Emoji list shown in the expanded picker
size?'sm' | 'md' | 'lg''md'Emoji and count size
accessibilityLabel?stringAccessibility label
testID?stringTest identifier

Reaction shape:

interface Reaction {
  emoji: string;
  count: number;
}
import { ReactionPicker } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

function PostReactions({ postId }: { postId: string }) {
  const [reactions, setReactions] = useState([
    { emoji: '👍', count: 12 },
    { emoji: '❤️',  count: 5  },
    { emoji: '😂', count: 3  },
  ]);
  const [userReaction, setUserReaction] = useState<string | undefined>();

  return (
    <ReactionPicker
      reactions={reactions}
      userReaction={userReaction}
      onReact={(emoji) => { addReaction(postId, emoji); setUserReaction(emoji); }}
      onUnreact={(emoji) => { removeReaction(postId, emoji); setUserReaction(undefined); }}
    />
  );
}

CommentThread

A scrollable list of comments with nested reply support. Each comment shows an avatar, username, timestamp, body text, and a reply affordance.

PropTypeDefaultDescription
commentsComment[]Flat or nested comment array
onReply?(commentId: string) => voidCalled when the reply button on a comment is pressed
onLike?(commentId: string) => voidCalled when the like button is pressed
onLoadMore?() => voidCalled when the list scrolls near the end
loading?booleanfalseShows a loading indicator
maxDepth?number2Maximum nesting level for inline replies
accessibilityLabel?stringAccessibility label
testID?stringTest identifier

Comment shape:

interface Comment {
  id: string;
  authorName: string;
  authorAvatarUri?: string;
  body: string;
  timestamp: string;
  likeCount?: number;
  liked?: boolean;
  replies?: Comment[];
}
import { CommentThread } from '@objectifthunes/limestone-sdk';

<CommentThread
  comments={comments}
  onReply={(id) => openReplyInput(id)}
  onLike={(id) => toggleLike(id)}
  onLoadMore={fetchMoreComments}
/>

MentionInput

A text input that detects @ triggers and renders an autocomplete suggestion list. When the user selects a mention, it is inserted inline and highlighted.

PropTypeDefaultDescription
valuestringControlled value
onChangeText(v: string) => voidChange handler
suggestionsMentionSuggestion[]Current suggestions to show
onMentionSearch?(query: string) => voidCalled with the text after @ so you can fetch suggestions
onMentionSelect?(suggestion: MentionSuggestion) => voidCalled when the user picks a suggestion
placeholder?stringInput placeholder
size?'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Input size
variant?'outline' | 'filled' | 'underline''outline'Input variant
accessibilityLabel?stringAccessibility label
testID?stringTest identifier

MentionSuggestion shape:

interface MentionSuggestion {
  id: string;
  username: string;
  displayName?: string;
  avatarUri?: string;
}
import { MentionInput } from '@objectifthunes/limestone-sdk';
import { useState } from 'react';

function CommentComposer() {
  const [text, setText] = useState('');
  const [suggestions, setSuggestions] = useState<MentionSuggestion[]>([]);

  return (
    <MentionInput
      value={text}
      onChangeText={setText}
      suggestions={suggestions}
      onMentionSearch={(query) => searchUsers(query).then(setSuggestions)}
      onMentionSelect={(s) => console.log('Mentioned:', s.username)}
      placeholder="Write a comment…"
    />
  );
}

PresenceIndicator

A small dot or badge overlaid on an avatar or icon to show the online/offline/away/busy status of a user.

PropTypeDefaultDescription
status'online' | 'offline' | 'away' | 'busy'Presence status; controls the dot color
size?'xs' | 'sm' | 'md' | 'lg''sm'Dot diameter
placement?'top-right' | 'top-left' | 'bottom-right' | 'bottom-left''bottom-right'Position relative to the child
childrenReact.ReactNodeThe element the indicator overlays (usually an Avatar)
accessibilityLabel?stringAccessibility label for the status dot
testID?stringTest identifier
import { PresenceIndicator, Avatar } from '@objectifthunes/limestone-sdk';

function UserAvatar({ user }: { user: { name: string; status: 'online' | 'offline' | 'away' | 'busy' } }) {
  return (
    <PresenceIndicator status={user.status} placement="bottom-right">
      <Avatar name={user.name} size="md" />
    </PresenceIndicator>
  );
}