Overview
Compare
Inspect
MainTabShell(title="클럽", headerRight=ClubJoinHeaderAction — a "초대 코드" HeaderTextAction since 2026-09-17, plus a "+" HeaderIconButton once the viewer already has clubs) → PillNav 내 클럽/둘러보기. 내 클럽 = MyClubListControls "가입한 클럽" SectionHeader (renamed 2026-09-17 from "내 클럽") → GroupedFeedList (title-less 멤버 section, ClubCard TRACKING anatomy) + conditional 게스트 section (SAME rows, GLIMPSE anatomy — isMember=false); the "클럽 만들기" BottomCtaBand pins under this tab ONLY for a newcomer (no clubs, no guest invite) — a member with clubs finds it as the header's "+" icon instead. 둘러보기 = ClubDiscoveryControls (search + expandable filter panel) → FeedList of ClubCard GLIMPSE cards, no bottom CTA. 초대코드 = its own DetailShell route, an inline (not pinned) full-width CTA.
route · apps/mobile/app/(tabs)/clubs.tsx → club-list-screen.tsx; /clubs?tab=discover → the same screen DiscoverTab pane (/clubs/discover redirects there; the standalone screen was retired 2026-09-08); /join-club → join-club-screen.tsx
grounded in 7 source file(s)
packages/features/clubs/src/club-list-screen.tsxpackages/features/clubs/src/join-club-screen.tsxpackages/features/clubs/src/shared/club-card.tsxpackages/features/clubs/src/shared/club-discovery-controls.tsxpackages/features/clubs/src/shared/discover-filters.tsxpackages/features/clubs/src/shared/my-club-list-controls.tsxpackages/features/clubs/src/shared/clubs-header-bar.ts
Spec
1
Foundations · 디자인 시스템
Spacing · 4px grid
$1 – $4
4 · 8 · 12 · 16 px
$5 – $10
20 · 24 · 32 · 40 · 48 · 64 px
Radius
$2 8 · $3 12 · $4 16 · $7 9999 (pill)
Color · Classic Grass (light)
Surface
$background #F8FBF9 · $card #FFFFFF · $surfaceTertiary #E5FAEA
Brand
$primary #006D37 · $primarySubtle #E5FAEA
Text
$text #0D100E · $textSecondary #4F5851 · $textTertiary #626C64
Border
$border #CAD4CC · $borderSubtle #DFE7E1
Status
$success #14804A · $warning #946200 · $error #B8332E
Badge intents
accent #E0F8F3/#007B6B · warning #FFF4D6/#946200 · error #FDECEC/#B8332E
Court
$courtClay #B84A2E · $courtHard #247BA0 · $courtGreen #006633
Typography · Pretendard
cardTitle · navTitle
16 / 24 · 600 · 17 / 22 · 600
cardBody · cardMeta
14 / 22 · 400 · 13 / 18 · 500
badge · micro
12 / 18 · 600 · 12 / 16 · 600
display Sm/Md/Lg/Xl
20/28 · 28/36 · 36/44 · 48/56 · 700
Primitives · @twomore/ui
Layout
YStack · XStack · Card (default/flat) · Well · Divider
Controls
Pressable · Badge · Icon · PillNav · MainTabShell · BottomCtaBand
Data viz
DateTile · CapacityRing · ProgressBar · AvatarStack · SegmentedDonutChart
Domain cards
SessionCard · ClubCard · VenueHero · SessionCompactRow · member row
Box sizes
AVATAR XS 28 · SM 32 · MD 40 · LG 44 · 2XL 88 · TOUCH_TARGET 44
Rationale
Every part is built from these — one 4px grid, the Pretendard type scale, the Classic-Grass palette, and the shared @twomore/ui primitives + domain cards.
2
헤더 + 탭
Structure
Header
MainTabShell — title "클럽", headerRight = ClubJoinHeaderAction (a "초대 코드" HeaderTextAction, changed 2026-09-17/U-09 from an icon-only 🔑 button; routes.joinClub() with no origin club) + a "+" HeaderIconButton (routes.createClub) once the viewer already has clubs
Tabs
PillNav 내 클럽/둘러보기 — segmented, solid $primary fill on the active chip
3
내 클럽 — 멤버 카드
Structure
ClubCard (member)
TRACKING anatomy — corners-law seam (tier band top-left, alarm circle top-right, flag+faces+capacity bottom-left) then up to 2 render-only session rows (dot+date+time+venue+micro meter), no eyebrow/divider.
게스트 section
renders ONLY when the viewer holds an active club_guests invite — rows are isMember=false, i.e. GLIMPSE anatomy, not a member card.
CTA
Now: no band here — a member with clubs sees 클럽 만들기 as the header's "+" icon instead; see the 비어있음 state for the band.
4
둘러보기
Structure
Controls
ClubDiscoveryControls → ExpandableFilterBar (search + "필터 N" toggle)
Card
ClubCard (potential) — GLIMPSE anatomy: tagline + ONE wrapping row of 📍/👥/📅 fact chips, no session rows, no bottom CTA
5
둘러보기 필터 (expanded)
Structure
DiscoverFilterPanel
discover-filters.tsx :112-173 — DropdownChip order: 형식 → 지역→구 cascade → ELO 실력 range (RangeChip) → 요일 → 시간대 → 가입 방식, wrapping chip row, "필터 N" badge counts active selections
6
초대 코드
Structure
Header
DetailShell — single-line title "{club} 가입하기" when reached from a club header, else generic "클럽 가입하기"; NO subtitle (i18n/ko/clubs.ts :255-264)
Identity row
origin club avatar + name — ONLY when originClubId is known (Wave 2b audit finding 7)
Code entry
a big letter-spaced Text mirror (StyledInput has no font-size override surface) above the real StyledInput
CTA
inline full-width ActionButton "가입하기" — NOT a pinned BottomCtaBand, it lives in the scroll body
7
비어있음
Structure
EmptyState
icon=Users, variant="full", actionLabel="클럽 둘러보기" → routes.discoverClubs — this is the ONE state where the "클럽 만들기" BottomCtaBand pins underneath (newcomer only); the header carries no "+" icon here