Skip to content

Epic 0 — Design System Expansion

All UI components must live in packages/design-system. Build these before or in parallel with feature epics.


DS-001 — TalentCard component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/TalentCard.tsx
  • Edit: packages/design-system/src/index.ts — add export

Description: Swipeable card for the Discover Feed. Shows talent photo, name, primary category, top skills, location, and match score badge. Supports swipe-right (shortlist), swipe-left (skip), and an X button (block). Built on Card + Radix motion or CSS transforms. Uses cva for state variants (default, swiping-right, swiping-left).

Props: talent: TalentCardData, onShortlist, onSkip, onBlock, matchScore?: number, matchReasons?: string[], className?

Acceptance criteria:

  • Renders photo with gradient overlay, name, category, skills chips, location
  • Swipe gesture works on touch and mouse drag
  • Action buttons (✓, ✗, ×) trigger respective callbacks
  • Match score badge visible when matchScore is provided

DS-002 — JobCard component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/JobCard.tsx
  • Edit: packages/design-system/src/index.ts

Description: Card for job listings. Shows title, producer name, production type, location (or Remote badge), payment type, commitment type, and deadline. Tappable — navigates to job detail. Uses Card primitive.

Props: job: JobCardData, onClick, className?


DS-003 — SkillBadge / Chip component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/Chip.tsx
  • Edit: packages/design-system/src/index.ts

Description: Compact label chip for skills, categories, genres. Extends Badge with a removable variant (shows × icon when onRemove is provided). Used in profile editors, search filters, talent cards.

Props: label: string, onRemove?: () => void, variant?: 'default' | 'outline' | 'filled', size?: 'sm' | 'md'


DS-004 — Select and MultiSelect components

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/Select.tsx
  • Create: packages/design-system/src/components/ui/MultiSelect.tsx
  • Edit: packages/design-system/src/index.ts

Description: Select wraps Radix Select primitive with project styling. MultiSelect shows selected items as Chip components and opens a dropdown with checkboxes. Used for: primary category, skills, languages, production types, filters.


DS-005 — FileUpload component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/FileUpload.tsx
  • Edit: packages/design-system/src/index.ts

Description: Drag-and-drop + click-to-browse file upload zone. Shows upload progress bar. Accepts accept (MIME types), maxSize, multiple. Emits onFiles(files: File[]). Used for: profile photo, media uploads, reel builder.


DS-006 — EmptyState component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/EmptyState.tsx
  • Edit: packages/design-system/src/index.ts

Description: Centered empty state with icon slot, title, description, and optional CTA button. Used across all list pages when there is no data.

Props: icon?: ReactNode, title: string, description?: string, action?: { label: string; onClick: () => void }


DS-007 — Skeleton loaders

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/Skeleton.tsx
  • Edit: packages/design-system/src/index.ts

Description: Animated shimmer skeleton with Skeleton, SkeletonText, SkeletonAvatar, SkeletonCard. Used in all React Query loading states.


DS-008 — Toast / Notification Banner

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/Toast.tsx
  • Edit: packages/design-system/src/index.ts

Description: Wraps Radix Toast. Supports success, error, info, warning variants. Provides a useToast() hook to trigger toasts from anywhere. Used for action confirmations, API error messages.


DS-009 — BottomTabBar component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/layout/BottomTabBar.tsx
  • Edit: packages/design-system/src/index.ts

Description: Fixed bottom navigation bar for the app (mobile-first). Receives an array of { label, icon, path } nav items. Active tab is highlighted with brand color. Used in apps/app as the primary navigation structure matching the Figma Jornada flows.


DS-010 — ProgressRing / ProfileCompleteness component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/ProgressRing.tsx
  • Edit: packages/design-system/src/index.ts

Description: SVG circular progress ring for profile completeness indicator. Shown on profile pages and in the discover feed card.

Props: value: number (0–100), size?: number, strokeWidth?: number, className?


DS-011 — SearchBar component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/SearchBar.tsx
  • Edit: packages/design-system/src/index.ts

Description: Styled search input with a leading search icon, optional clear (×) button, and onSearch(query) callback on submit/Enter. Used in the talent search and job board pages.


DS-012 — FilterSheet component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/FilterSheet.tsx
  • Edit: packages/design-system/src/index.ts

Description: Bottom sheet drawer (Radix Dialog styled as sheet) for search/filter options. Contains slots for filter sections. Shows an "Apply" and "Reset" button. Used on the Discover and Jobs pages.


DS-013 — MediaPlayer component

  • [x] Implemented

Files:

  • Create: packages/design-system/src/components/ui/MediaPlayer.tsx
  • Edit: packages/design-system/src/index.ts

Description: Video/audio player using native HTML5 <video>/<audio> with custom controls overlay (play/pause, scrubber, time, volume). Handles thumbnail display before play. Used on talent profile for demo reels.


DS-014 — Migrate landing page components to design system

  • [x] Implemented

Files to refactor (landing → use DS imports):

  • apps/landing/src/components/HeroSection.tsx — extract Button CTA to <Button> from DS
  • apps/landing/src/components/Nav.tsx — use DS Button for CTA
  • apps/landing/src/components/WaitlistForm.tsx — use DS Input, Button
  • apps/landing/src/components/Footer.tsx — use DS Button where applicable
  • apps/landing/src/components/FeaturesSection.tsx — use DS Card
  • apps/landing/src/components/CategoriesSection.tsx — use DS Badge/Chip

Description: Replace inline Tailwind patterns that duplicate design system components. Import from @castyou/design-system. Do not change visual appearance — only replace duplicated component patterns. Keep Next.js / next-intl specific logic in place.


DS-015 — Light / Dark mode system + ThemeToggle

  • [x] Implemented

Files:

  • Create: packages/design-system/src/tokens/theme.css — CSS custom property definitions for both modes
  • Edit: packages/design-system/src/tokens/index.ts — add semantic token names as JS constants
  • Create: packages/design-system/src/providers/ThemeProvider.tsx — React context, persistence, prefers-color-scheme detection
  • Create: packages/design-system/src/hooks/useTheme.tsuseTheme() hook
  • Create: packages/design-system/src/components/ui/ThemeToggle.tsx — sun/moon toggle button
  • Edit: packages/design-system/src/index.ts — export ThemeProvider, useTheme, ThemeToggle
  • Edit: packages/design-system/tailwind.config.ts — wire semantic tokens to CSS variables
  • Edit: every existing DS component — replace hardcoded bg-neutral-900, text-white, border-white/10 etc. with semantic token classes (bg-surface, text-primary, border-default, etc.)
  • Edit: apps/app/src/main.tsx — wrap app in <ThemeProvider>
  • Edit: packages/design-system/src/components/layout/AppHeader.tsx — add <ThemeToggle> to header

Design reference: the attached screenshots show the intended light and dark visual language.

ElementLight modeDark mode
Page background#f5f0fc — soft lavender#0d0820 — near-black purple
Card surface#ffffff — white#1a0f35 — dark purple
Card surface alt#faf8ff — off-white lavender#221442 — mid purple
Text primary#1a0a2e — deep purple-black#ffffff — white
Text secondary#6D10A3 — brand purple#c4a8e8 — soft lavender
Text muted#8b7aa0#7a6a8a
Border#e8d9f5 — light purple tint#2d1b4e — dark purple border
Input background#ffffff#150c2e
Input border#d4b8f0#3d2460

Semantic CSS custom properties to define in theme.css:

css
:root {
  /* surfaces */
  --color-surface-page:     #f5f0fc;
  --color-surface-card:     #ffffff;
  --color-surface-card-alt: #faf8ff;
  --color-surface-input:    #ffffff;
  --color-surface-overlay:  rgba(109, 16, 163, 0.06);

  /* text */
  --color-text-primary:   #1a0a2e;
  --color-text-secondary: #6D10A3;
  --color-text-muted:     #8b7aa0;
  --color-text-inverse:   #ffffff;

  /* borders */
  --color-border:       #e8d9f5;
  --color-border-input: #d4b8f0;
  --color-border-focus: #6D10A3;

  /* brand (same in both modes) */
  --color-brand:        #6D10A3;
  --color-brand-hover:  #5a0d87;
  --color-violet:       #7367F0;
}

.dark {
  --color-surface-page:     #0d0820;
  --color-surface-card:     #1a0f35;
  --color-surface-card-alt: #221442;
  --color-surface-input:    #150c2e;
  --color-surface-overlay:  rgba(115, 103, 240, 0.10);

  --color-text-primary:   #ffffff;
  --color-text-secondary: #c4a8e8;
  --color-text-muted:     #7a6a8a;
  --color-text-inverse:   #1a0a2e;

  --color-border:       #2d1b4e;
  --color-border-input: #3d2460;
  --color-border-focus: #9b45d4;
}

Tailwind semantic utility classes to add (via CSS variables):

js
// tailwind.config.ts — extend colors
colors: {
  surface: {
    page:     'var(--color-surface-page)',
    card:     'var(--color-surface-card)',
    'card-alt': 'var(--color-surface-card-alt)',
    input:    'var(--color-surface-input)',
    overlay:  'var(--color-surface-overlay)',
  },
  text: {
    primary:   'var(--color-text-primary)',
    secondary: 'var(--color-text-secondary)',
    muted:     'var(--color-text-muted)',
    inverse:   'var(--color-text-inverse)',
  },
  border: {
    DEFAULT: 'var(--color-border)',
    input:   'var(--color-border-input)',
    focus:   'var(--color-border-focus)',
  },
}

Chip colour palette (same in both modes — vibrant, category-coded):

The skill chips in the screenshots use distinct vivid colours per category. These should become chip variant values — not generic colours but named categories:

VariantBackgroundTextExample use
dance#FF6B35 (orange)whiteDance
acting#E91E8C (hot pink)whiteActing
hiphop#7367F0 (violet)whiteHipHop
language-native#22C55E (green)whiteNative
language-accent#14B8A6 (teal) gradientwhiteEnglish • British • Scouse
defaultvar(--color-surface-card-alt)var(--color-text-primary)generic

Add these as new variants to the Chip component (cva config).

ThemeProvider behaviour:

  1. On mount: read localStorage.getItem('castyou-theme') — apply saved preference
  2. If no saved preference: check window.matchMedia('(prefers-color-scheme: dark)') — apply system preference
  3. On toggle: flip .dark class on <html>, persist choice to localStorage
  4. Export useTheme(): { theme: 'light' | 'dark'; toggle: () => void; setTheme: (t: 'light' | 'dark') => void }

ThemeToggle component:

  • Sun icon (light mode) / Moon icon (dark mode) from lucide-react
  • Smooth icon crossfade transition (200ms)
  • Available in two sizes: size="sm" (icon-only, for header) and size="md" (icon + label, for settings page)
  • Uses DS Button with variant="ghost" as base

Component migration checklist — every DS component must be audited. Replace:

ReplaceWith
bg-neutral-900 / bg-neutral-800bg-surface-card / bg-surface-page
text-white (body text)text-text-primary
text-neutral-400 / text-neutral-500 (muted)text-text-muted
border-white/10 / border-neutral-700border-border
bg-white (card/input surfaces)bg-surface-card / bg-surface-input
text-neutral-900 (dark text)text-text-primary

Components to audit: Button, Input, Textarea, Card, Badge, Chip, Select, MultiSelect, Modal, Toast, FilterSheet, SearchBar, FileUpload, EmptyState, Skeleton, MediaPlayer, AppShell, AppHeader, BottomTabBar.

Acceptance criteria:

  • Toggling theme switches the full app instantly with no flash
  • On first load, system preference is respected
  • Preference persists across page reloads (localStorage)
  • All DS components look correct in both light and dark mode — no hardcoded dark-only or light-only colours remain in component files
  • ThemeToggle is visible in AppHeader
  • apps/landing also supports light/dark (wrap in ThemeProvider, add toggle to Nav)
  • No regression in existing tests; add new tests for useTheme hook (toggle, persistence, system preference fallback)

DS-016 — Migrate local app components to design system

  • [x] Implemented

Files:

  • Audit: apps/app/src/components/ — identify every component not imported from @castyou/design-system
  • Move each to packages/design-system/src/components/ui/ or layout/
  • Edit: packages/design-system/src/index.ts — export each migrated component
  • Edit: all apps/app and apps/landing import sites — update to @castyou/design-system
  • Delete the original local file after import sites are updated
  • Run: pnpm --filter @castyou/app test run + pnpm --filter @castyou/design-system build — must pass

Components to audit and migrate (non-exhaustive — run the audit first):

Likely locationComponentNotes
apps/app/src/components/AppShell, AppHeaderAlready referenced in DS tickets — confirm they live in DS
apps/app/src/components/CaStarsWidgetDefined in FE-CASTARS-001 as a local component — move to DS
apps/app/src/components/BundleCardFE-CASTARS-002 — move to DS
apps/app/src/components/FeatureStoreCardFE-CASTARS-003 — move to DS
apps/app/src/components/LivenessChallengeException — owns raw MediaPipe/camera logic, stays local
apps/app/src/components/StripeCheckoutException — owns Stripe Elements, stays local
apps/app/src/components/feed/PostCardEpic 16 — should live in DS from the start
apps/app/src/components/groups/GroupApplyModalEpic 9 — move to DS
apps/app/src/components/reel/TransitionPickerEpic 7 v2 — move to DS
apps/app/src/components/folders/SaveToFolderButtonEpic 19 — move to DS
apps/app/src/components/ExportPitchModalEpic 18 — move to DS

Audit command to find local components not from DS:

bash
grep -rL "@castyou/design-system" apps/app/src/components/ --include="*.tsx"

Rules:

  • A component moves to DS if it is (or could be) used in more than one page, or is visually part of the product's design language.
  • A component stays local if it owns a third-party SDK boundary (Stripe, MediaPipe, Firebase) — wrap it in a DS shell instead.
  • After migration, the component must use DS tokens only (no raw Tailwind colour literals — see DS-015 migration table).