Med Store screen.
import { useState } from "react";
import { Image, ScrollView, View } from "react-native";
import Svg, { Path } from "react-native-svg";
import { SafeAreaView } from "react-native-safe-area-context";
import { Avatar, Button, Glass, Icon, Text, type IconName } from "@lumioui/ui";
import { cn, useHaptic } from "@lumioui/core";
import { Mount, Press, ROW_STAGGER, ROW_STAGGER_CAP } from "../shared";
export interface MedStoreScreenProps {
onNext?: () => void;
}
const ACTIONS: { icon: IconName; label: string }[] = [
{ icon: "camera", label: "Upload" },
{ icon: "package", label: "Order" },
{ icon: "pin", label: "Pharmacy" },
{ icon: "clock", label: "Reminder" },
];
const MEDS: { name: string; price: string; tint: string; img: string }[] = [
{
name: "Women Multivitamin",
price: "$29.00",
tint: "#BFE9F1",
img: "https://images.unsplash.com/photo-1587854692152-cbe660dbde88?w=700&q=80",
},
{
name: "Calcium Core",
price: "$24.50",
tint: "#F6C4D5",
img: "https://images.unsplash.com/photo-1471864190281-a93a3070b6de?w=700&q=80",
},
{
name: "Omega-3",
price: "$19.99",
tint: "#FFE0B8",
img: "https://images.unsplash.com/photo-1584308666744-24d5c474f2ae?w=700&q=80",
},
];
function ActionPill({ icon, label, index }: { icon: IconName; label: string; index: number }) {
const haptics = useHaptic();
return (
<Mount delay={140 + index * ROW_STAGGER} className="w-[48%]">
<Press onPress={() => haptics.light()} className="w-full rounded-full bg-white p-1.5">
<View className="flex-row items-center gap-3">
<View className="h-10 w-10 items-center justify-center rounded-full bg-[#F4F5F9]">
<Icon name={icon} size="md" color="#1C1C1E" />
</View>
<Text className="text-[15px] font-medium text-[#1C1C1E]">{label}</Text>
</View>
</Press>
</Mount>
);
}
function MedCard({ med, index, onAdd }: { med: (typeof MEDS)[number]; index: number; onAdd: () => void }) {
const haptics = useHaptic();
const [faved, setFaved] = useState(false);
return (
<Mount delay={220 + index * 90} className="mr-3 w-[310px]">
<View className="overflow-hidden rounded-[30px] bg-white pb-5">
<View className="mx-4 mt-4 h-56 overflow-hidden rounded-[22px]" style={{ backgroundColor: med.tint }}>
<Image source={{ uri: med.img }} className="absolute inset-0" resizeMode="cover" accessibilityIgnoresInvertColors />
<Press
accessibilityRole="button"
accessibilityLabel="Favorite"
onPress={() => {
haptics.light();
setFaved((f) => !f);
}}
className="absolute right-3 top-3 h-9 w-9 items-center justify-center rounded-full bg-white"
>
<Icon name="heart" size="md" color={faved ? "#E2503B" : "#1C1C1E"} />
</Press>
</View>
<View className="flex-row items-end justify-between px-5 pt-4">
<View className="min-w-0 flex-1">
<Text className="text-[16px] font-semibold text-[#1C1C1E]" numberOfLines={1}>{med.name}</Text>
<Text className="mt-1 text-[22px] font-bold text-[#1C1C1E]">{med.price}</Text>
</View>
<Press
accessibilityRole="button"
accessibilityLabel="Add to cart"
onPress={() => {
haptics.light();
onAdd();
}}
className="h-11 w-11 items-center justify-center rounded-full bg-[#1B1B26]"
>
<Icon name="plus" size="md" color="#BEF04C" />
</Press>
</View>
</View>
</Mount>
);
}
export function MedStoreScreen({ onNext }: MedStoreScreenProps) {
const haptics = useHaptic();
const [cart, setCart] = useState(0);
const complete = () => {
haptics.success();
onNext?.();
};
return (
<View className="flex-1 bg-[#F4F5F9]">
<SafeAreaView className="flex-1" edges={["top"]}>
<Mount className="flex-row items-center gap-3 px-6 pt-2" delay={40}>
<Avatar size="lg" fallback="MJ" source={{ uri: "https://i.pravatar.cc/150?img=11" }} />
<View className="flex-1">
<Text className="text-[15px] font-semibold text-[#1C1C1E]">Michael Johnson</Text>
<View className="flex-row items-center gap-1">
<Icon name="pin" size="sm" color="#8E8E93" />
<Text className="text-[12px] text-[#8E8E93]">New York, NY</Text>
</View>
</View>
<Press accessibilityRole="button" accessibilityLabel="Cart" onPress={() => haptics.light()} className="h-11 w-11 items-center justify-center rounded-full bg-white">
<Icon name="package" size="md" color="#1C1C1E" />
{cart > 0 ? (
<View className="absolute -right-0.5 -top-0.5 h-5 min-w-5 items-center justify-center rounded-full bg-[#BEF04C] px-1">
<Text className="text-[10px] font-bold text-[#1C1C1E]">{cart}</Text>
</View>
) : (
<View className="absolute right-2.5 top-2.5 h-2 w-2 rounded-full bg-[#BEF04C]" />
)}
</Press>
</Mount>
<ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="pb-44">
<Mount delay={80} className="mt-6 flex-row items-center justify-between px-6">
<Text className="text-[30px] font-bold leading-[38px] text-[#1C1C1E]">
{"Find Your\nMedicines Fast"}
</Text>
<Press
accessibilityRole="button" accessibilityLabel="Search medicines"
onPress={complete}
className="h-14 w-14 items-center justify-center rounded-full bg-[#1C1C1E]"
>
<Icon name="search" size="lg" color="#FFFFFF" />
</Press>
</Mount>
<View className="mt-6 flex-row flex-wrap justify-between gap-y-3 px-6">
{ACTIONS.map((a, i) => (
<ActionPill key={a.label} icon={a.icon} label={a.label} index={i} />
))}
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
className="mt-7"
contentContainerClassName="pl-6 pr-8"
>
{MEDS.map((m, i) => (
<MedCard key={m.name} med={m} index={i} onAdd={() => setCart((c) => c + 1)} />
))}
</ScrollView>
</ScrollView>
<View className="absolute inset-x-5 bottom-8">
<Press
accessibilityRole="button"
accessibilityLabel="Scan"
onPress={() => haptics.light()}
className="absolute -top-7 left-1/2 z-10 h-[60px] w-[60px] -translate-x-1/2 items-center justify-center rounded-full bg-[#1C1C1E]"
>
<Svg width={22} height={22} viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" strokeWidth={2} strokeLinecap="round">
<Path d="M4 8V6a2 2 0 0 1 2-2h2" />
<Path d="M16 4h2a2 2 0 0 1 2 2v2" />
<Path d="M20 16v2a2 2 0 0 1-2 2h-2" />
<Path d="M8 20H6a2 2 0 0 1-2-2v-2" />
<Path d="M5 12h14" />
</Svg>
</Press>
<Glass effect="regular" className="flex-row items-center justify-around rounded-full px-2 py-3">
<NavItem icon="home" label="Home" active />
<NavItem icon="list" label="Orders" />
<View className="w-14" />
<NavItem icon="sparkles" label="AI Chat" />
<NavItem icon="user" label="Profile" />
</Glass>
</View>
</SafeAreaView>
</View>
);
}
function NavItem({ icon, label, active }: { icon: IconName; label: string; active?: boolean }) {
const haptics = useHaptic();
return (
<Press onPress={() => haptics.light()} className="items-center gap-0.5 px-1">
<Icon name={icon} size="md" color={active ? "#1C1C1E" : "#9C9CA6"} />
<Text className={active ? "text-[11px] font-semibold text-[#1C1C1E]" : "text-[11px] text-[#9C9CA6]"}>
{label}
</Text>
</Press>
);
}
Recreate the "Med Store" screen in React Native using LumioUI.
Goal: Med Store 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 health-med-store
```
Or pull just the components: `npx lumioui add button`
Native packages:
- @lumioui/ui@latest
- @lumioui/core@latest
- react-native-svg@*
- react-native-safe-area-context@*
- react-native-reanimated@*
- 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
## 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/health/med-store.tsx` exporting MedStoreScreen. 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 { useState } from "react";
import { Image, ScrollView, View } from "react-native";
import Svg, { Path } from "react-native-svg";
import { SafeAreaView } from "react-native-safe-area-context";
import { Avatar, Button, Glass, Icon, Text, type IconName } from "@lumioui/ui";
import { cn, useHaptic } from "@lumioui/core";
import { Mount, Press, ROW_STAGGER, ROW_STAGGER_CAP } from "../shared";
export interface MedStoreScreenProps {
onNext?: () => void;
}
const ACTIONS: { icon: IconName; label: string }[] = [
{ icon: "camera", label: "Upload" },
{ icon: "package", label: "Order" },
{ icon: "pin", label: "Pharmacy" },
{ icon: "clock", label: "Reminder" },
];
const MEDS: { name: string; price: string; tint: string; img: string }[] = [
{
name: "Women Multivitamin",
price: "$29.00",
tint: "#BFE9F1",
img: "https://images.unsplash.com/photo-1587854692152-cbe660dbde88?w=700&q=80",
},
{
name: "Calcium Core",
price: "$24.50",
tint: "#F6C4D5",
img: "https://images.unsplash.com/photo-1471864190281-a93a3070b6de?w=700&q=80",
},
{
name: "Omega-3",
price: "$19.99",
tint: "#FFE0B8",
img: "https://images.unsplash.com/photo-1584308666744-24d5c474f2ae?w=700&q=80",
},
];
function ActionPill({ icon, label, index }: { icon: IconName; label: string; index: number }) {
const haptics = useHaptic();
return (
<Mount delay={140 + index * ROW_STAGGER} className="w-[48%]">
<Press onPress={() => haptics.light()} className="w-full rounded-full bg-white p-1.5">
<View className="flex-row items-center gap-3">
<View className="h-10 w-10 items-center justify-center rounded-full bg-[#F4F5F9]">
<Icon name={icon} size="md" color="#1C1C1E" />
</View>
<Text className="text-[15px] font-medium text-[#1C1C1E]">{label}</Text>
</View>
</Press>
</Mount>
);
}
function MedCard({ med, index, onAdd }: { med: (typeof MEDS)[number]; index: number; onAdd: () => void }) {
const haptics = useHaptic();
const [faved, setFaved] = useState(false);
return (
<Mount delay={220 + index * 90} className="mr-3 w-[310px]">
<View className="overflow-hidden rounded-[30px] bg-white pb-5">
<View className="mx-4 mt-4 h-56 overflow-hidden rounded-[22px]" style={{ backgroundColor: med.tint }}>
<Image source={{ uri: med.img }} className="absolute inset-0" resizeMode="cover" accessibilityIgnoresInvertColors />
<Press
accessibilityRole="button"
accessibilityLabel="Favorite"
onPress={() => {
haptics.light();
setFaved((f) => !f);
}}
className="absolute right-3 top-3 h-9 w-9 items-center justify-center rounded-full bg-white"
>
<Icon name="heart" size="md" color={faved ? "#E2503B" : "#1C1C1E"} />
</Press>
</View>
<View className="flex-row items-end justify-between px-5 pt-4">
<View className="min-w-0 flex-1">
<Text className="text-[16px] font-semibold text-[#1C1C1E]" numberOfLines={1}>{med.name}</Text>
<Text className="mt-1 text-[22px] font-bold text-[#1C1C1E]">{med.price}</Text>
</View>
<Press
accessibilityRole="button"
accessibilityLabel="Add to cart"
onPress={() => {
haptics.light();
onAdd();
}}
className="h-11 w-11 items-center justify-center rounded-full bg-[#1B1B26]"
>
<Icon name="plus" size="md" color="#BEF04C" />
</Press>
</View>
</View>
</Mount>
);
}
export function MedStoreScreen({ onNext }: MedStoreScreenProps) {
const haptics = useHaptic();
const [cart, setCart] = useState(0);
const complete = () => {
haptics.success();
onNext?.();
};
return (
<View className="flex-1 bg-[#F4F5F9]">
<SafeAreaView className="flex-1" edges={["top"]}>
<Mount className="flex-row items-center gap-3 px-6 pt-2" delay={40}>
<Avatar size="lg" fallback="MJ" source={{ uri: "https://i.pravatar.cc/150?img=11" }} />
<View className="flex-1">
<Text className="text-[15px] font-semibold text-[#1C1C1E]">Michael Johnson</Text>
<View className="flex-row items-center gap-1">
<Icon name="pin" size="sm" color="#8E8E93" />
<Text className="text-[12px] text-[#8E8E93]">New York, NY</Text>
</View>
</View>
<Press accessibilityRole="button" accessibilityLabel="Cart" onPress={() => haptics.light()} className="h-11 w-11 items-center justify-center rounded-full bg-white">
<Icon name="package" size="md" color="#1C1C1E" />
{cart > 0 ? (
<View className="absolute -right-0.5 -top-0.5 h-5 min-w-5 items-center justify-center rounded-full bg-[#BEF04C] px-1">
<Text className="text-[10px] font-bold text-[#1C1C1E]">{cart}</Text>
</View>
) : (
<View className="absolute right-2.5 top-2.5 h-2 w-2 rounded-full bg-[#BEF04C]" />
)}
</Press>
</Mount>
<ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="pb-44">
<Mount delay={80} className="mt-6 flex-row items-center justify-between px-6">
<Text className="text-[30px] font-bold leading-[38px] text-[#1C1C1E]">
{"Find Your\nMedicines Fast"}
</Text>
<Press
accessibilityRole="button" accessibilityLabel="Search medicines"
onPress={complete}
className="h-14 w-14 items-center justify-center rounded-full bg-[#1C1C1E]"
>
<Icon name="search" size="lg" color="#FFFFFF" />
</Press>
</Mount>
<View className="mt-6 flex-row flex-wrap justify-between gap-y-3 px-6">
{ACTIONS.map((a, i) => (
<ActionPill key={a.label} icon={a.icon} label={a.label} index={i} />
))}
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
className="mt-7"
contentContainerClassName="pl-6 pr-8"
>
{MEDS.map((m, i) => (
<MedCard key={m.name} med={m} index={i} onAdd={() => setCart((c) => c + 1)} />
))}
</ScrollView>
</ScrollView>
<View className="absolute inset-x-5 bottom-8">
<Press
accessibilityRole="button"
accessibilityLabel="Scan"
onPress={() => haptics.light()}
className="absolute -top-7 left-1/2 z-10 h-[60px] w-[60px] -translate-x-1/2 items-center justify-center rounded-full bg-[#1C1C1E]"
>
<Svg width={22} height={22} viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" strokeWidth={2} strokeLinecap="round">
<Path d="M4 8V6a2 2 0 0 1 2-2h2" />
<Path d="M16 4h2a2 2 0 0 1 2 2v2" />
<Path d="M20 16v2a2 2 0 0 1-2 2h-2" />
<Path d="M8 20H6a2 2 0 0 1-2-2v-2" />
<Path d="M5 12h14" />
</Svg>
</Press>
<Glass effect="regular" className="flex-row items-center justify-around rounded-full px-2 py-3">
<NavItem icon="home" label="Home" active />
<NavItem icon="list" label="Orders" />
<View className="w-14" />
<NavItem icon="sparkles" label="AI Chat" />
<NavItem icon="user" label="Profile" />
</Glass>
</View>
</SafeAreaView>
</View>
);
}
function NavItem({ icon, label, active }: { icon: IconName; label: string; active?: boolean }) {
const haptics = useHaptic();
return (
<Press onPress={() => haptics.light()} className="items-center gap-0.5 px-1">
<Icon name={icon} size="md" color={active ? "#1C1C1E" : "#9C9CA6"} />
<Text className={active ? "text-[11px] font-semibold text-[#1C1C1E]" : "text-[11px] text-[#9C9CA6]"}>
{label}
</Text>
</Press>
);
}
```
Screen: https://lumioui.dev/screens/health-med-store