Dark alarm list: huge time numerals, schedules, and toggles plus bedtime card
import * as React from "react";
import { useState } from "react";
import { Pressable, ScrollView, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Icon, Switch, Text } from "@lumioui/ui";
import { GlowSpot, Mount, World } from "../shared";
import { cn, useHaptic } from "@lumioui/core";
interface Alarm {
id: string;
time: string;
label: string;
days: string;
enabled: boolean;
}
export interface AlarmClockScreenProps {
alarms?: Alarm[];
onToggle?: (id: string, enabled: boolean) => void;
onAdd?: () => void;
onAlarmPress?: (id: string) => void;
}
const DEFAULT_ALARMS: Alarm[] = [
{ id: "1", time: "6:30 AM", label: "Morning run", days: "Mon · Wed · Fri", enabled: true },
{ id: "2", time: "7:15 AM", label: "Wake up", days: "Every day", enabled: true },
{ id: "3", time: "8:00 AM", label: "Standup", days: "Weekdays", enabled: true },
{ id: "4", time: "10:00 PM", label: "Wind down", days: "Every day", enabled: false },
];
/** Alarm clock — the sleep-app idiom: near-black canvas, one row per alarm
* with a huge thin time numeral, schedule underneath, and a working toggle. */
export function AlarmClockScreen({
alarms = DEFAULT_ALARMS,
onToggle,
onAdd,
onAlarmPress,
}: AlarmClockScreenProps) {
const haptics = useHaptic();
const [enabled, setEnabled] = useState<Record<string, boolean>>(() =>
Object.fromEntries(alarms.map((a) => [a.id, a.enabled]))
);
const toggle = (id: string, v: boolean) => {
haptics.selection();
setEnabled((e) => ({ ...e, [id]: v }));
onToggle?.(id, v);
};
return (
<World primary="#BEF04C">
{/* Dark-first — clock/bedtime apps are dark in both schemes. */}
<SafeAreaView className="flex-1 bg-neutral-950" edges={["top"]}>
<GlowSpot color="#BEF04C" size={300} className="-top-20 right-[-80px]" />
<GlowSpot color="#8B5CF6" size={220} className="bottom-[30%] left-[-100px]" />
<View className="flex-row items-center justify-between px-6 pb-2 pt-3">
<View>
<Text variant="h2" className="text-white" accessibilityRole="header">
Alarms
</Text>
{/* Next-ring countdown — the only question alarms answer. */}
<Text className="text-xs text-white/50">
Next: 6:30 AM · in 8h 14m
</Text>
</View>
<Button
variant="ghost"
size="icon"
haptic="light"
onPress={onAdd}
accessibilityLabel="Add alarm"
className="h-10 w-10 rounded-full bg-white/10"
>
<Icon name="plus" size="md" color="#FFF" />
</Button>
</View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="gap-2 px-5 pb-10">
{/* Next-ring hero — iOS Clock's one-line answer to "when do I wake
up", rendered in the accent before the list. */}
<Mount>
<View className="mb-2 flex-row items-center gap-3 rounded-2xl bg-[#BEF04C]/15 px-4 py-3.5">
<Icon name="clock" size="md" color="#BEF04C" />
<View className="flex-1">
<Text className="text-sm font-semibold text-[#BEF04C]">Next alarm 6:30 AM</Text>
<Text className="text-xs text-white/40">Morning run · rings in 8h 12m</Text>
</View>
</View>
</Mount>
{alarms.map((alarm) => {
const on = enabled[alarm.id] ?? alarm.enabled;
return (
<Pressable
key={alarm.id}
onPress={() => {
haptics.selection();
onAlarmPress?.(alarm.id);
}}
accessibilityRole="button"
accessibilityLabel={`${alarm.time}, ${alarm.label}, ${alarm.days}, ${on ? "on" : "off"}`}
accessibilityState={{ checked: on }}
className="flex-row items-center gap-4 rounded-2xl px-4 py-5"
>
<View className="flex-1">
<Text
className={cn("text-5xl font-extralight tracking-tight tabular-nums", on ? "text-white" : "text-white/30")}
>
{alarm.time}
</Text>
<Text className={cn("pt-1 text-sm", on ? "text-white/60" : "text-white/25")}>
{alarm.label} · {alarm.days}
</Text>
</View>
<Switch
checked={on}
onCheckedChange={(v) => toggle(alarm.id, v)}
accessibilityLabel={`${alarm.time} alarm`}
/>
</Pressable>
);
})}
{/* Bedtime card */}
<Mount className="mt-6 gap-3 rounded-2xl bg-white/5 p-5">
<View className="flex-row items-center justify-between">
<Text className="font-semibold text-white">Bedtime</Text>
<Icon name="moon" size="sm" color="#FFF" />
</View>
<View className="flex-row items-center justify-between">
<View>
<Text className="text-3xl font-extralight text-white tabular-nums">10:30 PM</Text>
<Text className="text-xs text-white/50">Sleep goal 7h 30m</Text>
</View>
<View className="rounded-full bg-white/10 px-3 py-1.5">
<Text className="text-xs font-medium text-white">Edit</Text>
</View>
</View>
</Mount>
</ScrollView>
</SafeAreaView>
</World>
);
}
AlarmClockScreen.displayName = "AlarmClockScreen";
Recreate the "Alarm Clock Screen" screen in React Native using LumioUI.
Goal: Dark alarm list: huge time numerals, schedules, and toggles plus bedtime card 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 productivity-alarm-clock
```
Or pull just the components: `npx lumioui add button icon switch 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
### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×3
### `switch` — iOS-size toggle with spring thumb and haptics.
- `<Switch>` ×1
### `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/productivity/alarm-clock.tsx` exporting AlarmClockScreen. 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 } from "react-native-safe-area-context";
import { Button, Icon, Switch, Text } from "@lumioui/ui";
import { GlowSpot, Mount, World } from "../shared";
import { cn, useHaptic } from "@lumioui/core";
interface Alarm {
id: string;
time: string;
label: string;
days: string;
enabled: boolean;
}
export interface AlarmClockScreenProps {
alarms?: Alarm[];
onToggle?: (id: string, enabled: boolean) => void;
onAdd?: () => void;
onAlarmPress?: (id: string) => void;
}
const DEFAULT_ALARMS: Alarm[] = [
{ id: "1", time: "6:30 AM", label: "Morning run", days: "Mon · Wed · Fri", enabled: true },
{ id: "2", time: "7:15 AM", label: "Wake up", days: "Every day", enabled: true },
{ id: "3", time: "8:00 AM", label: "Standup", days: "Weekdays", enabled: true },
{ id: "4", time: "10:00 PM", label: "Wind down", days: "Every day", enabled: false },
];
/** Alarm clock — the sleep-app idiom: near-black canvas, one row per alarm
* with a huge thin time numeral, schedule underneath, and a working toggle. */
export function AlarmClockScreen({
alarms = DEFAULT_ALARMS,
onToggle,
onAdd,
onAlarmPress,
}: AlarmClockScreenProps) {
const haptics = useHaptic();
const [enabled, setEnabled] = useState<Record<string, boolean>>(() =>
Object.fromEntries(alarms.map((a) => [a.id, a.enabled]))
);
const toggle = (id: string, v: boolean) => {
haptics.selection();
setEnabled((e) => ({ ...e, [id]: v }));
onToggle?.(id, v);
};
return (
<World primary="#BEF04C">
{/* Dark-first — clock/bedtime apps are dark in both schemes. */}
<SafeAreaView className="flex-1 bg-neutral-950" edges={["top"]}>
<GlowSpot color="#BEF04C" size={300} className="-top-20 right-[-80px]" />
<GlowSpot color="#8B5CF6" size={220} className="bottom-[30%] left-[-100px]" />
<View className="flex-row items-center justify-between px-6 pb-2 pt-3">
<View>
<Text variant="h2" className="text-white" accessibilityRole="header">
Alarms
</Text>
{/* Next-ring countdown — the only question alarms answer. */}
<Text className="text-xs text-white/50">
Next: 6:30 AM · in 8h 14m
</Text>
</View>
<Button
variant="ghost"
size="icon"
haptic="light"
onPress={onAdd}
accessibilityLabel="Add alarm"
className="h-10 w-10 rounded-full bg-white/10"
>
<Icon name="plus" size="md" color="#FFF" />
</Button>
</View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="gap-2 px-5 pb-10">
{/* Next-ring hero — iOS Clock's one-line answer to "when do I wake
up", rendered in the accent before the list. */}
<Mount>
<View className="mb-2 flex-row items-center gap-3 rounded-2xl bg-[#BEF04C]/15 px-4 py-3.5">
<Icon name="clock" size="md" color="#BEF04C" />
<View className="flex-1">
<Text className="text-sm font-semibold text-[#BEF04C]">Next alarm 6:30 AM</Text>
<Text className="text-xs text-white/40">Morning run · rings in 8h 12m</Text>
</View>
</View>
</Mount>
{alarms.map((alarm) => {
const on = enabled[alarm.id] ?? alarm.enabled;
return (
<Pressable
key={alarm.id}
onPress={() => {
haptics.selection();
onAlarmPress?.(alarm.id);
}}
accessibilityRole="button"
accessibilityLabel={`${alarm.time}, ${alarm.label}, ${alarm.days}, ${on ? "on" : "off"}`}
accessibilityState={{ checked: on }}
className="flex-row items-center gap-4 rounded-2xl px-4 py-5"
>
<View className="flex-1">
<Text
className={cn("text-5xl font-extralight tracking-tight tabular-nums", on ? "text-white" : "text-white/30")}
>
{alarm.time}
</Text>
<Text className={cn("pt-1 text-sm", on ? "text-white/60" : "text-white/25")}>
{alarm.label} · {alarm.days}
</Text>
</View>
<Switch
checked={on}
onCheckedChange={(v) => toggle(alarm.id, v)}
accessibilityLabel={`${alarm.time} alarm`}
/>
</Pressable>
);
})}
{/* Bedtime card */}
<Mount className="mt-6 gap-3 rounded-2xl bg-white/5 p-5">
<View className="flex-row items-center justify-between">
<Text className="font-semibold text-white">Bedtime</Text>
<Icon name="moon" size="sm" color="#FFF" />
</View>
<View className="flex-row items-center justify-between">
<View>
<Text className="text-3xl font-extralight text-white tabular-nums">10:30 PM</Text>
<Text className="text-xs text-white/50">Sleep goal 7h 30m</Text>
</View>
<View className="rounded-full bg-white/10 px-3 py-1.5">
<Text className="text-xs font-medium text-white">Edit</Text>
</View>
</View>
</Mount>
</ScrollView>
</SafeAreaView>
</World>
);
}
AlarmClockScreen.displayName = "AlarmClockScreen";
```
Screen: https://lumioui.dev/screens/productivity-alarm-clock