Dark premium restaurant: moody photo hero, gold accents, add-to-cart discs, cart bar
import * as React from "react";
import { useState } from "react";
import { Pressable, ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Button, Glass, Icon, Image, Text, type IconName } from "@lumioui/ui";
import { useHaptic } from "@lumioui/core";
import { GlowSpot, Mount, SERIF, StoryScrim } from "../shared";
export interface DishItem {
id: string;
name: string;
desc: string;
price: number;
photoUrl: string;
}
export interface RestaurantDarkScreenProps {
name?: string;
cuisine?: string;
rating?: number;
deliveryTime?: string;
heroPhotoUrl?: string;
menu?: DishItem[];
onBack?: () => void;
onCheckout?: (itemCount: number) => void;
}
const GOLD = "#D4A853";
const food = (id: string) =>
`https://images.unsplash.com/${id}?w=400&h=400&fit=crop&q=70&auto=format`;
const MENU: DishItem[] = [
{ id: "1", name: "Tagliatelle al Ragù", desc: "Slow-braised beef · fresh pasta", price: 24, photoUrl: food("photo-1473093295043-cdd812d0e601") },
{ id: "2", name: "Cacio e Pepe", desc: "Pecorino · cracked pepper", price: 21, photoUrl: food("photo-1621996346565-e3dbc646d9a9") },
{ id: "3", name: "Burrata", desc: "Heirloom tomato · basil oil", price: 16, photoUrl: food("photo-1546069901-ba9599a7e63c") },
];
/** Restaurant — dark premium idiom (Pasteria reference): moody photo hero,
* gold accent, serif-leaning type, dish rows with plus-discs, and a gold
* "View cart · n" bar that materializes once items are added. */
export function RestaurantDarkScreen({
name = "Pasteria",
cuisine = "Italian Pasta",
rating = 4.9,
deliveryTime = "25–35 min",
heroPhotoUrl = "https://images.unsplash.com/photo-1552566626-52f8b828add9?w=800&h=600&fit=crop&q=75&auto=format",
menu = MENU,
onBack,
onCheckout,
}: RestaurantDarkScreenProps) {
const haptics = useHaptic();
const insets = useSafeAreaInsets();
const [cart, setCart] = useState<Record<string, number>>({});
const [saved, setSaved] = useState(false);
const count = Object.values(cart).reduce((a, b) => a + b, 0);
const total = menu.reduce((sum, m) => sum + (cart[m.id] ?? 0) * m.price, 0);
const add = (id: string) => {
haptics.selection();
setCart((c) => ({ ...c, [id]: (c[id] ?? 0) + 1 }));
};
return (
<SafeAreaView className="flex-1 bg-neutral-950" edges={["top"]}>
<GlowSpot color="#D4A853" size={300} className="-top-20 right-[-80px]" />
<GlowSpot color="#D4A853" size={240} className="bottom-[30%] left-[-110px]" />
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: insets.bottom + 96 }}
>
<Mount className="relative">
<Image
source={{ uri: heroPhotoUrl }}
className="h-64 w-full bg-neutral-900"
accessibilityLabel={`${name} dish photography`}
/>
<StoryScrim edge="bottom" />
<Glass interactive effect="clear" className="absolute left-4 top-3 rounded-full">
<Pressable
onPress={() => {
haptics.light();
onBack?.();
}}
accessibilityLabel="Go back"
accessibilityRole="button"
className="h-10 w-10 items-center justify-center rounded-full"
>
<Icon name="chevron-left" size="md" color="#fff" />
</Pressable>
</Glass>
<Glass interactive effect="clear" className="absolute right-4 top-3 rounded-full">
<Pressable
onPress={() => {
haptics.selection();
setSaved((s) => !s);
}}
accessibilityLabel={saved ? "Remove from favorites" : "Save to favorites"}
accessibilityRole="button"
className="h-10 w-10 items-center justify-center rounded-full"
>
<Icon name="heart" size="md" color={saved ? GOLD : "#fff"} filled={saved} />
</Pressable>
</Glass>
{/* Wordmark over the scrim — gold, letter-spaced. */}
<View className="absolute bottom-4 left-6">
<Text className="text-3xl font-light tracking-widest text-white" style={{ fontFamily: SERIF }}>{name}</Text>
<View className="mt-1 h-0.5 w-12 rounded-full" style={{ backgroundColor: GOLD }} />
<Text className="mt-1 text-xs uppercase tracking-widest text-white/70">
{cuisine}
</Text>
</View>
</Mount>
{/* Meta strip — rating, time, delivery. */}
<Mount delay={60} className="flex-row items-center gap-5 px-6 py-4">
{[
{ icon: "star" as IconName, label: `${rating} (2.4k)` },
{ icon: "clock" as IconName, label: deliveryTime },
{ icon: "truck" as IconName, label: "Free delivery" },
].map((m) => (
<View key={m.label} className="flex-row items-center gap-1.5">
<Icon name={m.icon} size="sm" color={GOLD} />
<Text className="text-xs text-white/80">{m.label}</Text>
</View>
))}
</Mount>
{/* Chef's note — premium menus voice the kitchen, not just prices. */}
<Mount delay={90} className="px-6 pt-1">
<Text className="text-xs italic text-white/40">
"Everything is rolled by hand at 4 PM — nothing sits overnight."
</Text>
</Mount>
{/* Menu — dark rows, photo thumbs, gold plus-discs. */}
<Text className="mt-6 px-6 text-xs font-semibold uppercase tracking-widest text-white/50">
Mains
</Text>
<Mount delay={120} className="px-6">
{menu.map((item) => {
const n = cart[item.id] ?? 0;
return (
<View key={item.id} className="flex-row items-center gap-3 py-4">
<Image
source={{ uri: item.photoUrl }}
className="h-16 w-16 rounded-2xl bg-neutral-900"
accessibilityLabel={item.name}
/>
<View className="flex-1">
<Text className="font-medium text-white">{item.name}</Text>
<Text className="mt-0.5 text-xs text-white/50">{item.desc}</Text>
<Text className="mt-1 font-semibold" style={{ color: GOLD }}>
${item.price}
</Text>
</View>
<View className="flex-row items-center gap-2">
{n > 0 ? <Text className="text-sm font-semibold text-white">{n}</Text> : null}
<Pressable
onPress={() => add(item.id)}
accessibilityLabel={`Add ${item.name}`}
accessibilityRole="button"
className="h-9 w-9 items-center justify-center rounded-full"
style={{ backgroundColor: GOLD }}
>
<Icon name="plus" size="sm" color="#000" />
</Pressable>
</View>
</View>
);
})}
</Mount>
</ScrollView>
{/* Cart bar — appears once items are in. */}
{count > 0 ? (
<Mount delay={180} className="absolute bottom-0 left-0 right-0 px-4" style={{ paddingBottom: insets.bottom + 12 }}>
<Button
size="lg"
haptic="medium"
onPress={() => {
haptics.medium();
onCheckout?.(count);
}}
accessibilityLabel={`View cart, ${count} items, ${total} dollars`}
className="w-full rounded-full bg-[#D4A853]"
textClassName="text-black font-semibold"
>
{`View cart · ${count} · $${total}`}
</Button>
</Mount>
) : null}
</SafeAreaView>
);
}
RestaurantDarkScreen.displayName = "RestaurantDarkScreen";
Recreate the "Restaurant — Dark Screen" screen in React Native using LumioUI.
Goal: Dark premium restaurant: moody photo hero, gold accents, add-to-cart discs, cart bar 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 food-restaurant-dark
```
Or pull just the components: `npx lumioui add button glass icon image text`
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.
- `<Button>` ×1
### `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>` ×4
### `image` — Cached image with placeholder, fallback and aspect handling.
- `<Image>` ×2
### `text` — Themed type scale from caption to display.
- `<Text>` ×9
## 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/food/restaurant-dark.tsx` exporting RestaurantDarkScreen. 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 { useState } from "react";
import { Pressable, ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Button, Glass, Icon, Image, Text, type IconName } from "@lumioui/ui";
import { useHaptic } from "@lumioui/core";
import { GlowSpot, Mount, SERIF, StoryScrim } from "../shared";
export interface DishItem {
id: string;
name: string;
desc: string;
price: number;
photoUrl: string;
}
export interface RestaurantDarkScreenProps {
name?: string;
cuisine?: string;
rating?: number;
deliveryTime?: string;
heroPhotoUrl?: string;
menu?: DishItem[];
onBack?: () => void;
onCheckout?: (itemCount: number) => void;
}
const GOLD = "#D4A853";
const food = (id: string) =>
`https://images.unsplash.com/${id}?w=400&h=400&fit=crop&q=70&auto=format`;
const MENU: DishItem[] = [
{ id: "1", name: "Tagliatelle al Ragù", desc: "Slow-braised beef · fresh pasta", price: 24, photoUrl: food("photo-1473093295043-cdd812d0e601") },
{ id: "2", name: "Cacio e Pepe", desc: "Pecorino · cracked pepper", price: 21, photoUrl: food("photo-1621996346565-e3dbc646d9a9") },
{ id: "3", name: "Burrata", desc: "Heirloom tomato · basil oil", price: 16, photoUrl: food("photo-1546069901-ba9599a7e63c") },
];
/** Restaurant — dark premium idiom (Pasteria reference): moody photo hero,
* gold accent, serif-leaning type, dish rows with plus-discs, and a gold
* "View cart · n" bar that materializes once items are added. */
export function RestaurantDarkScreen({
name = "Pasteria",
cuisine = "Italian Pasta",
rating = 4.9,
deliveryTime = "25–35 min",
heroPhotoUrl = "https://images.unsplash.com/photo-1552566626-52f8b828add9?w=800&h=600&fit=crop&q=75&auto=format",
menu = MENU,
onBack,
onCheckout,
}: RestaurantDarkScreenProps) {
const haptics = useHaptic();
const insets = useSafeAreaInsets();
const [cart, setCart] = useState<Record<string, number>>({});
const [saved, setSaved] = useState(false);
const count = Object.values(cart).reduce((a, b) => a + b, 0);
const total = menu.reduce((sum, m) => sum + (cart[m.id] ?? 0) * m.price, 0);
const add = (id: string) => {
haptics.selection();
setCart((c) => ({ ...c, [id]: (c[id] ?? 0) + 1 }));
};
return (
<SafeAreaView className="flex-1 bg-neutral-950" edges={["top"]}>
<GlowSpot color="#D4A853" size={300} className="-top-20 right-[-80px]" />
<GlowSpot color="#D4A853" size={240} className="bottom-[30%] left-[-110px]" />
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: insets.bottom + 96 }}
>
<Mount className="relative">
<Image
source={{ uri: heroPhotoUrl }}
className="h-64 w-full bg-neutral-900"
accessibilityLabel={`${name} dish photography`}
/>
<StoryScrim edge="bottom" />
<Glass interactive effect="clear" className="absolute left-4 top-3 rounded-full">
<Pressable
onPress={() => {
haptics.light();
onBack?.();
}}
accessibilityLabel="Go back"
accessibilityRole="button"
className="h-10 w-10 items-center justify-center rounded-full"
>
<Icon name="chevron-left" size="md" color="#fff" />
</Pressable>
</Glass>
<Glass interactive effect="clear" className="absolute right-4 top-3 rounded-full">
<Pressable
onPress={() => {
haptics.selection();
setSaved((s) => !s);
}}
accessibilityLabel={saved ? "Remove from favorites" : "Save to favorites"}
accessibilityRole="button"
className="h-10 w-10 items-center justify-center rounded-full"
>
<Icon name="heart" size="md" color={saved ? GOLD : "#fff"} filled={saved} />
</Pressable>
</Glass>
{/* Wordmark over the scrim — gold, letter-spaced. */}
<View className="absolute bottom-4 left-6">
<Text className="text-3xl font-light tracking-widest text-white" style={{ fontFamily: SERIF }}>{name}</Text>
<View className="mt-1 h-0.5 w-12 rounded-full" style={{ backgroundColor: GOLD }} />
<Text className="mt-1 text-xs uppercase tracking-widest text-white/70">
{cuisine}
</Text>
</View>
</Mount>
{/* Meta strip — rating, time, delivery. */}
<Mount delay={60} className="flex-row items-center gap-5 px-6 py-4">
{[
{ icon: "star" as IconName, label: `${rating} (2.4k)` },
{ icon: "clock" as IconName, label: deliveryTime },
{ icon: "truck" as IconName, label: "Free delivery" },
].map((m) => (
<View key={m.label} className="flex-row items-center gap-1.5">
<Icon name={m.icon} size="sm" color={GOLD} />
<Text className="text-xs text-white/80">{m.label}</Text>
</View>
))}
</Mount>
{/* Chef's note — premium menus voice the kitchen, not just prices. */}
<Mount delay={90} className="px-6 pt-1">
<Text className="text-xs italic text-white/40">
"Everything is rolled by hand at 4 PM — nothing sits overnight."
</Text>
</Mount>
{/* Menu — dark rows, photo thumbs, gold plus-discs. */}
<Text className="mt-6 px-6 text-xs font-semibold uppercase tracking-widest text-white/50">
Mains
</Text>
<Mount delay={120} className="px-6">
{menu.map((item) => {
const n = cart[item.id] ?? 0;
return (
<View key={item.id} className="flex-row items-center gap-3 py-4">
<Image
source={{ uri: item.photoUrl }}
className="h-16 w-16 rounded-2xl bg-neutral-900"
accessibilityLabel={item.name}
/>
<View className="flex-1">
<Text className="font-medium text-white">{item.name}</Text>
<Text className="mt-0.5 text-xs text-white/50">{item.desc}</Text>
<Text className="mt-1 font-semibold" style={{ color: GOLD }}>
${item.price}
</Text>
</View>
<View className="flex-row items-center gap-2">
{n > 0 ? <Text className="text-sm font-semibold text-white">{n}</Text> : null}
<Pressable
onPress={() => add(item.id)}
accessibilityLabel={`Add ${item.name}`}
accessibilityRole="button"
className="h-9 w-9 items-center justify-center rounded-full"
style={{ backgroundColor: GOLD }}
>
<Icon name="plus" size="sm" color="#000" />
</Pressable>
</View>
</View>
);
})}
</Mount>
</ScrollView>
{/* Cart bar — appears once items are in. */}
{count > 0 ? (
<Mount delay={180} className="absolute bottom-0 left-0 right-0 px-4" style={{ paddingBottom: insets.bottom + 12 }}>
<Button
size="lg"
haptic="medium"
onPress={() => {
haptics.medium();
onCheckout?.(count);
}}
accessibilityLabel={`View cart, ${count} items, ${total} dollars`}
className="w-full rounded-full bg-[#D4A853]"
textClassName="text-black font-semibold"
>
{`View cart · ${count} · $${total}`}
</Button>
</Mount>
) : null}
</SafeAreaView>
);
}
RestaurantDarkScreen.displayName = "RestaurantDarkScreen";
```
Screen: https://lumioui.dev/screens/food-restaurant-dark