v0.1 public beta early access on GitHub
Home / Screens / media-game-store
Screen template

Ugame Ustore Screen

Ugame Ustore screen

pro Expo Go ✓ iosandroid gestures: tap haptics: light · selection
$ npx lumioui add media-game-store
import { GameStoreScreen } from "@/screens/media/game-store";
Registry dependencies4
Native packages installed6
@lumioui/ui@latest@lumioui/core@latestreact-native-safe-area-context@*react-native-reanimated@*react-native-svg@*nativewind@*
media/game-store.tsxsource · yours to edit
import * as React from "react";
import { Image, ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Avatar, Icon, type IconName, Text } from "@lumioui/ui";
import { cn, useHaptic } from "@lumioui/core";
import { catalogImg, HeroGradient, KenBurns, Mount, Press } from "../shared";

const NEW_RELEASES = [
  { title: "Horizon Forbidden West", price: "$39.99" },
  { title: "Red Dead Redemption", price: "$49.99" },
  { title: "Ghost of Tsushima", price: "$59.99" },
];

const POPULAR = [
  { title: "God of War Ragnarök", price: "$39.99", tag: "Action · Adventure" },
  { title: "Elden Ring", price: "$59.99", tag: "RPG · Open world" },
];

/** Floating dark tab capsule under everything — same idiom as the wallet
 *  movers nav so the two screens read as siblings. */
function GameNav() {
  const insets = useSafeAreaInsets();
  const items: { icon: IconName; label: string; active?: boolean }[] = [
    { icon: "home", label: "Home", active: true },
    { icon: "search", label: "Browse" },
    { icon: "book-open", label: "Library" },
    { icon: "user", label: "Profile" },
  ];
  return (
    <View
      className="absolute inset-x-5 flex-row items-center rounded-full bg-[#141418] px-2 py-2"
      style={{ bottom: Math.max(insets.bottom, 10) }}
    >
      {items.map((it) => (
        <Press
          key={it.label}
          accessibilityRole="button"
          accessibilityLabel={it.label}
          className={cn(
            "h-11 flex-1 flex-row items-center justify-center gap-1.5 rounded-full",
            it.active && "bg-[#3A3A3C]",
          )}
        >
          <Icon name={it.icon} size="sm" color={it.active ? "#FFFFFF" : "#8E8E93"} />
          <Text className={cn("text-[12.5px]", it.active ? "font-semibold text-white" : "text-[#8E8E93]")}>
            {it.label}
          </Text>
        </Press>
      ))}
    </View>
  );
}

/** Game store — dark catalog with a full-bleed key-art hero (chrome floats
 *  on the art), then the scrollable catalog: New Releases poster rail and
 *  Popular rows, dark pill nav floating at the bottom. */
export function GameStoreScreen({ onNotifications, onProfile }: { onNotifications?: () => void; onProfile?: () => void }) {
  const haptics = useHaptic();
  return (
    <View className="flex-1 bg-[#0B0B0F]">
      <ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="pb-32">
        {/* key-art hero — image underlaps the chrome */}
        <View className="relative h-[430px] overflow-hidden">
          <KenBurns>
            <Image
              source={{ uri: catalogImg("game", 900, 1100) }}
              className="absolute inset-0"
              resizeMode="cover"
              accessibilityIgnoresInvertColors
            />
          </KenBurns>
          {/* bottom melt into the dark catalog */}
          <View className="absolute inset-x-0 bottom-0 h-56">
            <HeroGradient from="rgba(11,11,15,0)" to="#0B0B0F" />
          </View>

          <SafeAreaView edges={["top"]} className="flex-1">
            <View className="flex-row items-center justify-between px-4 pt-1">
              <Press onPress={onProfile} accessibilityRole="button" accessibilityLabel="Profile">
                <Avatar fallback="JD" size="md" source={{ uri: "https://i.pravatar.cc/150?img=12" }} />
              </Press>
              <Press
                onPress={onNotifications}
                accessibilityRole="button"
                accessibilityLabel="Notifications"
                className="h-10 w-10 items-center justify-center rounded-full bg-black/40"
              >
                <Icon name="bell" size="sm" color="#FFFFFF" />
              </Press>
            </View>

            <View className="flex-1" />
            <Mount className="px-5 pb-5">
              <Text className="text-center text-[34px] font-bold tracking-tight text-white">
                The Last of Us
              </Text>
              <Text className="mt-1 text-center text-[13px] text-white/70">
                A story of survival and hope
              </Text>
              <View className="mt-2.5 flex-row items-center justify-center gap-1.5">
                <Text className="text-[12px] text-white/60">Reviews by</Text>
                <View className="flex-row">
                  {[32, 47, 12].map((n, i) => (
                    <Avatar
                      key={n}
                      fallback="·"
                      size="sm"
                      source={{ uri: `https://i.pravatar.cc/64?img=${n}` }}
                      className={cn("border-2 border-[#0B0B0F]", i > 0 && "-ml-2")}
                    />
                  ))}
                </View>
              </View>
              <View className="mt-4 flex-row items-center justify-center gap-3">
                <Press
                  onPress={() => haptics.light()}
                  accessibilityRole="button"
                  className="rounded-full bg-white px-6 py-3"
                >
                  <Text className="text-[14px] font-semibold text-[#0B0B0F]">View game ›</Text>
                </Press>
                <Press
                  onPress={() => haptics.selection()}
                  accessibilityRole="button"
                  accessibilityLabel="Add to wishlist"
                  className="flex-row items-center gap-1.5"
                >
                  <Text className="text-[14px] font-medium text-white/85">Add to wishlist</Text>
                  <Icon name="bookmark" size="sm" color="#FFFFFF" />
                </Press>
              </View>
            </Mount>
          </SafeAreaView>
        </View>

        {/* catalog */}
        <Mount delay={60} className="mt-2">
          <View className="flex-row items-center justify-between px-5">
            <Text className="text-[17px] font-bold text-white">New Releases</Text>
            <Text className="text-[13px] text-white/45">See all</Text>
          </View>
          <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerClassName="mt-3.5 gap-4 px-5">
            {NEW_RELEASES.map((g, i) => (
              <Press key={g.title} accessibilityRole="button" className="w-40">
                <Image
                  source={{ uri: catalogImg(i === 1 ? "controller" : "game", 360, 240) }}
                  className="h-24 w-40 rounded-2xl bg-white/10"
                  resizeMode="cover"
                />
                <Text numberOfLines={1} className="mt-2 text-[14px] font-medium text-white">
                  {g.title}
                </Text>
                <Text className="mt-0.5 text-[13px] font-semibold text-white/60">{g.price}</Text>
              </Press>
            ))}
          </ScrollView>
        </Mount>

        <Mount delay={110} className="mt-6 px-5">
          <View className="flex-row items-center justify-between">
            <Text className="text-[17px] font-bold text-white">Popular now</Text>
            <Text className="text-[13px] text-white/45">See all</Text>
          </View>
          {POPULAR.map((g) => (
            <Press
              key={g.title}
              accessibilityRole="button"
              className="mt-3.5 flex-row items-center rounded-3xl bg-white/[0.07] p-3.5"
            >
              <Image
                source={{ uri: catalogImg("game", 200, 200) }}
                className="h-14 w-14 rounded-2xl bg-white/10"
                resizeMode="cover"
              />
              <View className="ml-3.5 flex-1">
                <Text className="text-[15px] font-semibold text-white">{g.title}</Text>
                <Text className="mt-0.5 text-[12px] text-white/45">{g.tag}</Text>
              </View>
              <Text className="text-[15px] font-bold text-white">{g.price}</Text>
            </Press>
          ))}
        </Mount>
      </ScrollView>
      <GameNav />
    </View>
  );
}
GameStoreScreen.displayName = "GameStoreScreen";
agent prompt — paste into Claude Code, Cursor, Devin…
Recreate the "Ugame Ustore Screen" screen in React Native using LumioUI.

Goal: Ugame Ustore screen 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 media-game-store
```

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

Native packages:
- @lumioui/ui@latest
- @lumioui/core@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

### `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

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

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

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

## 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/media/game-store.tsx` exporting GameStoreScreen. 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 { Image, ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Avatar, Icon, type IconName, Text } from "@lumioui/ui";
import { cn, useHaptic } from "@lumioui/core";
import { catalogImg, HeroGradient, KenBurns, Mount, Press } from "../shared";

const NEW_RELEASES = [
  { title: "Horizon Forbidden West", price: "$39.99" },
  { title: "Red Dead Redemption", price: "$49.99" },
  { title: "Ghost of Tsushima", price: "$59.99" },
];

const POPULAR = [
  { title: "God of War Ragnarök", price: "$39.99", tag: "Action · Adventure" },
  { title: "Elden Ring", price: "$59.99", tag: "RPG · Open world" },
];

/** Floating dark tab capsule under everything — same idiom as the wallet
 *  movers nav so the two screens read as siblings. */
function GameNav() {
  const insets = useSafeAreaInsets();
  const items: { icon: IconName; label: string; active?: boolean }[] = [
    { icon: "home", label: "Home", active: true },
    { icon: "search", label: "Browse" },
    { icon: "book-open", label: "Library" },
    { icon: "user", label: "Profile" },
  ];
  return (
    <View
      className="absolute inset-x-5 flex-row items-center rounded-full bg-[#141418] px-2 py-2"
      style={{ bottom: Math.max(insets.bottom, 10) }}
    >
      {items.map((it) => (
        <Press
          key={it.label}
          accessibilityRole="button"
          accessibilityLabel={it.label}
          className={cn(
            "h-11 flex-1 flex-row items-center justify-center gap-1.5 rounded-full",
            it.active && "bg-[#3A3A3C]",
          )}
        >
          <Icon name={it.icon} size="sm" color={it.active ? "#FFFFFF" : "#8E8E93"} />
          <Text className={cn("text-[12.5px]", it.active ? "font-semibold text-white" : "text-[#8E8E93]")}>
            {it.label}
          </Text>
        </Press>
      ))}
    </View>
  );
}

/** Game store — dark catalog with a full-bleed key-art hero (chrome floats
 *  on the art), then the scrollable catalog: New Releases poster rail and
 *  Popular rows, dark pill nav floating at the bottom. */
export function GameStoreScreen({ onNotifications, onProfile }: { onNotifications?: () => void; onProfile?: () => void }) {
  const haptics = useHaptic();
  return (
    <View className="flex-1 bg-[#0B0B0F]">
      <ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="pb-32">
        {/* key-art hero — image underlaps the chrome */}
        <View className="relative h-[430px] overflow-hidden">
          <KenBurns>
            <Image
              source={{ uri: catalogImg("game", 900, 1100) }}
              className="absolute inset-0"
              resizeMode="cover"
              accessibilityIgnoresInvertColors
            />
          </KenBurns>
          {/* bottom melt into the dark catalog */}
          <View className="absolute inset-x-0 bottom-0 h-56">
            <HeroGradient from="rgba(11,11,15,0)" to="#0B0B0F" />
          </View>

          <SafeAreaView edges={["top"]} className="flex-1">
            <View className="flex-row items-center justify-between px-4 pt-1">
              <Press onPress={onProfile} accessibilityRole="button" accessibilityLabel="Profile">
                <Avatar fallback="JD" size="md" source={{ uri: "https://i.pravatar.cc/150?img=12" }} />
              </Press>
              <Press
                onPress={onNotifications}
                accessibilityRole="button"
                accessibilityLabel="Notifications"
                className="h-10 w-10 items-center justify-center rounded-full bg-black/40"
              >
                <Icon name="bell" size="sm" color="#FFFFFF" />
              </Press>
            </View>

            <View className="flex-1" />
            <Mount className="px-5 pb-5">
              <Text className="text-center text-[34px] font-bold tracking-tight text-white">
                The Last of Us
              </Text>
              <Text className="mt-1 text-center text-[13px] text-white/70">
                A story of survival and hope
              </Text>
              <View className="mt-2.5 flex-row items-center justify-center gap-1.5">
                <Text className="text-[12px] text-white/60">Reviews by</Text>
                <View className="flex-row">
                  {[32, 47, 12].map((n, i) => (
                    <Avatar
                      key={n}
                      fallback="·"
                      size="sm"
                      source={{ uri: `https://i.pravatar.cc/64?img=${n}` }}
                      className={cn("border-2 border-[#0B0B0F]", i > 0 && "-ml-2")}
                    />
                  ))}
                </View>
              </View>
              <View className="mt-4 flex-row items-center justify-center gap-3">
                <Press
                  onPress={() => haptics.light()}
                  accessibilityRole="button"
                  className="rounded-full bg-white px-6 py-3"
                >
                  <Text className="text-[14px] font-semibold text-[#0B0B0F]">View game ›</Text>
                </Press>
                <Press
                  onPress={() => haptics.selection()}
                  accessibilityRole="button"
                  accessibilityLabel="Add to wishlist"
                  className="flex-row items-center gap-1.5"
                >
                  <Text className="text-[14px] font-medium text-white/85">Add to wishlist</Text>
                  <Icon name="bookmark" size="sm" color="#FFFFFF" />
                </Press>
              </View>
            </Mount>
          </SafeAreaView>
        </View>

        {/* catalog */}
        <Mount delay={60} className="mt-2">
          <View className="flex-row items-center justify-between px-5">
            <Text className="text-[17px] font-bold text-white">New Releases</Text>
            <Text className="text-[13px] text-white/45">See all</Text>
          </View>
          <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerClassName="mt-3.5 gap-4 px-5">
            {NEW_RELEASES.map((g, i) => (
              <Press key={g.title} accessibilityRole="button" className="w-40">
                <Image
                  source={{ uri: catalogImg(i === 1 ? "controller" : "game", 360, 240) }}
                  className="h-24 w-40 rounded-2xl bg-white/10"
                  resizeMode="cover"
                />
                <Text numberOfLines={1} className="mt-2 text-[14px] font-medium text-white">
                  {g.title}
                </Text>
                <Text className="mt-0.5 text-[13px] font-semibold text-white/60">{g.price}</Text>
              </Press>
            ))}
          </ScrollView>
        </Mount>

        <Mount delay={110} className="mt-6 px-5">
          <View className="flex-row items-center justify-between">
            <Text className="text-[17px] font-bold text-white">Popular now</Text>
            <Text className="text-[13px] text-white/45">See all</Text>
          </View>
          {POPULAR.map((g) => (
            <Press
              key={g.title}
              accessibilityRole="button"
              className="mt-3.5 flex-row items-center rounded-3xl bg-white/[0.07] p-3.5"
            >
              <Image
                source={{ uri: catalogImg("game", 200, 200) }}
                className="h-14 w-14 rounded-2xl bg-white/10"
                resizeMode="cover"
              />
              <View className="ml-3.5 flex-1">
                <Text className="text-[15px] font-semibold text-white">{g.title}</Text>
                <Text className="mt-0.5 text-[12px] text-white/45">{g.tag}</Text>
              </View>
              <Text className="text-[15px] font-bold text-white">{g.price}</Text>
            </Press>
          ))}
        </Mount>
      </ScrollView>
      <GameNav />
    </View>
  );
}
GameStoreScreen.displayName = "GameStoreScreen";
```

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