v0.1 public beta early access on GitHub
Home / Screens / health-habit-tracker
Screen template

Habit Tracker Screen

Habit tracker: week strip, progress header, tappable check rings and streaks

pro Expo Go ✓ iosandroid gestures: tap · scroll haptics: light · selection
$ npx lumioui add health-habit-tracker
import { HabitTrackerScreen } from "@/screens/health/habit-tracker";
Registry dependencies4
Native packages installed6
@lumioui/ui@latest@lumioui/core@latestreact-native-safe-area-context@*react-native-reanimated@*react-native-svg@*nativewind@*
health/habit-tracker.tsxsource · yours to edit
import * as React from "react";
import { useState } from "react";
import { ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Fab, Glass, Icon, type IconName, Progress, Text } from "@lumioui/ui";
import { cn, useHaptic } from "@lumioui/core";
import { LumioMark, Mount, Press, World } from "../shared";

interface Habit {
  id: string;
  name: string;
  streak: number;
  icon: IconName;
}

export interface HabitTrackerScreenProps {
  habits?: Habit[];
  onToggleHabit?: (id: string, done: boolean) => void;
  onAddHabit?: () => void;
  onDaySelect?: (day: number) => void;
  onBack?: () => void;
}

const HABITS: Habit[] = [
  { id: "1", name: "Read 20 pages", streak: 12, icon: "book-open" },
  { id: "2", name: "Run 3 km", streak: 4, icon: "zap" },
  { id: "3", name: "Meditate", streak: 21, icon: "sun" },
  { id: "4", name: "No sugar", streak: 2, icon: "wheat" },
  { id: "5", name: "Journal", streak: 7, icon: "edit" },
];

const WEEK = ["M", "T", "W", "T", "F", "S", "S"];
const DATES = [21, 22, 23, 24, 25, 26, 27];

/** Per-habit accent — soft tint resting, fills solid when done. Cycled so
 *  custom `habits` lists stay varied without a data field. */
const HABIT_TONES = [
  { on: "bg-[#16A34A]", off: "bg-[#16A34A]/15", fg: "primary" },
  { on: "bg-warning", off: "bg-warning/15", fg: "warning" },
  { on: "bg-chart-3", off: "bg-chart-3/15", fg: "chart3" },
  { on: "bg-success", off: "bg-success/15", fg: "success" },
  { on: "bg-chart-2", off: "bg-chart-2/15", fg: "chart2" },
] as const;

/** Completion per weekday this week — trailing days are upcoming. */
const WEEK_DONE = [1, 0.8, 0.6, 0, 0, 0, 0];

/** Level track — matches the quest screens' milestone language. */
const XP = 135;
const XP_MAX = 150;
const MILESTONES = [30, 60, 90, 120, 150];

/** Habit tracker — Momentum/Streaks idiom: a week strip with a selectable
 *  day, a "X of Y done" progress header, and habit rows whose check rings
 *  fill on tap. The streak count is the motivation. */
export function HabitTrackerScreen({
  habits = HABITS,
  onToggleHabit,
  onAddHabit,
  onDaySelect,
  onBack,
}: HabitTrackerScreenProps) {
  const haptics = useHaptic();
  const insets = useSafeAreaInsets();
  const [day, setDay] = useState(2); // Wednesday (today)
  const [done, setDone] = useState<Record<string, boolean>>({
    "1": true,
    "3": true,
    "5": true,
  });

  const doneCount = habits.filter((h) => done[h.id]).length;
  const pct = habits.length > 0 ? Math.round((doneCount / habits.length) * 100) : 0;

  const toggleHabit = (id: string) => {
    haptics.selection();
    const next = !done[id];
    setDone((d) => ({ ...d, [id]: next }));
    onToggleHabit?.(id, next);
  };

  return (
    <World primary="#16A34A">
    <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="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">
              Today
            </Text>
          </View>
        </View>
      </View>

      <View className="flex-row items-center justify-end gap-1.5 px-4 pb-1" style={{ paddingTop: insets.top + 58 }}>
        {/* Currency pills — the quest family's gem + longest-streak brag. */}
        <View className="ml-auto flex-row items-center gap-1.5">
          <View className="overflow-hidden rounded-full">
            <Glass effect="regular" className="absolute inset-0 rounded-full" pointerEvents="none" />
            <View className="flex-row items-center gap-1 px-2.5 py-1.5">
              <Text className="text-[13px]">💎</Text>
              <Text className="text-[13px] font-bold tabular-nums">235</Text>
            </View>
          </View>
          <View className="overflow-hidden rounded-full">
            <Glass effect="regular" className="absolute inset-0 rounded-full" pointerEvents="none" />
            <View className="flex-row items-center gap-1 px-2.5 py-1.5">
              <Text className="text-[13px]">🔥</Text>
              <Text className="text-[13px] font-bold tabular-nums">21d</Text>
            </View>
          </View>
        </View>
      </View>

      <ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="gap-6 px-6 pb-10 pt-4">
        {/* Week strip — selectable day cells, today ringed. */}
        <Mount className="flex-row justify-between">
          {WEEK.map((d, i) => {
            const selected = i === day;
            return (
              <Press
                key={`${d}-${i}`}
                onPress={() => {
                  haptics.selection();
                  setDay(i);
                  onDaySelect?.(i);
                }}
                accessibilityRole="button"
                accessibilityLabel={`${d} ${DATES[i]}`}
                accessibilityState={{ selected }}
                className={cn(
                  "h-14 w-11 items-center justify-center gap-0.5 rounded-2xl",
                  selected ? "bg-[#16A34A]" : "bg-white dark:bg-[#1A1A20]"
                )}
              >
                <Text className={cn("text-xs", selected ? "text-primary-foreground/70" : "text-muted-foreground")}>
                  {d}
                </Text>
                <Text className={cn("font-semibold tabular-nums", selected ? "text-primary-foreground" : "")}>
                  {DATES[i]}
                </Text>
              </Press>
            );
          })}
        </Mount>

        {/* Progress header */}
        <Mount delay={60} className="gap-2.5">
          <View className="flex-row items-baseline justify-between">
            <Text variant="h2">
              {doneCount} of {habits.length} done
            </Text>
            <Text variant="muted" className="tabular-nums">
              {pct}%
            </Text>
          </View>
          <Progress value={pct} />
        </Mount>

        {/* Level strip — the milestone track that makes the XP real. */}
        <Mount delay={90} className="overflow-hidden rounded-3xl">
          <Glass interactive effect="regular" tintColor="#D8F1DC" sheen={0.4} className="absolute inset-0 rounded-3xl" pointerEvents="none" />
          <View className="gap-3 p-4">
            <View className="flex-row items-center justify-between">
              <Text className="text-[13px] font-bold text-[#14532D]">👑 Level 4</Text>
              <Text className="text-[12px] font-semibold text-[#14532D]/60">⚡ {XP}/{XP_MAX} XP</Text>
            </View>
            <View className="relative h-8 justify-center">
              <View className="h-1.5 rounded-full bg-[#15803D]/15" />
              <View
                className="absolute h-1.5 rounded-full bg-[#22C55E]"
                style={{ width: `${(XP / XP_MAX) * 96}%` }}
              />
              <View className="absolute inset-x-0 flex-row justify-between">
                {MILESTONES.map((m) => (
                  <View
                    key={m}
                    className="h-8 w-8 items-center justify-center rounded-full"
                    style={{ backgroundColor: m <= XP ? "#22C55E" : "#DCF5E3" }}
                  >
                    <Text className="text-[11px]">{m <= XP ? "✓" : "🏆"}</Text>
                  </View>
                ))}
              </View>
            </View>
          </View>
        </Mount>

        {/* Habit rows — check ring fills on tap; each pays out XP. */}
        <Mount delay={120} className="gap-2.5">
          {habits.map((h, hi) => {
            const checked = !!done[h.id];
            const tone = HABIT_TONES[hi % HABIT_TONES.length]!;
            return (
              <Press
                key={h.id}
                onPress={() => toggleHabit(h.id)}
                accessibilityRole="button"
                accessibilityLabel={`${h.name}, ${h.streak} day streak${checked ? ", done" : ""}`}
                accessibilityState={{ checked }}
                className={cn(
                  "flex-row items-center gap-3.5 rounded-3xl p-4",
                  checked ? "bg-[#16A34A]/10" : "bg-white dark:bg-[#1A1A20]"
                )}
              >
                <View
                  className={cn(
                    "h-9 w-9 items-center justify-center rounded-full",
                    checked ? tone.on : tone.off
                  )}
                >
                  <Icon
                    name={h.icon}
                    size="sm"
                    color={checked ? "primaryForeground" : tone.fg}
                  />
                </View>
                <View className="flex-1">
                  <Text variant="small" className="font-semibold">
                    {h.name}
                  </Text>
                  <View className="flex-row items-center gap-1">
                    <Icon name="zap" size="sm" color="warning" />
                    <Text variant="muted" className="text-xs tabular-nums">
                      {h.streak} day streak · +10 XP
                    </Text>
                  </View>
                </View>
                <View
                  className={cn(
                    "h-7 w-7 items-center justify-center rounded-full border-2",
                    checked ? "border-[#16A34A] bg-[#16A34A]" : "border-border bg-transparent"
                  )}
                >
                  {checked ? <Icon name="check" size="sm" color="primaryForeground" /> : null}
                </View>
              </Press>
            );
          })}
        </Mount>

        {/* Week at a glance — mini completion bars, today carries state. */}
        <Mount delay={180} className="gap-4 overflow-hidden rounded-3xl bg-white p-5 dark:bg-[#1A1A20]">
          <Text className="font-semibold">This week</Text>
          <View className="flex-row items-end justify-between">
            {WEEK.map((d, i) => (
              <View key={`${d}-${i}`} className="items-center gap-2">
                <View className="h-12 w-4 justify-end overflow-hidden rounded-full bg-muted">
                  {(WEEK_DONE[i] ?? 0) > 0 && (
                    <View
                      className={cn("w-full rounded-full", i === day ? "bg-[#16A34A]" : "bg-foreground/70")}
                      style={{ height: `${(WEEK_DONE[i] ?? 0) * 100}%` }}
                    />
                  )}
                </View>
                <Text className="text-xs text-muted-foreground">{d}</Text>
              </View>
            ))}
          </View>
        </Mount>
      </ScrollView>

      <Fab
        icon={<Icon name="plus" size="md" color="#1B1B26" />}
        haptic="medium"
        onPress={() => onAddHabit?.()}
        accessibilityLabel="New habit"
        className="bg-[#BEF04C]"
      />
    </SafeAreaView>
    </World>
  );
}
HabitTrackerScreen.displayName = "HabitTrackerScreen";
agent prompt — paste into Claude Code, Cursor, Devin…
Recreate the "Habit Tracker Screen" screen in React Native using LumioUI.

Goal: Habit tracker: week strip, progress header, tappable check rings and streaks 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-habit-tracker
```

Or pull just the components: `npx lumioui add fab icon progress 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

### `fab` — Liquid-glass floating action button — glass disc or filled pill, extended label mode, auto-async loading swap, mount pop
- `<Fab>` ×1

### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×5

### `progress` — Animated fill bar with indeterminate sweep mode.
- `<Progress>` ×1

### `text` — Themed type scale from caption to display.
- `<Text>` ×16

## 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/habit-tracker.tsx` exporting HabitTrackerScreen. 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 { ScrollView, View } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Fab, Glass, Icon, type IconName, Progress, Text } from "@lumioui/ui";
import { cn, useHaptic } from "@lumioui/core";
import { LumioMark, Mount, Press, World } from "../shared";

interface Habit {
  id: string;
  name: string;
  streak: number;
  icon: IconName;
}

export interface HabitTrackerScreenProps {
  habits?: Habit[];
  onToggleHabit?: (id: string, done: boolean) => void;
  onAddHabit?: () => void;
  onDaySelect?: (day: number) => void;
  onBack?: () => void;
}

const HABITS: Habit[] = [
  { id: "1", name: "Read 20 pages", streak: 12, icon: "book-open" },
  { id: "2", name: "Run 3 km", streak: 4, icon: "zap" },
  { id: "3", name: "Meditate", streak: 21, icon: "sun" },
  { id: "4", name: "No sugar", streak: 2, icon: "wheat" },
  { id: "5", name: "Journal", streak: 7, icon: "edit" },
];

const WEEK = ["M", "T", "W", "T", "F", "S", "S"];
const DATES = [21, 22, 23, 24, 25, 26, 27];

/** Per-habit accent — soft tint resting, fills solid when done. Cycled so
 *  custom `habits` lists stay varied without a data field. */
const HABIT_TONES = [
  { on: "bg-[#16A34A]", off: "bg-[#16A34A]/15", fg: "primary" },
  { on: "bg-warning", off: "bg-warning/15", fg: "warning" },
  { on: "bg-chart-3", off: "bg-chart-3/15", fg: "chart3" },
  { on: "bg-success", off: "bg-success/15", fg: "success" },
  { on: "bg-chart-2", off: "bg-chart-2/15", fg: "chart2" },
] as const;

/** Completion per weekday this week — trailing days are upcoming. */
const WEEK_DONE = [1, 0.8, 0.6, 0, 0, 0, 0];

/** Level track — matches the quest screens' milestone language. */
const XP = 135;
const XP_MAX = 150;
const MILESTONES = [30, 60, 90, 120, 150];

/** Habit tracker — Momentum/Streaks idiom: a week strip with a selectable
 *  day, a "X of Y done" progress header, and habit rows whose check rings
 *  fill on tap. The streak count is the motivation. */
export function HabitTrackerScreen({
  habits = HABITS,
  onToggleHabit,
  onAddHabit,
  onDaySelect,
  onBack,
}: HabitTrackerScreenProps) {
  const haptics = useHaptic();
  const insets = useSafeAreaInsets();
  const [day, setDay] = useState(2); // Wednesday (today)
  const [done, setDone] = useState<Record<string, boolean>>({
    "1": true,
    "3": true,
    "5": true,
  });

  const doneCount = habits.filter((h) => done[h.id]).length;
  const pct = habits.length > 0 ? Math.round((doneCount / habits.length) * 100) : 0;

  const toggleHabit = (id: string) => {
    haptics.selection();
    const next = !done[id];
    setDone((d) => ({ ...d, [id]: next }));
    onToggleHabit?.(id, next);
  };

  return (
    <World primary="#16A34A">
    <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="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">
              Today
            </Text>
          </View>
        </View>
      </View>

      <View className="flex-row items-center justify-end gap-1.5 px-4 pb-1" style={{ paddingTop: insets.top + 58 }}>
        {/* Currency pills — the quest family's gem + longest-streak brag. */}
        <View className="ml-auto flex-row items-center gap-1.5">
          <View className="overflow-hidden rounded-full">
            <Glass effect="regular" className="absolute inset-0 rounded-full" pointerEvents="none" />
            <View className="flex-row items-center gap-1 px-2.5 py-1.5">
              <Text className="text-[13px]">💎</Text>
              <Text className="text-[13px] font-bold tabular-nums">235</Text>
            </View>
          </View>
          <View className="overflow-hidden rounded-full">
            <Glass effect="regular" className="absolute inset-0 rounded-full" pointerEvents="none" />
            <View className="flex-row items-center gap-1 px-2.5 py-1.5">
              <Text className="text-[13px]">🔥</Text>
              <Text className="text-[13px] font-bold tabular-nums">21d</Text>
            </View>
          </View>
        </View>
      </View>

      <ScrollView showsVerticalScrollIndicator={false} contentContainerClassName="gap-6 px-6 pb-10 pt-4">
        {/* Week strip — selectable day cells, today ringed. */}
        <Mount className="flex-row justify-between">
          {WEEK.map((d, i) => {
            const selected = i === day;
            return (
              <Press
                key={`${d}-${i}`}
                onPress={() => {
                  haptics.selection();
                  setDay(i);
                  onDaySelect?.(i);
                }}
                accessibilityRole="button"
                accessibilityLabel={`${d} ${DATES[i]}`}
                accessibilityState={{ selected }}
                className={cn(
                  "h-14 w-11 items-center justify-center gap-0.5 rounded-2xl",
                  selected ? "bg-[#16A34A]" : "bg-white dark:bg-[#1A1A20]"
                )}
              >
                <Text className={cn("text-xs", selected ? "text-primary-foreground/70" : "text-muted-foreground")}>
                  {d}
                </Text>
                <Text className={cn("font-semibold tabular-nums", selected ? "text-primary-foreground" : "")}>
                  {DATES[i]}
                </Text>
              </Press>
            );
          })}
        </Mount>

        {/* Progress header */}
        <Mount delay={60} className="gap-2.5">
          <View className="flex-row items-baseline justify-between">
            <Text variant="h2">
              {doneCount} of {habits.length} done
            </Text>
            <Text variant="muted" className="tabular-nums">
              {pct}%
            </Text>
          </View>
          <Progress value={pct} />
        </Mount>

        {/* Level strip — the milestone track that makes the XP real. */}
        <Mount delay={90} className="overflow-hidden rounded-3xl">
          <Glass interactive effect="regular" tintColor="#D8F1DC" sheen={0.4} className="absolute inset-0 rounded-3xl" pointerEvents="none" />
          <View className="gap-3 p-4">
            <View className="flex-row items-center justify-between">
              <Text className="text-[13px] font-bold text-[#14532D]">👑 Level 4</Text>
              <Text className="text-[12px] font-semibold text-[#14532D]/60">⚡ {XP}/{XP_MAX} XP</Text>
            </View>
            <View className="relative h-8 justify-center">
              <View className="h-1.5 rounded-full bg-[#15803D]/15" />
              <View
                className="absolute h-1.5 rounded-full bg-[#22C55E]"
                style={{ width: `${(XP / XP_MAX) * 96}%` }}
              />
              <View className="absolute inset-x-0 flex-row justify-between">
                {MILESTONES.map((m) => (
                  <View
                    key={m}
                    className="h-8 w-8 items-center justify-center rounded-full"
                    style={{ backgroundColor: m <= XP ? "#22C55E" : "#DCF5E3" }}
                  >
                    <Text className="text-[11px]">{m <= XP ? "✓" : "🏆"}</Text>
                  </View>
                ))}
              </View>
            </View>
          </View>
        </Mount>

        {/* Habit rows — check ring fills on tap; each pays out XP. */}
        <Mount delay={120} className="gap-2.5">
          {habits.map((h, hi) => {
            const checked = !!done[h.id];
            const tone = HABIT_TONES[hi % HABIT_TONES.length]!;
            return (
              <Press
                key={h.id}
                onPress={() => toggleHabit(h.id)}
                accessibilityRole="button"
                accessibilityLabel={`${h.name}, ${h.streak} day streak${checked ? ", done" : ""}`}
                accessibilityState={{ checked }}
                className={cn(
                  "flex-row items-center gap-3.5 rounded-3xl p-4",
                  checked ? "bg-[#16A34A]/10" : "bg-white dark:bg-[#1A1A20]"
                )}
              >
                <View
                  className={cn(
                    "h-9 w-9 items-center justify-center rounded-full",
                    checked ? tone.on : tone.off
                  )}
                >
                  <Icon
                    name={h.icon}
                    size="sm"
                    color={checked ? "primaryForeground" : tone.fg}
                  />
                </View>
                <View className="flex-1">
                  <Text variant="small" className="font-semibold">
                    {h.name}
                  </Text>
                  <View className="flex-row items-center gap-1">
                    <Icon name="zap" size="sm" color="warning" />
                    <Text variant="muted" className="text-xs tabular-nums">
                      {h.streak} day streak · +10 XP
                    </Text>
                  </View>
                </View>
                <View
                  className={cn(
                    "h-7 w-7 items-center justify-center rounded-full border-2",
                    checked ? "border-[#16A34A] bg-[#16A34A]" : "border-border bg-transparent"
                  )}
                >
                  {checked ? <Icon name="check" size="sm" color="primaryForeground" /> : null}
                </View>
              </Press>
            );
          })}
        </Mount>

        {/* Week at a glance — mini completion bars, today carries state. */}
        <Mount delay={180} className="gap-4 overflow-hidden rounded-3xl bg-white p-5 dark:bg-[#1A1A20]">
          <Text className="font-semibold">This week</Text>
          <View className="flex-row items-end justify-between">
            {WEEK.map((d, i) => (
              <View key={`${d}-${i}`} className="items-center gap-2">
                <View className="h-12 w-4 justify-end overflow-hidden rounded-full bg-muted">
                  {(WEEK_DONE[i] ?? 0) > 0 && (
                    <View
                      className={cn("w-full rounded-full", i === day ? "bg-[#16A34A]" : "bg-foreground/70")}
                      style={{ height: `${(WEEK_DONE[i] ?? 0) * 100}%` }}
                    />
                  )}
                </View>
                <Text className="text-xs text-muted-foreground">{d}</Text>
              </View>
            ))}
          </View>
        </Mount>
      </ScrollView>

      <Fab
        icon={<Icon name="plus" size="md" color="#1B1B26" />}
        haptic="medium"
        onPress={() => onAddHabit?.()}
        accessibilityLabel="New habit"
        className="bg-[#BEF04C]"
      />
    </SafeAreaView>
    </World>
  );
}
HabitTrackerScreen.displayName = "HabitTrackerScreen";
```

Screen: https://lumioui.dev/screens/health-habit-tracker
iPhone 17 · iOS
9:41
Live preview — the real screen via Expo Web · open full ↗