Provider-first sign-in — member avatar proof, Apple and Google buttons with brand marks, email fallback, and legal footer
import * as React from "react";
import { View, ScrollView } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AvatarGroup, Button, Divider, Icon, Text } from "@lumioui/ui";
import { useHaptic, useTheme } from "@lumioui/core";
import { AppleLogo, GoogleLogo, Mount, Press, World } from "../shared";
export interface SocialAuthScreenProps {
onProviderAuth?: (provider: "apple" | "google" | "email") => void;
onSignIn?: () => void;
onTerms?: () => void;
onPrivacy?: () => void;
}
const face = (n: number) => `https://i.pravatar.cc/150?img=${n}`;
const MEMBERS = [
{ id: "pn", source: { uri: face(47) }, fallback: "PN", alt: "Priya Nair" },
{ id: "jf", source: { uri: face(12) }, fallback: "JF", alt: "Jonah Fields" },
{ id: "mv", source: { uri: face(32) }, fallback: "MV", alt: "Marta Vidal" },
{ id: "so", source: { uri: face(53) }, fallback: "SO", alt: "Sam Okafor" },
{ id: "lr", source: { uri: face(25) }, fallback: "LR", alt: "Lena Roth" },
];
/** Social auth — provider-first sign-in: member proof row, Apple and Google
* buttons with real brand marks, email fallback, legal footer. */
export function SocialAuthScreen({ onProviderAuth, onSignIn, onTerms, onPrivacy }: SocialAuthScreenProps) {
const haptics = useHaptic();
const insets = useSafeAreaInsets();
const { colors } = useTheme();
const pick = (provider: "apple" | "google" | "email") => {
onProviderAuth?.(provider);
};
return (
<World primary="#65A30D">
<View className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]">
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerClassName="flex-grow justify-center px-6"
contentContainerStyle={{ paddingTop: insets.top + 24, paddingBottom: insets.bottom + 24 }}
>
{/* Member proof — real faces + a rating row beat any hero
illustration */}
<Mount delay={0}>
<View className="items-center gap-4 self-center overflow-hidden rounded-3xl bg-white px-8 py-6 dark:bg-[#1A1A20]">
<AvatarGroup items={MEMBERS} max={5} />
<View className="items-center gap-1.5">
<Text variant="muted" className="text-sm">
Joined by 2.4M members
</Text>
<View className="flex-row items-center gap-1">
{[0, 1, 2, 3, 4].map((s) => (
<Icon key={s} name="star" size="sm" color="#EAB308" />
))}
<Text className="pl-1 text-xs font-medium text-muted-foreground">
4.9 · App Store
</Text>
</View>
</View>
</View>
</Mount>
<Mount delay={100}>
<View className="mt-8 items-center gap-3">
<Text variant="h1" className="text-center tracking-tight">
Create your <Text className="italic text-[#65A30D]">account</Text>
</Text>
<Text variant="muted" className="max-w-xs text-center text-base leading-relaxed">
Pick a provider — you'll be set up in under a minute.
</Text>
</View>
</Mount>
{/* Providers — brand glyphs carry the meaning, no extra decoration */}
<View className="mt-9 gap-3">
<Mount delay={180}>
<Button
size="lg"
className="bg-[#65A30D] w-full"
icon={<AppleLogo color={colors.primaryForeground} />}
onPress={() => pick("apple")}
haptic="medium"
accessibilityLabel="Continue with Apple"
>
Continue with Apple
</Button>
</Mount>
<Mount delay={240}>
<Button
variant="glass"
size="lg"
className="w-full"
icon={<GoogleLogo />}
onPress={() => pick("google")}
haptic="medium"
accessibilityLabel="Continue with Google"
>
Continue with Google
</Button>
</Mount>
</View>
<Mount delay={300} className="mt-7">
<Divider
label="or"
labelClassName="text-xs uppercase tracking-widest"
/>
</Mount>
<Mount delay={360}>
<View className="mt-6">
<Button
variant="glass"
size="lg"
className="w-full"
icon={<Icon name="mail" size="sm" color="foreground" />}
onPress={() => pick("email")}
haptic="medium"
accessibilityLabel="Continue with email"
>
Continue with email
</Button>
</View>
</Mount>
{/* Legal + returning-user row */}
<Mount delay={440} className="mt-auto">
<View className="items-center pt-10">
<Text variant="muted" className="max-w-xs text-center text-xs leading-relaxed">
By continuing you agree to our{" "}
<Text
className="text-foreground font-medium"
accessibilityRole="link"
onPress={() => {
haptics.light();
onTerms?.();
}}
>
Terms of Service
</Text>{" "}
and{" "}
<Text
className="text-foreground font-medium"
accessibilityRole="link"
onPress={() => {
haptics.light();
onPrivacy?.();
}}
>
Privacy Policy
</Text>
.
</Text>
<Press
onPress={onSignIn}
haptic="light"
accessibilityRole="link"
accessibilityLabel="Sign in to an existing account"
hitSlop={8}
className="mt-3 min-h-11 items-center justify-center px-2"
>
<Text variant="muted">
Already have an account?{" "}
<Text className="text-[#65A30D] font-semibold">Sign in</Text>
</Text>
</Press>
</View>
</Mount>
</ScrollView>
</View>
</World>
);
}
SocialAuthScreen.displayName = "SocialAuthScreen";
Recreate the "Social Auth Screen" screen in React Native using LumioUI.
Goal: Provider-first sign-in — member avatar proof, Apple and Google buttons with brand marks, email fallback, and legal footer 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 auth-social-auth
```
Or pull just the components: `npx lumioui add avatar-group button divider icon 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
### `avatar-group` — Overlapping avatar stack with a border ring per avatar, staggered pop-in, and a tappable "+N" overflow bubble
- `<AvatarGroup>` ×1
### `button` — Tactile pill button with press spring, ink wash and async loading.
- `<Button>` ×3
### `divider` — Hairline rule with optional centered label, horizontal or vertical.
- `<Divider>` ×1
### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×2
### `text` — Themed type scale from caption to display.
- `<Text>` ×10
## 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/auth/social-auth.tsx` exporting SocialAuthScreen. 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 { View, ScrollView } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AvatarGroup, Button, Divider, Icon, Text } from "@lumioui/ui";
import { useHaptic, useTheme } from "@lumioui/core";
import { AppleLogo, GoogleLogo, Mount, Press, World } from "../shared";
export interface SocialAuthScreenProps {
onProviderAuth?: (provider: "apple" | "google" | "email") => void;
onSignIn?: () => void;
onTerms?: () => void;
onPrivacy?: () => void;
}
const face = (n: number) => `https://i.pravatar.cc/150?img=${n}`;
const MEMBERS = [
{ id: "pn", source: { uri: face(47) }, fallback: "PN", alt: "Priya Nair" },
{ id: "jf", source: { uri: face(12) }, fallback: "JF", alt: "Jonah Fields" },
{ id: "mv", source: { uri: face(32) }, fallback: "MV", alt: "Marta Vidal" },
{ id: "so", source: { uri: face(53) }, fallback: "SO", alt: "Sam Okafor" },
{ id: "lr", source: { uri: face(25) }, fallback: "LR", alt: "Lena Roth" },
];
/** Social auth — provider-first sign-in: member proof row, Apple and Google
* buttons with real brand marks, email fallback, legal footer. */
export function SocialAuthScreen({ onProviderAuth, onSignIn, onTerms, onPrivacy }: SocialAuthScreenProps) {
const haptics = useHaptic();
const insets = useSafeAreaInsets();
const { colors } = useTheme();
const pick = (provider: "apple" | "google" | "email") => {
onProviderAuth?.(provider);
};
return (
<World primary="#65A30D">
<View className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]">
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerClassName="flex-grow justify-center px-6"
contentContainerStyle={{ paddingTop: insets.top + 24, paddingBottom: insets.bottom + 24 }}
>
{/* Member proof — real faces + a rating row beat any hero
illustration */}
<Mount delay={0}>
<View className="items-center gap-4 self-center overflow-hidden rounded-3xl bg-white px-8 py-6 dark:bg-[#1A1A20]">
<AvatarGroup items={MEMBERS} max={5} />
<View className="items-center gap-1.5">
<Text variant="muted" className="text-sm">
Joined by 2.4M members
</Text>
<View className="flex-row items-center gap-1">
{[0, 1, 2, 3, 4].map((s) => (
<Icon key={s} name="star" size="sm" color="#EAB308" />
))}
<Text className="pl-1 text-xs font-medium text-muted-foreground">
4.9 · App Store
</Text>
</View>
</View>
</View>
</Mount>
<Mount delay={100}>
<View className="mt-8 items-center gap-3">
<Text variant="h1" className="text-center tracking-tight">
Create your <Text className="italic text-[#65A30D]">account</Text>
</Text>
<Text variant="muted" className="max-w-xs text-center text-base leading-relaxed">
Pick a provider — you'll be set up in under a minute.
</Text>
</View>
</Mount>
{/* Providers — brand glyphs carry the meaning, no extra decoration */}
<View className="mt-9 gap-3">
<Mount delay={180}>
<Button
size="lg"
className="bg-[#65A30D] w-full"
icon={<AppleLogo color={colors.primaryForeground} />}
onPress={() => pick("apple")}
haptic="medium"
accessibilityLabel="Continue with Apple"
>
Continue with Apple
</Button>
</Mount>
<Mount delay={240}>
<Button
variant="glass"
size="lg"
className="w-full"
icon={<GoogleLogo />}
onPress={() => pick("google")}
haptic="medium"
accessibilityLabel="Continue with Google"
>
Continue with Google
</Button>
</Mount>
</View>
<Mount delay={300} className="mt-7">
<Divider
label="or"
labelClassName="text-xs uppercase tracking-widest"
/>
</Mount>
<Mount delay={360}>
<View className="mt-6">
<Button
variant="glass"
size="lg"
className="w-full"
icon={<Icon name="mail" size="sm" color="foreground" />}
onPress={() => pick("email")}
haptic="medium"
accessibilityLabel="Continue with email"
>
Continue with email
</Button>
</View>
</Mount>
{/* Legal + returning-user row */}
<Mount delay={440} className="mt-auto">
<View className="items-center pt-10">
<Text variant="muted" className="max-w-xs text-center text-xs leading-relaxed">
By continuing you agree to our{" "}
<Text
className="text-foreground font-medium"
accessibilityRole="link"
onPress={() => {
haptics.light();
onTerms?.();
}}
>
Terms of Service
</Text>{" "}
and{" "}
<Text
className="text-foreground font-medium"
accessibilityRole="link"
onPress={() => {
haptics.light();
onPrivacy?.();
}}
>
Privacy Policy
</Text>
.
</Text>
<Press
onPress={onSignIn}
haptic="light"
accessibilityRole="link"
accessibilityLabel="Sign in to an existing account"
hitSlop={8}
className="mt-3 min-h-11 items-center justify-center px-2"
>
<Text variant="muted">
Already have an account?{" "}
<Text className="text-[#65A30D] font-semibold">Sign in</Text>
</Text>
</Press>
</View>
</Mount>
</ScrollView>
</View>
</World>
);
}
SocialAuthScreen.displayName = "SocialAuthScreen";
```
Screen: https://lumioui.dev/screens/auth-social-auth