v0.1 public beta early access on GitHub
Home / Screens / misc-match-detail
Screen template

Match Detail Screen

Live scoreboard + event feed

pro Expo Go ✓ iosandroid gestures: tap · scroll haptics: light · selection
$ npx lumioui add misc-match-detail
import { MatchDetailScreen } from "@/screens/misc/match-detail";
Registry dependencies6
Native packages installed6
@lumioui/core@latest@lumioui/ui@latestreact-native-safe-area-context@*react-native-reanimated@*react-native-svg@*nativewind@*
misc/match-detail.tsxsource · yours to edit
import * as React from "react";
import { ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withRepeat,
  withTiming,
} from "react-native-reanimated";
import { useReducedMotion } from "@lumioui/core";
import { Avatar, Button, Glass, Icon, Text, type IconName } from "@lumioui/ui";
import { MONO, Mount, Press, World } from "../shared";

export interface MatchEvent {
  id: string;
  minute: string;
  text: string;
  team: "home" | "away";
  /** Drives the timeline glyph — goals get a star, cards a card, subs a swap. */
  kind?: "goal" | "card" | "sub" | "whistle";
}

export interface MatchStat {
  label: string;
  home: number;
  away: number;
}

export interface MatchDetailScreenProps {
  home?: string;
  away?: string;
  homeScore?: string;
  awayScore?: string;
  status?: string;
  league?: string;
  homeAvatarUrl?: string;
  awayAvatarUrl?: string;
  events?: MatchEvent[];
  stats?: MatchStat[];
  lineups?: { home: string[]; away: string[] };
  onBack?: () => void;
}

const DEFAULT_EVENTS: MatchEvent[] = [
  { id: "e0", minute: "1'", text: "Kick-off — we're underway at the Emirates", team: "home", kind: "whistle" },
  { id: "e1", minute: "12'", text: "Goal — De Bruyne assist, Haaland finish", team: "away", kind: "goal" },
  { id: "e2", minute: "34'", text: "Yellow card — Rice", team: "home", kind: "card" },
  { id: "e6", minute: "41'", text: "Big save — Raya palms away Doku's curler", team: "home", kind: "whistle" },
  { id: "e7", minute: "45'", text: "Half-time — City lead by one", team: "away", kind: "whistle" },
  { id: "e3", minute: "58'", text: "Goal — Saka equalises", team: "home", kind: "goal" },
  { id: "e8", minute: "66'", text: "Yellow card — Dias for a cynical block", team: "away", kind: "card" },
  { id: "e4", minute: "74'", text: "Substitution — Jesus on for Trossard", team: "home", kind: "sub" },
  { id: "e9", minute: "76'", text: "Substitution — Kovacic on for Silva", team: "away", kind: "sub" },
];

const DEFAULT_STATS: MatchStat[] = [
  { label: "Possession", home: 46, away: 54 },
  { label: "Shots", home: 9, away: 14 },
  { label: "xG", home: 1.1, away: 1.9 },
  { label: "Shots on target", home: 4, away: 6 },
  { label: "Corners", home: 3, away: 7 },
  { label: "Fouls", home: 11, away: 8 },
];

const DEFAULT_LINEUPS = {
  home: ["Raya", "White", "Saliba", "Gabriel", "Timber", "Ødegaard", "Rice", "Saka", "Havertz", "Martinelli", "Jesus"],
  away: ["Ederson", "Walker", "Dias", "Gvardiol", "Akanji", "Rodri", "De Bruyne", "Foden", "Silva", "Doku", "Haaland"],
};

const KIND_ICON: Record<NonNullable<MatchEvent["kind"]>, IconName> = {
  goal: "star",
  card: "credit-card",
  sub: "repeat",
  whistle: "clock",
};

const TABS = ["events", "stats", "lineups"] as const;
type Tab = (typeof TABS)[number];

/** Pulsing dot for the LIVE badge. */
function LiveDot() {
  const reduce = useReducedMotion();
  const t = useSharedValue(1);
  React.useEffect(() => {
    if (reduce) return;
    t.value = withRepeat(withTiming(0.3, { duration: 900 }), -1, true);
  }, [reduce, t]);
  const style = useAnimatedStyle(() => ({ opacity: t.value }));
  return <Animated.View style={style} className="h-1.5 w-1.5 rounded-full bg-success" />;
}

/** Match detail — scoreboard + Events/Stats/Lineups tabs. */
export function MatchDetailScreen({
  home = "Arsenal",
  away = "Man City",
  homeScore = "1",
  awayScore = "1",
  status = "LIVE · 78'",
  league = "Premier League",
  homeAvatarUrl = "https://upload.wikimedia.org/wikipedia/en/thumb/5/53/Arsenal_FC.svg/120px-Arsenal_FC.svg.png",
  awayAvatarUrl = "https://upload.wikimedia.org/wikipedia/en/thumb/e/eb/Manchester_City_FC_badge.svg/120px-Manchester_City_FC_badge.svg.png",
  events = DEFAULT_EVENTS,
  stats = DEFAULT_STATS,
  lineups = DEFAULT_LINEUPS,
  onBack,
}: MatchDetailScreenProps) {
  const insets = useSafeAreaInsets();
  const [tab, setTab] = React.useState<Tab>("events");

  return (
    <World primary="#65A30D">
    <SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
      <View pointerEvents="box-none" className="absolute inset-x-4 z-10" style={{ top: insets.top + 8 }}>
        <View className="flex-row items-center justify-between">
          <Glass interactive effect="regular" className="h-11 w-11 items-center justify-center rounded-full">
            <Press
              haptic="light"
              onPress={() => onBack?.()}
              accessibilityRole="button"
              accessibilityLabel="Go back"
              className="h-9 w-9 items-center justify-center"
            >
              <Icon name="chevron-left" size="md" color="foreground" />
            </Press>
          </Glass>
          <View className="h-11 w-11" />
        </View>
      </View>

      {/* Scoreboard — the only thing that matters at a glance. */}
      <View className="mx-4 mt-14 overflow-hidden rounded-3xl bg-white p-5 dark:bg-[#1A1A20]">
        <View className="flex-row items-center justify-center gap-1.5">
          <LiveDot />
          <Text className="text-center text-[10px] font-semibold uppercase tracking-wide text-success">
            {status}
          </Text>
        </View>
        <View className="mt-2 flex-row items-center justify-between">
          <View className="w-20 items-center gap-1">
            <Avatar size="lg" source={{ uri: homeAvatarUrl }} fallback={home.slice(0, 2).toUpperCase()} alt={home} />
            <Text className="text-xs font-medium" numberOfLines={1}>
              {home}
            </Text>
          </View>
          <View className="items-center">
            <Text className="text-5xl font-bold tracking-tight" style={{ fontFamily: MONO, fontVariant: ["tabular-nums"] }}>
              {homeScore} – {awayScore}
            </Text>
            <Text className="text-xs text-muted-foreground">{league}</Text>
            {/* Venue — scoreboards situate the game. */}
            <Text className="pt-0.5 text-[10px] text-muted-foreground/70">
              Emirates Stadium · 60,704
            </Text>
          </View>
          <View className="w-20 items-center gap-1">
            <Avatar size="lg" source={{ uri: awayAvatarUrl }} fallback={away.slice(0, 2).toUpperCase()} alt={away} />
            <Text className="text-xs font-medium" numberOfLines={1}>
              {away}
            </Text>
          </View>
        </View>

        {/* Tabs — Events / Stats / Lineups fill the detail half. */}
        <Glass interactive effect="regular" className="mt-4 flex-row self-center rounded-full p-1">
          {TABS.map((t) => (
            <Press
              key={t}
              haptic="light"
              pressedScale={0.96}
              onPress={() => setTab(t)}
              accessibilityRole="button"
              accessibilityLabel={`${t} tab`}
              accessibilityState={{ selected: tab === t }}
              className={`rounded-full px-4 py-1.5 ${tab === t ? "bg-card" : ""}`}
            >
              <Text
                className={`text-xs font-medium capitalize ${
                  tab === t ? "text-foreground" : "text-muted-foreground"
                }`}
              >
                {t}
              </Text>
            </Press>
          ))}
        </Glass>
      </View>

      <ScrollView
        showsVerticalScrollIndicator={false}
        contentContainerStyle={{ paddingBottom: insets.bottom + 24 }}
      >
        {tab === "events" && (
          /* Timeline — spine connects icon discs; minute column left. */
          <View className="relative mx-4 mt-4">
            <View className="absolute bottom-4 left-[39px] top-4 w-[2px] rounded-full bg-border" />
            {events.map((e) => (
              <View key={e.id} className="flex-row items-start gap-3 py-2.5">
                <Text className="w-9 pt-1 text-right text-sm font-semibold text-primary">
                  {e.minute}
                </Text>
                <View
                  className={`z-10 h-6 w-6 items-center justify-center rounded-full ${
                    e.team === "home" ? "bg-primary" : "bg-muted-foreground/40"
                  }`}
                >
                  <Icon
                    name={KIND_ICON[e.kind ?? "whistle"]}
                    size="sm"
                    color="primaryForeground"
                  />
                </View>
                <Text className="flex-1 pt-0.5 text-sm leading-5">{e.text}</Text>
              </View>
            ))}
          </View>
        )}

        {tab === "stats" && (
          <Mount>
            <View className="mx-4 mt-4 gap-4 overflow-hidden rounded-3xl bg-white p-5 dark:bg-[#1A1A20]">
              {stats.map((s) => {
                const total = s.home + s.away || 1;
                return (
                  <View key={s.label} className="gap-1.5">
                    <View className="flex-row items-baseline justify-between">
                      <Text className="w-12 text-sm font-semibold">{s.home}</Text>
                      <Text className="text-xs text-muted-foreground">{s.label}</Text>
                      <Text className="w-12 text-right text-sm font-semibold">{s.away}</Text>
                    </View>
                    <View className="flex-row gap-1">
                      <View className="h-1.5 flex-1 flex-row justify-end rounded-full bg-muted">
                        <View
                          className="h-1.5 rounded-full bg-primary"
                          style={{ width: `${(s.home / total) * 100}%` }}
                        />
                      </View>
                      <View className="h-1.5 flex-1 rounded-full bg-muted">
                        <View
                          className="h-1.5 rounded-full bg-muted-foreground"
                          style={{ width: `${(s.away / total) * 100}%` }}
                        />
                      </View>
                    </View>
                  </View>
                );
              })}
            </View>
          </Mount>
        )}

        {tab === "lineups" && (
          <Mount>
            <View className="mx-4 mt-4 flex-row gap-3">
              {([["home", lineups.home], ["away", lineups.away]] as const).map(([side, names]) => (
                <View key={side} className="flex-1 overflow-hidden rounded-3xl bg-white p-4 dark:bg-[#1A1A20]">
                  <Text className="pb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    {side === "home" ? home : away}
                  </Text>
                  {names.map((n, i) => (
                    <View key={n} className="flex-row items-center gap-2 py-1">
                      <Text className="w-4 text-right text-[11px] text-muted-foreground">
                        {i + 1}
                      </Text>
                      <Text className="flex-1 text-sm" numberOfLines={1}>
                        {n}
                      </Text>
                    </View>
                  ))}
                </View>
              ))}
            </View>
          </Mount>
        )}
      </ScrollView>
    </SafeAreaView>
    </World>
  );
}
MatchDetailScreen.displayName = "MatchDetailScreen";
agent prompt — paste into Claude Code, Cursor, Devin…
Recreate the "Match Detail Screen" screen in React Native using LumioUI.

Goal: Live scoreboard + event feed Match the reference implementation's layout, behavior, and motion exactly — this is a production screen, not a mockup.

## Setup

Fastest path — install the screen plus its component deps:

```bash
npx lumioui add misc-match-detail
```

Or pull just the components: `npx lumioui add avatar button glass icon list text`

Native packages:
- @lumioui/core@latest
- @lumioui/ui@latest
- react-native-safe-area-context@*
- react-native-reanimated@*
- react-native-svg@*
- nativewind@*

Stack assumptions: Expo SDK 54 / RN 0.81, Reanimated 4, NativeWind v4, TypeScript strict, Expo Go compatible.

## Components — how, where, when

### `avatar` — Circular user image with fallback initials and status dot.
- `<Avatar>` ×2

### `button` — Tactile pill button with press spring, ink wash and async loading.
- imported but not rendered directly — likely used through a sibling or as a utility

### `glass` — iOS 26 liquid-glass surface — real glass via expo-glass-effect (Expo Go) or @callstack/liquid-glass (dev builds), SVG refraction + specular rim on web, frosted fallback elsewhere; GlassContainer melts adjacent panes into one blob
- `<Glass>` ×2

### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×2

### `list` — Settings-style rows with separators and press feedback.
- imported but not rendered directly — likely used through a sibling or as a utility

### `text` — Themed type scale from caption to display.
- `<Text>` ×15

## Conventions (non-negotiable)

- Styling via NativeWind `className` with semantic tokens only: `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-secondary`, `bg-muted`, `text-muted-foreground`, `bg-accent`, `bg-destructive`, `border-border`. Hard-coded colors only inside media overlays (`text-white`, `bg-black/N`).
- Motion via the token presets from `@lumioui/core` (`spring.press`, `spring.surface`, `spring.glide`, `timing.fast`, `timing.slow`, …) — no bespoke spring configs or durations.
- Gate transform motion behind `useReducedMotion()`; keep opacity/color feedback, drop travel and overshoot.
- Haptics via `useHaptic()`: `light` on press, `selection` on toggle/select, `warning` on invalid input. Never import expo-haptics directly.
- Press feedback via `usePressAnimation()` (~0.97 scale), not `activeOpacity` tricks.
- a11y: `accessibilityRole` + `accessibilityLabel` on every control, touch targets ≥44pt.
- Entrance choreography: staggered `Mount` wrappers (delay 0/60/120…) so regions land in sequence instead of popping in at once.
- Wrap the screen in `SafeAreaView`; keep dark mode working via the semantic tokens.

## Deliverable

One file: `screens/misc/match-detail.tsx` exporting MatchDetailScreen. Preserve the props interface — the host app wires the callbacks (navigation, submit handlers) through them.

## Reference implementation

The LumioUI source for this screen — reproduce it faithfully:

```tsx
import * as React from "react";
import { ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withRepeat,
  withTiming,
} from "react-native-reanimated";
import { useReducedMotion } from "@lumioui/core";
import { Avatar, Button, Glass, Icon, Text, type IconName } from "@lumioui/ui";
import { MONO, Mount, Press, World } from "../shared";

export interface MatchEvent {
  id: string;
  minute: string;
  text: string;
  team: "home" | "away";
  /** Drives the timeline glyph — goals get a star, cards a card, subs a swap. */
  kind?: "goal" | "card" | "sub" | "whistle";
}

export interface MatchStat {
  label: string;
  home: number;
  away: number;
}

export interface MatchDetailScreenProps {
  home?: string;
  away?: string;
  homeScore?: string;
  awayScore?: string;
  status?: string;
  league?: string;
  homeAvatarUrl?: string;
  awayAvatarUrl?: string;
  events?: MatchEvent[];
  stats?: MatchStat[];
  lineups?: { home: string[]; away: string[] };
  onBack?: () => void;
}

const DEFAULT_EVENTS: MatchEvent[] = [
  { id: "e0", minute: "1'", text: "Kick-off — we're underway at the Emirates", team: "home", kind: "whistle" },
  { id: "e1", minute: "12'", text: "Goal — De Bruyne assist, Haaland finish", team: "away", kind: "goal" },
  { id: "e2", minute: "34'", text: "Yellow card — Rice", team: "home", kind: "card" },
  { id: "e6", minute: "41'", text: "Big save — Raya palms away Doku's curler", team: "home", kind: "whistle" },
  { id: "e7", minute: "45'", text: "Half-time — City lead by one", team: "away", kind: "whistle" },
  { id: "e3", minute: "58'", text: "Goal — Saka equalises", team: "home", kind: "goal" },
  { id: "e8", minute: "66'", text: "Yellow card — Dias for a cynical block", team: "away", kind: "card" },
  { id: "e4", minute: "74'", text: "Substitution — Jesus on for Trossard", team: "home", kind: "sub" },
  { id: "e9", minute: "76'", text: "Substitution — Kovacic on for Silva", team: "away", kind: "sub" },
];

const DEFAULT_STATS: MatchStat[] = [
  { label: "Possession", home: 46, away: 54 },
  { label: "Shots", home: 9, away: 14 },
  { label: "xG", home: 1.1, away: 1.9 },
  { label: "Shots on target", home: 4, away: 6 },
  { label: "Corners", home: 3, away: 7 },
  { label: "Fouls", home: 11, away: 8 },
];

const DEFAULT_LINEUPS = {
  home: ["Raya", "White", "Saliba", "Gabriel", "Timber", "Ødegaard", "Rice", "Saka", "Havertz", "Martinelli", "Jesus"],
  away: ["Ederson", "Walker", "Dias", "Gvardiol", "Akanji", "Rodri", "De Bruyne", "Foden", "Silva", "Doku", "Haaland"],
};

const KIND_ICON: Record<NonNullable<MatchEvent["kind"]>, IconName> = {
  goal: "star",
  card: "credit-card",
  sub: "repeat",
  whistle: "clock",
};

const TABS = ["events", "stats", "lineups"] as const;
type Tab = (typeof TABS)[number];

/** Pulsing dot for the LIVE badge. */
function LiveDot() {
  const reduce = useReducedMotion();
  const t = useSharedValue(1);
  React.useEffect(() => {
    if (reduce) return;
    t.value = withRepeat(withTiming(0.3, { duration: 900 }), -1, true);
  }, [reduce, t]);
  const style = useAnimatedStyle(() => ({ opacity: t.value }));
  return <Animated.View style={style} className="h-1.5 w-1.5 rounded-full bg-success" />;
}

/** Match detail — scoreboard + Events/Stats/Lineups tabs. */
export function MatchDetailScreen({
  home = "Arsenal",
  away = "Man City",
  homeScore = "1",
  awayScore = "1",
  status = "LIVE · 78'",
  league = "Premier League",
  homeAvatarUrl = "https://upload.wikimedia.org/wikipedia/en/thumb/5/53/Arsenal_FC.svg/120px-Arsenal_FC.svg.png",
  awayAvatarUrl = "https://upload.wikimedia.org/wikipedia/en/thumb/e/eb/Manchester_City_FC_badge.svg/120px-Manchester_City_FC_badge.svg.png",
  events = DEFAULT_EVENTS,
  stats = DEFAULT_STATS,
  lineups = DEFAULT_LINEUPS,
  onBack,
}: MatchDetailScreenProps) {
  const insets = useSafeAreaInsets();
  const [tab, setTab] = React.useState<Tab>("events");

  return (
    <World primary="#65A30D">
    <SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
      <View pointerEvents="box-none" className="absolute inset-x-4 z-10" style={{ top: insets.top + 8 }}>
        <View className="flex-row items-center justify-between">
          <Glass interactive effect="regular" className="h-11 w-11 items-center justify-center rounded-full">
            <Press
              haptic="light"
              onPress={() => onBack?.()}
              accessibilityRole="button"
              accessibilityLabel="Go back"
              className="h-9 w-9 items-center justify-center"
            >
              <Icon name="chevron-left" size="md" color="foreground" />
            </Press>
          </Glass>
          <View className="h-11 w-11" />
        </View>
      </View>

      {/* Scoreboard — the only thing that matters at a glance. */}
      <View className="mx-4 mt-14 overflow-hidden rounded-3xl bg-white p-5 dark:bg-[#1A1A20]">
        <View className="flex-row items-center justify-center gap-1.5">
          <LiveDot />
          <Text className="text-center text-[10px] font-semibold uppercase tracking-wide text-success">
            {status}
          </Text>
        </View>
        <View className="mt-2 flex-row items-center justify-between">
          <View className="w-20 items-center gap-1">
            <Avatar size="lg" source={{ uri: homeAvatarUrl }} fallback={home.slice(0, 2).toUpperCase()} alt={home} />
            <Text className="text-xs font-medium" numberOfLines={1}>
              {home}
            </Text>
          </View>
          <View className="items-center">
            <Text className="text-5xl font-bold tracking-tight" style={{ fontFamily: MONO, fontVariant: ["tabular-nums"] }}>
              {homeScore} – {awayScore}
            </Text>
            <Text className="text-xs text-muted-foreground">{league}</Text>
            {/* Venue — scoreboards situate the game. */}
            <Text className="pt-0.5 text-[10px] text-muted-foreground/70">
              Emirates Stadium · 60,704
            </Text>
          </View>
          <View className="w-20 items-center gap-1">
            <Avatar size="lg" source={{ uri: awayAvatarUrl }} fallback={away.slice(0, 2).toUpperCase()} alt={away} />
            <Text className="text-xs font-medium" numberOfLines={1}>
              {away}
            </Text>
          </View>
        </View>

        {/* Tabs — Events / Stats / Lineups fill the detail half. */}
        <Glass interactive effect="regular" className="mt-4 flex-row self-center rounded-full p-1">
          {TABS.map((t) => (
            <Press
              key={t}
              haptic="light"
              pressedScale={0.96}
              onPress={() => setTab(t)}
              accessibilityRole="button"
              accessibilityLabel={`${t} tab`}
              accessibilityState={{ selected: tab === t }}
              className={`rounded-full px-4 py-1.5 ${tab === t ? "bg-card" : ""}`}
            >
              <Text
                className={`text-xs font-medium capitalize ${
                  tab === t ? "text-foreground" : "text-muted-foreground"
                }`}
              >
                {t}
              </Text>
            </Press>
          ))}
        </Glass>
      </View>

      <ScrollView
        showsVerticalScrollIndicator={false}
        contentContainerStyle={{ paddingBottom: insets.bottom + 24 }}
      >
        {tab === "events" && (
          /* Timeline — spine connects icon discs; minute column left. */
          <View className="relative mx-4 mt-4">
            <View className="absolute bottom-4 left-[39px] top-4 w-[2px] rounded-full bg-border" />
            {events.map((e) => (
              <View key={e.id} className="flex-row items-start gap-3 py-2.5">
                <Text className="w-9 pt-1 text-right text-sm font-semibold text-primary">
                  {e.minute}
                </Text>
                <View
                  className={`z-10 h-6 w-6 items-center justify-center rounded-full ${
                    e.team === "home" ? "bg-primary" : "bg-muted-foreground/40"
                  }`}
                >
                  <Icon
                    name={KIND_ICON[e.kind ?? "whistle"]}
                    size="sm"
                    color="primaryForeground"
                  />
                </View>
                <Text className="flex-1 pt-0.5 text-sm leading-5">{e.text}</Text>
              </View>
            ))}
          </View>
        )}

        {tab === "stats" && (
          <Mount>
            <View className="mx-4 mt-4 gap-4 overflow-hidden rounded-3xl bg-white p-5 dark:bg-[#1A1A20]">
              {stats.map((s) => {
                const total = s.home + s.away || 1;
                return (
                  <View key={s.label} className="gap-1.5">
                    <View className="flex-row items-baseline justify-between">
                      <Text className="w-12 text-sm font-semibold">{s.home}</Text>
                      <Text className="text-xs text-muted-foreground">{s.label}</Text>
                      <Text className="w-12 text-right text-sm font-semibold">{s.away}</Text>
                    </View>
                    <View className="flex-row gap-1">
                      <View className="h-1.5 flex-1 flex-row justify-end rounded-full bg-muted">
                        <View
                          className="h-1.5 rounded-full bg-primary"
                          style={{ width: `${(s.home / total) * 100}%` }}
                        />
                      </View>
                      <View className="h-1.5 flex-1 rounded-full bg-muted">
                        <View
                          className="h-1.5 rounded-full bg-muted-foreground"
                          style={{ width: `${(s.away / total) * 100}%` }}
                        />
                      </View>
                    </View>
                  </View>
                );
              })}
            </View>
          </Mount>
        )}

        {tab === "lineups" && (
          <Mount>
            <View className="mx-4 mt-4 flex-row gap-3">
              {([["home", lineups.home], ["away", lineups.away]] as const).map(([side, names]) => (
                <View key={side} className="flex-1 overflow-hidden rounded-3xl bg-white p-4 dark:bg-[#1A1A20]">
                  <Text className="pb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    {side === "home" ? home : away}
                  </Text>
                  {names.map((n, i) => (
                    <View key={n} className="flex-row items-center gap-2 py-1">
                      <Text className="w-4 text-right text-[11px] text-muted-foreground">
                        {i + 1}
                      </Text>
                      <Text className="flex-1 text-sm" numberOfLines={1}>
                        {n}
                      </Text>
                    </View>
                  ))}
                </View>
              ))}
            </View>
          </Mount>
        )}
      </ScrollView>
    </SafeAreaView>
    </World>
  );
}
MatchDetailScreen.displayName = "MatchDetailScreen";
```

Screen: https://lumioui.dev/screens/misc-match-detail
iPhone 17 · iOS
9:41
Live preview — the real screen via Expo Web · open full ↗