Skip to main content

PostCard

Displays an individual post with author information, content, engagement metrics, and action buttons. Supports likes, comments, views, and edit/delete actions for post owners.

Props

userId
string
required
The current user’s ID, used to determine if edit/delete actions should be shown
post
Post
required
Post object containing all post data including:
  • $id: Post unique identifier
  • authorId: Post author’s user ID
  • authorName: Post author’s display name
  • content: Post text content
  • likes: Number of likes
  • likedBy: Array of user IDs who liked the post
  • commentCount: Number of comments
  • views: Number of views
  • image: Optional image URL
  • $createdAt: ISO timestamp of creation

Usage Example

import PostCard from "@/components/PostCard";
import { usePosts } from "@/store/usePosts";

function HomeScreen() {
  const [userId, setUserId] = useState("");
  const posts = usePosts((s) => s.posts);

  return (
    <LegendList
      data={posts}
      keyExtractor={(item) => item.$id}
      renderItem={({ item }) => (
        <PostCard userId={userId} post={item} />
      )}
    />
  );
}

Visual Description

  • Header: Circular avatar with author’s initial, author name, and relative timestamp
  • Actions (owner only): Edit and delete icons in top-right
  • Content: Post text with proper line height
  • Image: Optional aspect-video image placeholder
  • Footer: Like, comment, and view counts with icons
  • Interaction: Entire card is pressable and navigates to post detail

CommentCard

Displays a user comment with author information, timestamp, and edit/delete actions for comment owners.

Props

userId
string
required
Current user’s ID for determining ownership
comment
CommentType
required
Comment object with:
  • $id: Comment unique identifier
  • authorId: Comment author’s user ID
  • content: Comment text
  • $createdAt: ISO timestamp
onEditPress
() => void
required
Callback function invoked when edit button is pressed
onDeletePress
() => void
required
Callback function invoked when delete button is pressed

Usage Example

import CommentCard from "@/components/CommentCard";

function PostDetailScreen() {
  const [userId, setUserId] = useState("");
  const [editingComment, setEditingComment] = useState<string | null>(null);

  const handleEdit = (commentId: string) => {
    setEditingComment(commentId);
    // Open edit modal
  };

  const handleDelete = async (commentId: string) => {
    // Delete comment logic
  };

  return (
    <CommentCard
      userId={userId}
      comment={comment}
      onEditPress={() => handleEdit(comment.$id)}
      onDeletePress={() => handleDelete(comment.$id)}
    />
  );
}

Visual Description

  • Avatar: Circular gray background with author’s first letter
  • Header: Author name with edit/delete icons (if owner)
  • Content: Comment text with proper spacing
  • Footer: Formatted publication date in bottom-right
  • Border: Gray bottom border separating comments

MatchRoomCard

Displays a match room with team information, status badge, and join/view button. Automatically calculates status based on start/end times.

Props

room
Room
required
Room object containing:
  • $id: Room unique identifier
  • teams: Array of two team names
  • startTime: ISO timestamp of match start
  • endTime: ISO timestamp of match end (optional)
  • status: Current status (“live” | “upcoming” | “finished”)
  • matchType: “ODI” | “TEST” | “T20”
  • isLocked: Boolean indicating if chat is locked

Usage Example

import MatchRoomCard from "@/components/MatchRoomCard";
import { useRooms } from "@/store/useRooms";

function RoomsScreen() {
  const rooms = useRooms((s) => s.rooms);

  return (
    <LegendList
      data={rooms}
      keyExtractor={(item) => item.$id}
      renderItem={({ item }) => <MatchRoomCard room={item} />}
    />
  );
}

Visual Description

  • Team Logos: Two circular badges with team abbreviations (first 3 letters)
  • Teams Display: “Team A vs Team B” centered text
  • Status Badge: Color-coded pill (green=live, yellow=upcoming, red=finished)
  • CTA Button: Orange “Join Room” or “View Room” button based on status
  • Card Style: White background with shadow and active scale animation

CreatePostModal

Full-screen modal for creating new posts with content input and character counter.

Props

isVisible
boolean
required
Controls modal visibility
onClose
() => void
required
Callback function to close the modal

Usage Example

import CreatePostModal from "@/components/CreatePostModal";

function HomeScreen() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <>
      <Pressable onPress={() => setIsVisible(true)}>
        <Ionicons name="add" size={24} color="white" />
      </Pressable>

      <CreatePostModal
        isVisible={isVisible}
        onClose={() => setIsVisible(false)}
      />
    </>
  );
}

Features

  • Header: Close icon (left), “Create Post” submit button (right)
  • Content Input: Multi-line text input with 512 character limit
  • Character Counter: Shows current/max characters
  • Validation: Uses Zod schema validation before submission
  • Toast Feedback: Success/error notifications
  • Auto-clear: Resets form on successful submission

CreateRoomModal

Multi-step modal for creating match rooms with team selection, match details, and room settings.

Props

visible
boolean
required
Controls modal visibility
onClose
() => void
required
Callback to close the modal

Usage Example

import CreateRoomModal from "@/components/CreateRoomModal";

function RoomsScreen() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <>
      <Pressable onPress={() => setIsVisible(true)}>
        <Ionicons name="add" size={24} color="white" />
      </Pressable>

      <CreateRoomModal
        visible={isVisible}
        onClose={() => setIsVisible(false)}
      />
    </>
  );
}

Multi-step Flow

  1. Team Info: Input for Team 1 and Team 2 names
  2. Match Info: Start date, end date, and match type (ODI/TEST/T20) selection
  3. Room Settings: Toggle for chat availability (locked/unlocked)

Features

  • Step Navigation: Back button adapts based on current step
  • Validation: Validates each step before proceeding
  • Date Pickers: DateTime pickers for match start/end
  • Dropdown: Match type selection dropdown
  • Form Reset: Clears all inputs on successful creation
  • Centered Modal: Semi-transparent overlay with centered card

LeaderboardPodiumUser

Displays a user in the top 3 podium positions with rank badge and avatar.

Props

userStat
UserStats
required
User statistics object containing:
  • username: User’s display name
  • messageCount: Number of messages sent
rank
1 | 2 | 3
required
Podium position (1st, 2nd, or 3rd place)

Usage Example

import LeaderboardPodiumUser from "@/components/LeaderboardPodiumUser";

function LeaderboardScreen() {
  const topUsers = userStats.slice(0, 3);

  return (
    <View className="flex-row items-end justify-center gap-4">
      {/* 2nd place */}
      <LeaderboardPodiumUser userStat={topUsers[1]} rank={2} />
      
      {/* 1st place */}
      <LeaderboardPodiumUser userStat={topUsers[0]} rank={1} />
      
      {/* 3rd place */}
      <LeaderboardPodiumUser userStat={topUsers[2]} rank={3} />
    </View>
  );
}

Visual Description

  • Avatar Size: 1st place (96x96), 2nd/3rd place (80x80)
  • Rank Badge: Overlapping badge with orange rank number and username
  • Styling: Larger avatar for 1st place, smaller margin-top offset

LeaderboardUserRow

Displays a leaderboard user row for positions 4 and below.

Props

user
UserStats
required
User statistics object
rank
number
required
User’s rank position (0-indexed, so rank 0 = 4th place)

Usage Example

import LeaderboardUserRow from "@/components/LeaderboardUserRow";

function LeaderboardScreen() {
  const remainingUsers = userStats.slice(3);

  return (
    <>
      {remainingUsers.map((user, index) => (
        <LeaderboardUserRow
          key={user.userId}
          user={user}
          rank={index}
        />
      ))}
    </>
  );
}

Visual Description

  • Badge: Orange circular badge with rank number (#4, #5, etc.)
  • User Info: Username and message count
  • Message Count: Compact formatted number on right side
  • Row Style: Gray background with shadow and active animation

FilterChip

Toggleable filter chip for category selection with selected/unselected states.

Props

label
string
required
Text label displayed in the chip
selected
boolean
required
Whether the chip is currently selected
onPress
() => void
required
Callback when chip is pressed
width
string
Optional Tailwind width class (e.g., “20” for w-20)

Usage Example

import FilterChip from "@/components/FilterChip";

function RoomsScreen() {
  const [selectedFilter, setSelectedFilter] = useState<"all" | "live" | "upcoming" | "finished">("all");

  return (
    <ScrollView horizontal showsHorizontalScrollIndicator={false}>
      {["all", "live", "upcoming", "finished"].map((label) => (
        <FilterChip
          key={label}
          label={label}
          selected={selectedFilter === label}
          onPress={() => setSelectedFilter(label as any)}
          width={label === "all" || label === "live" ? "20" : undefined}
        />
      ))}
    </ScrollView>
  );
}

Visual States

  • Selected: Orange background, white text
  • Unselected: Transparent background with orange border, orange text
  • Label: Capitalized text
  • Shape: Fully rounded pill shape

EmptyState

Displays an empty state placeholder with icon, message, and call-to-action button.

Props

type
'post' | 'comment' | 'room' | 'roomMessage'
required
Type of empty state to display (determines icon, title, description, and button text)
onPress
() => void
required
Callback when CTA button is pressed

Usage Example

import { EmptyState } from "@/components/EmptyState";

function HomeScreen() {
  const [isVisible, setIsVisible] = useState(false);
  const posts = usePosts((s) => s.posts);

  return (
    <LegendList
      data={posts}
      renderItem={({ item }) => <PostCard post={item} />}
      ListEmptyComponent={
        <EmptyState
          type="post"
          onPress={() => setIsVisible(true)}
        />
      }
    />
  );
}

Content by Type

TypeIconTitleDescriptionButton Text
postchatbubble-outlineNo posts yet!Be the first one to start the legacy conversation!Create one!
commentchatbox-ellipses-outlineNo comments yet!Be the first one to comment and start a discussion!Comment now!
roomchatbubble-ellipses-outlineNo rooms yet!Be the first one to start a room and create the legacy!Start legacy!
roomMessagechatbubble-ellipses-outlineNo messages yet!Be the first to send a message and start the conversation!Message now!

Visual Description

  • Layout: Centered vertically and horizontally
  • Icon: Large gray icon (48pt)
  • Title: Bold, large text
  • Description: Gray, smaller text with 80% max width
  • Button: Orange pill-shaped button with rocket icon

ProfileDrawer

Slide-in navigation drawer from the left side with user profile and navigation links.

Props

username
string
required
Current user’s display name
isDrawerOpen
boolean
required
Controls drawer open/closed state
onClose
() => void
required
Callback to close the drawer
searchQueryRef
RefObject<TextInput | null>
required
Reference to search input for focusing when “Explore” is pressed

Usage Example

import ProfileDrawer from "@/components/ProfileDrawer";
import { useUser } from "@/store/useUser";

function HomeScreen() {
  const [isDrawerOpen, setIsDrawerOpen] = useState(false);
  const searchQueryRef = useRef<TextInput>(null);
  const username = useUser((s) => s.username);

  return (
    <>
      {/* Overlay */}
      {isDrawerOpen && (
        <Pressable
          style={{ position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.4)" }}
          onPress={() => setIsDrawerOpen(false)}
        />
      )}

      {/* Drawer */}
      <ProfileDrawer
        username={username || ""}
        isDrawerOpen={isDrawerOpen}
        onClose={() => setIsDrawerOpen(false)}
        searchQueryRef={searchQueryRef}
      />
    </>
  );
}
  • Profile: Navigate to user profile screen
  • Explore: Close drawer and focus search input
  • Rooms: Navigate to rooms screen
  • Settings: Navigate to settings screen

Visual Description

  • Width: 80% of screen width
  • Animation: Slides in from left with 250ms ease-in transition
  • Background: Light gray (#f8fafc)
  • Shadow: Drop shadow on right edge
  • User Header: Avatar with username
  • Navigation Items: Icon + label in vertical list

Build docs developers (and LLMs) love