Active/Past switch over order cards — live orders carry pulsing status badge, stage dots, and track action; delivered rows get a reorder button
import * as React from "react";
import { useState } from "react";
import { FlatList, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Badge, Button, Card, EmptyState, Glass, Icon, Image, SegmentedControl, Separator, Steps, Text } from "@lumioui/ui";
import { LumioMark, Mount, Press, ROW_STAGGER, ROW_STAGGER_CAP, World } from "../shared";
export type FoodOrderStatus = "preparing" | "on-the-way" | "delivered" | "cancelled";
export interface FoodOrder {
id: string;
restaurant: string;
restaurantImageUrl?: string;
/** "Today, 6:12 PM" — when it was placed. */
placedAt: string;
/** Items line — "2× Margherita Verace, 1× Tiramisù". */
itemsSummary: string;
itemCount: number;
total: number;
status: FoodOrderStatus;
/** Live orders only — "ETA 6:42 PM". */
eta?: string;
}
export interface FoodOrdersScreenProps {
orders?: FoodOrder[];
currency?: string;
onBack?: () => void;
onOrderPress?: (order: FoodOrder) => void;
onTrackOrder?: (order: FoodOrder) => void;
onReorder?: (order: FoodOrder) => void;
onBrowse?: () => void;
}
/** Pinned Unsplash food photography — each id visually verified to show
* the named food. */
const FOOD_PHOTOS = {
pizza: "photo-1513104890138-7c749659a591",
sushi: "photo-1579871494447-9811cf80d66c",
burger: "photo-1568901346375-23c9450c58cd",
tacos: "photo-1551504734-5ee1c4a1479b",
bowl: "photo-1546069901-ba9599a7e63c",
} as const;
const img = (p: keyof typeof FOOD_PHOTOS) =>
`https://images.unsplash.com/${FOOD_PHOTOS[p]}?w=200&h=200&fit=crop&q=70&auto=format`;
const fmt = (n: number, currency = "$") =>
`${currency}${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const DEFAULT_ORDERS: FoodOrder[] = [
{
id: "NR-4821",
restaurant: "Nonna Rosa",
restaurantImageUrl: img("pizza"),
placedAt: "Today, 6:12 PM",
itemsSummary: "Margherita Verace, 2× Spicy Salmon Roll, Tiramisù",
itemCount: 4,
total: 46.0,
status: "on-the-way",
eta: "ETA 6:42 PM",
},
{
id: "CP-3390",
restaurant: "Casa Poke",
restaurantImageUrl: img("bowl"),
placedAt: "Today, 6:20 PM",
itemsSummary: "Spicy Tuna Bowl, Edamame, Yuzu Lemonade",
itemCount: 3,
total: 21.8,
status: "preparing",
eta: "ETA 7:05 PM",
},
{
id: "SK-1207",
restaurant: "Sakura Handroll Bar",
restaurantImageUrl: img("sushi"),
placedAt: "Yesterday, 1:30 PM",
itemsSummary: "Salmon Nigiri Set, Miso Soup",
itemCount: 2,
total: 24.5,
status: "delivered",
},
{
id: "SS-0964",
restaurant: "Smoky Stack",
restaurantImageUrl: img("burger"),
placedAt: "Sun, 7:45 PM",
itemsSummary: "Double Smash & Fries, Vanilla Shake",
itemCount: 2,
total: 19.9,
status: "delivered",
},
{
id: "EC-0441",
restaurant: "El Camino",
restaurantImageUrl: img("tacos"),
placedAt: "Sep 18, 8:20 PM",
itemsSummary: "Al Pastor Trio, Chips & Guac",
itemCount: 4,
total: 22.4,
status: "cancelled",
},
];
const STATUS_LABEL: Record<FoodOrderStatus, string> = {
preparing: "Preparing",
"on-the-way": "On the way",
delivered: "Delivered",
cancelled: "Cancelled",
};
const STATUS_VARIANT: Record<FoodOrderStatus, "success" | "neutral" | "destructive"> = {
preparing: "success",
"on-the-way": "success",
delivered: "neutral",
cancelled: "destructive",
};
const LIVE_STAGES = ["Confirmed", "Preparing", "On the way", "Delivered"];
const isLive = (status: FoodOrderStatus) => status === "preparing" || status === "on-the-way";
/** One order card — restaurant thumb, status badge, items, stage dots for
* live orders, track/reorder action. */
function OrderCard({
order,
currency,
onOrderPress,
onTrackOrder,
onReorder,
}: {
order: FoodOrder;
currency: string;
onOrderPress?: (order: FoodOrder) => void;
onTrackOrder?: (order: FoodOrder) => void;
onReorder?: (order: FoodOrder) => void;
}) {
const live = isLive(order.status);
return (
<Card
variant="elevated"
onPress={() => onOrderPress?.(order)}
accessibilityLabel={`Order ${order.id} from ${order.restaurant}, ${STATUS_LABEL[order.status]}, ${fmt(order.total, currency)}`}
className="rounded-3xl bg-white dark:bg-[#1A1A20]"
>
<Card.Content className="gap-3 p-4">
<View className="flex-row items-center gap-3">
<Image
source={order.restaurantImageUrl ? { uri: order.restaurantImageUrl } : undefined}
rounded="xl"
className="h-11 w-11"
accessibilityLabel={`${order.restaurant} logo`}
/>
<View className="flex-1">
<Text variant="small" className="font-semibold" numberOfLines={1}>
{order.restaurant}
</Text>
<Text variant="muted" className="text-xs">
{order.id} · {order.placedAt}
</Text>
</View>
<Badge
variant={STATUS_VARIANT[order.status]}
pulse={live}
accessibilityLabel={`Status: ${STATUS_LABEL[order.status]}`}
>
{STATUS_LABEL[order.status]}
</Badge>
</View>
<Text variant="muted" className="text-xs" numberOfLines={1}>
{order.itemCount} items — {order.itemsSummary}
</Text>
{live ? (
<Steps
steps={LIVE_STAGES}
current={order.status === "preparing" ? 1 : 2}
variant="dots"
labels={false}
interactive={false}
haptic={false}
accessibilityLabel={`Order progress: ${STATUS_LABEL[order.status]}`}
/>
) : null}
<Separator />
<View className="flex-row items-center justify-between">
<Text variant="small" className="font-semibold tabular-nums">
{fmt(order.total, currency)}
{order.eta ? (
<Text variant="muted" className="text-xs font-normal">
{" "}· {order.eta}
</Text>
) : null}
</Text>
{live && onTrackOrder ? (
<Button
variant="secondary"
size="sm"
haptic="light"
onPress={() => onTrackOrder(order)}
accessibilityLabel={`Track order ${order.id}`}
icon={<Icon name="map-pin" size="sm" color="#65A30D" />}
>
Track order
</Button>
) : null}
{!live && onReorder ? (
<Button
variant="secondary"
size="sm"
haptic="light"
onPress={() => onReorder(order)}
accessibilityLabel={`Reorder from ${order.restaurant}`}
icon={<Icon name="repeat" size="sm" color="secondaryForeground" />}
>
Reorder
</Button>
) : null}
</View>
</Card.Content>
</Card>
);
}
OrderCard.displayName = "OrderCard";
/** FoodOrders — Active/Past switch over order cards: live orders carry a
* pulsing status badge, stage dots, and a track action; past orders get
* a reorder button. */
export function FoodOrdersScreen({
orders = DEFAULT_ORDERS,
currency = "$",
onBack,
onOrderPress,
onTrackOrder,
onReorder,
onBrowse,
}: FoodOrdersScreenProps) {
const insets = useSafeAreaInsets();
const [tab, setTab] = useState("Active");
const activeOrders = orders.filter((o) => isLive(o.status));
const pastOrders = orders.filter((o) => !isLive(o.status));
const visible = tab === "Active" ? activeOrders : pastOrders;
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 pointerEvents="none" className="absolute inset-0 items-center justify-center">
<View className="h-11 flex-row items-center justify-center gap-2 rounded-full pl-3.5 pr-5">
<Glass effect="regular" className="absolute inset-0 rounded-full" />
<LumioMark size={18} />
<Text accessibilityRole="header" className="text-sm font-bold tracking-tight">
Orders
</Text>
</View>
</View>
</View>
<View className="px-4 pb-3" style={{ paddingTop: insets.top + 60 }}>
<SegmentedControl
options={["Active", "Past"]}
value={tab}
onValueChange={setTab}
accessibilityLabel="Order history filter"
/>
{/* Live-count caption — "2 on the way" reads better than a bare tab. */}
<Text variant="muted" className="pt-2 text-xs">
{activeOrders.length} on the way · {pastOrders.length} past
</Text>
</View>
<FlatList
data={visible}
keyExtractor={(order) => order.id}
contentContainerClassName="gap-4 px-6 pt-1"
contentContainerStyle={{ paddingBottom: insets.bottom + 32 }}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<EmptyState
variant={tab === "Active" ? "default" : "search"}
title={tab === "Active" ? "No orders on the way" : "No past orders"}
description={
tab === "Active"
? "When you place an order it shows up here live."
: "Delivered and cancelled orders land here."
}
action={
tab === "Active" && onBrowse ? (
<Button variant="secondary" onPress={onBrowse}>
Find food
</Button>
) : undefined
}
className="pt-24"
/>
}
renderItem={({ item, index }) => (
<Mount delay={Math.min(index * ROW_STAGGER, ROW_STAGGER_CAP)}>
<OrderCard
order={item}
currency={currency}
onOrderPress={onOrderPress}
onTrackOrder={onTrackOrder}
onReorder={onReorder}
/>
</Mount>
)}
/>
</SafeAreaView>
</World>
);
}
FoodOrdersScreen.displayName = "FoodOrdersScreen";
Recreate the "Food Orders Screen" screen in React Native using LumioUI.
Goal: Active/Past switch over order cards — live orders carry pulsing status badge, stage dots, and track action; delivered rows get a reorder button 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-food-orders
```
Or pull just the components: `npx lumioui add badge button card empty-state icon image segmented-control separator steps text`
Native packages:
- @lumioui/ui@latest
- react-native-safe-area-context@*
- @lumioui/core@latest
- 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
### `badge` — Compact status pill with variants, icons and pulse option.
- `<Badge>` ×1
### `button` — Tactile pill button with press spring, ink wash and async loading.
- `<Button>` ×3
### `card` — Compound surface — header, title, content and footer slots.
- `<Card>` ×1
### `empty-state` — Illustrated placeholder with title, copy and action slot.
- `<EmptyState>` ×1
### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×3
### `image` — Cached image with placeholder, fallback and aspect handling.
- `<Image>` ×1
### `segmented-control` — iOS 26 segmented picker — glass capsule track with a gliding liquid indicator pill, spring motion + haptics
- `<SegmentedControl>` ×1
### `separator` — Hairline rule with orientation and spacing control.
- `<Separator>` ×1
### `steps` — Horizontal step indicator with numbered or dot markers, check-on-complete pop, and connectors that fill as progress advances
- `<Steps>` ×1
### `text` — Themed type scale from caption to display.
- `<Text>` ×7
## 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/food-orders.tsx` exporting FoodOrdersScreen. 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 { FlatList, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Badge, Button, Card, EmptyState, Glass, Icon, Image, SegmentedControl, Separator, Steps, Text } from "@lumioui/ui";
import { LumioMark, Mount, Press, ROW_STAGGER, ROW_STAGGER_CAP, World } from "../shared";
export type FoodOrderStatus = "preparing" | "on-the-way" | "delivered" | "cancelled";
export interface FoodOrder {
id: string;
restaurant: string;
restaurantImageUrl?: string;
/** "Today, 6:12 PM" — when it was placed. */
placedAt: string;
/** Items line — "2× Margherita Verace, 1× Tiramisù". */
itemsSummary: string;
itemCount: number;
total: number;
status: FoodOrderStatus;
/** Live orders only — "ETA 6:42 PM". */
eta?: string;
}
export interface FoodOrdersScreenProps {
orders?: FoodOrder[];
currency?: string;
onBack?: () => void;
onOrderPress?: (order: FoodOrder) => void;
onTrackOrder?: (order: FoodOrder) => void;
onReorder?: (order: FoodOrder) => void;
onBrowse?: () => void;
}
/** Pinned Unsplash food photography — each id visually verified to show
* the named food. */
const FOOD_PHOTOS = {
pizza: "photo-1513104890138-7c749659a591",
sushi: "photo-1579871494447-9811cf80d66c",
burger: "photo-1568901346375-23c9450c58cd",
tacos: "photo-1551504734-5ee1c4a1479b",
bowl: "photo-1546069901-ba9599a7e63c",
} as const;
const img = (p: keyof typeof FOOD_PHOTOS) =>
`https://images.unsplash.com/${FOOD_PHOTOS[p]}?w=200&h=200&fit=crop&q=70&auto=format`;
const fmt = (n: number, currency = "$") =>
`${currency}${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const DEFAULT_ORDERS: FoodOrder[] = [
{
id: "NR-4821",
restaurant: "Nonna Rosa",
restaurantImageUrl: img("pizza"),
placedAt: "Today, 6:12 PM",
itemsSummary: "Margherita Verace, 2× Spicy Salmon Roll, Tiramisù",
itemCount: 4,
total: 46.0,
status: "on-the-way",
eta: "ETA 6:42 PM",
},
{
id: "CP-3390",
restaurant: "Casa Poke",
restaurantImageUrl: img("bowl"),
placedAt: "Today, 6:20 PM",
itemsSummary: "Spicy Tuna Bowl, Edamame, Yuzu Lemonade",
itemCount: 3,
total: 21.8,
status: "preparing",
eta: "ETA 7:05 PM",
},
{
id: "SK-1207",
restaurant: "Sakura Handroll Bar",
restaurantImageUrl: img("sushi"),
placedAt: "Yesterday, 1:30 PM",
itemsSummary: "Salmon Nigiri Set, Miso Soup",
itemCount: 2,
total: 24.5,
status: "delivered",
},
{
id: "SS-0964",
restaurant: "Smoky Stack",
restaurantImageUrl: img("burger"),
placedAt: "Sun, 7:45 PM",
itemsSummary: "Double Smash & Fries, Vanilla Shake",
itemCount: 2,
total: 19.9,
status: "delivered",
},
{
id: "EC-0441",
restaurant: "El Camino",
restaurantImageUrl: img("tacos"),
placedAt: "Sep 18, 8:20 PM",
itemsSummary: "Al Pastor Trio, Chips & Guac",
itemCount: 4,
total: 22.4,
status: "cancelled",
},
];
const STATUS_LABEL: Record<FoodOrderStatus, string> = {
preparing: "Preparing",
"on-the-way": "On the way",
delivered: "Delivered",
cancelled: "Cancelled",
};
const STATUS_VARIANT: Record<FoodOrderStatus, "success" | "neutral" | "destructive"> = {
preparing: "success",
"on-the-way": "success",
delivered: "neutral",
cancelled: "destructive",
};
const LIVE_STAGES = ["Confirmed", "Preparing", "On the way", "Delivered"];
const isLive = (status: FoodOrderStatus) => status === "preparing" || status === "on-the-way";
/** One order card — restaurant thumb, status badge, items, stage dots for
* live orders, track/reorder action. */
function OrderCard({
order,
currency,
onOrderPress,
onTrackOrder,
onReorder,
}: {
order: FoodOrder;
currency: string;
onOrderPress?: (order: FoodOrder) => void;
onTrackOrder?: (order: FoodOrder) => void;
onReorder?: (order: FoodOrder) => void;
}) {
const live = isLive(order.status);
return (
<Card
variant="elevated"
onPress={() => onOrderPress?.(order)}
accessibilityLabel={`Order ${order.id} from ${order.restaurant}, ${STATUS_LABEL[order.status]}, ${fmt(order.total, currency)}`}
className="rounded-3xl bg-white dark:bg-[#1A1A20]"
>
<Card.Content className="gap-3 p-4">
<View className="flex-row items-center gap-3">
<Image
source={order.restaurantImageUrl ? { uri: order.restaurantImageUrl } : undefined}
rounded="xl"
className="h-11 w-11"
accessibilityLabel={`${order.restaurant} logo`}
/>
<View className="flex-1">
<Text variant="small" className="font-semibold" numberOfLines={1}>
{order.restaurant}
</Text>
<Text variant="muted" className="text-xs">
{order.id} · {order.placedAt}
</Text>
</View>
<Badge
variant={STATUS_VARIANT[order.status]}
pulse={live}
accessibilityLabel={`Status: ${STATUS_LABEL[order.status]}`}
>
{STATUS_LABEL[order.status]}
</Badge>
</View>
<Text variant="muted" className="text-xs" numberOfLines={1}>
{order.itemCount} items — {order.itemsSummary}
</Text>
{live ? (
<Steps
steps={LIVE_STAGES}
current={order.status === "preparing" ? 1 : 2}
variant="dots"
labels={false}
interactive={false}
haptic={false}
accessibilityLabel={`Order progress: ${STATUS_LABEL[order.status]}`}
/>
) : null}
<Separator />
<View className="flex-row items-center justify-between">
<Text variant="small" className="font-semibold tabular-nums">
{fmt(order.total, currency)}
{order.eta ? (
<Text variant="muted" className="text-xs font-normal">
{" "}· {order.eta}
</Text>
) : null}
</Text>
{live && onTrackOrder ? (
<Button
variant="secondary"
size="sm"
haptic="light"
onPress={() => onTrackOrder(order)}
accessibilityLabel={`Track order ${order.id}`}
icon={<Icon name="map-pin" size="sm" color="#65A30D" />}
>
Track order
</Button>
) : null}
{!live && onReorder ? (
<Button
variant="secondary"
size="sm"
haptic="light"
onPress={() => onReorder(order)}
accessibilityLabel={`Reorder from ${order.restaurant}`}
icon={<Icon name="repeat" size="sm" color="secondaryForeground" />}
>
Reorder
</Button>
) : null}
</View>
</Card.Content>
</Card>
);
}
OrderCard.displayName = "OrderCard";
/** FoodOrders — Active/Past switch over order cards: live orders carry a
* pulsing status badge, stage dots, and a track action; past orders get
* a reorder button. */
export function FoodOrdersScreen({
orders = DEFAULT_ORDERS,
currency = "$",
onBack,
onOrderPress,
onTrackOrder,
onReorder,
onBrowse,
}: FoodOrdersScreenProps) {
const insets = useSafeAreaInsets();
const [tab, setTab] = useState("Active");
const activeOrders = orders.filter((o) => isLive(o.status));
const pastOrders = orders.filter((o) => !isLive(o.status));
const visible = tab === "Active" ? activeOrders : pastOrders;
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 pointerEvents="none" className="absolute inset-0 items-center justify-center">
<View className="h-11 flex-row items-center justify-center gap-2 rounded-full pl-3.5 pr-5">
<Glass effect="regular" className="absolute inset-0 rounded-full" />
<LumioMark size={18} />
<Text accessibilityRole="header" className="text-sm font-bold tracking-tight">
Orders
</Text>
</View>
</View>
</View>
<View className="px-4 pb-3" style={{ paddingTop: insets.top + 60 }}>
<SegmentedControl
options={["Active", "Past"]}
value={tab}
onValueChange={setTab}
accessibilityLabel="Order history filter"
/>
{/* Live-count caption — "2 on the way" reads better than a bare tab. */}
<Text variant="muted" className="pt-2 text-xs">
{activeOrders.length} on the way · {pastOrders.length} past
</Text>
</View>
<FlatList
data={visible}
keyExtractor={(order) => order.id}
contentContainerClassName="gap-4 px-6 pt-1"
contentContainerStyle={{ paddingBottom: insets.bottom + 32 }}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<EmptyState
variant={tab === "Active" ? "default" : "search"}
title={tab === "Active" ? "No orders on the way" : "No past orders"}
description={
tab === "Active"
? "When you place an order it shows up here live."
: "Delivered and cancelled orders land here."
}
action={
tab === "Active" && onBrowse ? (
<Button variant="secondary" onPress={onBrowse}>
Find food
</Button>
) : undefined
}
className="pt-24"
/>
}
renderItem={({ item, index }) => (
<Mount delay={Math.min(index * ROW_STAGGER, ROW_STAGGER_CAP)}>
<OrderCard
order={item}
currency={currency}
onOrderPress={onOrderPress}
onTrackOrder={onTrackOrder}
onReorder={onReorder}
/>
</Mount>
)}
/>
</SafeAreaView>
</World>
);
}
FoodOrdersScreen.displayName = "FoodOrdersScreen";
```
Screen: https://lumioui.dev/screens/food-food-orders