# iOS Fitness

> Dark activity rings, week strip and workout history.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/templates/ios-fitness
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/ios-fitness.json
```

Adds the page at `app/ios-fitness/page.tsx` together with the components below. Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Components

- [iOS Kit](https://chromotion.dev/r/ios-kit.md): `@/components/ios-kit`

## Dependencies

```bash
npm install lucide-react
```

## Source

```tsx title="app/ios-fitness/page.tsx"
"use client"

import * as React from "react"
import { Bike, Dumbbell, Footprints, Heart, PersonStanding, Users, Waves } from "lucide-react"

import { cn } from "@/lib/utils"
import { IOSCard, IOSDevice, IOSPage, IOSTabBar, type IOSTabItem } from "@/components/ios-kit"

const tabs: IOSTabItem[] = [
  { id: "summary", label: "Summary", icon: Heart },
  { id: "fitness", label: "Fitness+", icon: Dumbbell },
  { id: "sharing", label: "Sharing", icon: Users },
]

const week = [
  { d: "M", n: 8, move: 0.92, exercise: 0.8, stand: 1 },
  { d: "T", n: 9, move: 1.1, exercise: 1, stand: 0.9 },
  { d: "W", n: 10, move: 0.64, exercise: 0.5, stand: 0.8 },
  { d: "T", n: 11, move: 1.25, exercise: 1.1, stand: 1 },
  { d: "F", n: 12, move: 0.78, exercise: 0.66, stand: 0.75 },
  { d: "S", n: 13, move: 0, exercise: 0, stand: 0 },
  { d: "S", n: 14, move: 0, exercise: 0, stand: 0 },
]

const rings = [
  { key: "move", label: "Move", color: "#ff2d55", track: "#ff2d5533", r: 74 },
  { key: "exercise", label: "Exercise", color: "#a6ff00", track: "#a6ff0033", r: 54 },
  { key: "stand", label: "Stand", color: "#00e5e0", track: "#00e5e033", r: 34 },
] as const

const workouts = [
  { name: "Outdoor Run", detail: "5.2 km · 28:41", time: "7:12 AM", icon: PersonStanding, from: "from-lime-400", to: "to-green-600" },
  { name: "Strength", detail: "42 min · 312 kcal", time: "Yesterday", icon: Dumbbell, from: "from-orange-400", to: "to-red-500" },
  { name: "Cycling", detail: "18.4 km · 52:10", time: "Tuesday", icon: Bike, from: "from-sky-400", to: "to-blue-600" },
  { name: "Pool Swim", detail: "1,200 m · 31:05", time: "Monday", icon: Waves, from: "from-cyan-300", to: "to-teal-600" },
]

function Rings({ move, exercise, stand }: { move: number; exercise: number; stand: number }) {
  const values = { move, exercise, stand }
  return (
    <svg viewBox="0 0 180 180" className="size-[180px] -rotate-90" role="img" aria-label="Activity rings">
      {rings.map(({ key, color, track, r }) => {
        const c = 2 * Math.PI * r
        const value = Math.min(values[key], 1.5)
        return (
          <g key={key}>
            <circle cx="90" cy="90" r={r} fill="none" stroke={track} strokeWidth="16" />
            <circle
              cx="90" cy="90" r={r} fill="none" stroke={color} strokeWidth="16" strokeLinecap="round"
              strokeDasharray={c}
              strokeDashoffset={c * (1 - (value > 1 ? value % 1 || 1 : value))}
              className="transition-[stroke-dashoffset] duration-700 ease-out"
              style={{ opacity: value === 0 ? 0 : 1 }}
            />
          </g>
        )
      })}
    </svg>
  )
}

export default function IOSFitness() {
  const [tab, setTab] = React.useState("summary")
  const [selected, setSelected] = React.useState(12)
  const day = week.find((item) => item.n === selected) ?? week[4]

  const stats = [
    { label: "Move", value: `${Math.round(day.move * 500)}/500`, unit: "KCAL", color: "text-[#ff2d55]" },
    { label: "Exercise", value: `${Math.round(day.exercise * 30)}/30`, unit: "MIN", color: "text-[#a6ff00]" },
    { label: "Stand", value: `${Math.round(day.stand * 12)}/12`, unit: "HRS", color: "text-[#00e5e0]" },
  ]

  return (
    <IOSDevice className="bg-black text-white [--ios-bg:#000000] [--ios-label:#ffffff] [--ios-label2:#ebebf599] [--ios-label3:#ebebf54d] [--ios-sep:#54545aa6] [--ios-surface:#1c1c1e]">
      <IOSPage title="Summary" tabbed>
        <ul className="mb-3 grid grid-cols-7 px-2" aria-label="Week">
          {week.map(({ d, n }) => (
            <li key={n} className="flex flex-col items-center gap-1">
              <span className="text-[11px] font-semibold text-(color:--ios-label2)">{d}</span>
              <button
                type="button"
                onClick={() => setSelected(n)}
                aria-pressed={selected === n}
                aria-label={`November ${n}`}
                className={cn(
                  "flex size-9 items-center justify-center rounded-full text-[17px] transition-colors",
                  selected === n ? "bg-white font-semibold text-black" : "text-white"
                )}
              >
                {n}
              </button>
            </li>
          ))}
        </ul>

        <IOSCard className="mx-4 mb-3 flex items-center justify-between gap-2">
          <ul className="flex flex-col gap-3">
            {stats.map((stat) => (
              <li key={stat.label}>
                <p className="text-[14px] font-medium">{stat.label}</p>
                <p className={cn("text-[24px] leading-7 font-semibold tabular-nums", stat.color)}>
                  {stat.value}
                  <span className="ml-0.5 text-[16px]">{stat.unit}</span>
                </p>
              </li>
            ))}
          </ul>
          <Rings move={day.move} exercise={day.exercise} stand={day.stand} />
        </IOSCard>

        <IOSCard className="mx-4 mb-3">
          <div className="flex items-center justify-between">
            <h2 className="text-[17px] font-semibold">Move streak</h2>
            <Footprints className="size-5 text-[#ff2d55]" />
          </div>
          <div className="mt-3 flex h-24 items-end justify-between gap-2">
            {week.map(({ d, n, move }) => (
              <div key={n} className="flex flex-1 flex-col items-center gap-1.5">
                <div className="flex h-20 w-full items-end rounded-md bg-white/5">
                  <div
                    className="w-full rounded-md bg-[#ff2d55] transition-[height] duration-500"
                    style={{ height: `${Math.min(move, 1.25) * 80}%` }}
                  />
                </div>
                <span className="text-[11px] text-(color:--ios-label2)">{d}</span>
              </div>
            ))}
          </div>
        </IOSCard>

        <h2 className="px-5 pt-2 pb-2 text-[22px] font-bold">Workouts</h2>
        <ul className="mx-4 overflow-hidden rounded-[16px] bg-(color:--ios-surface)">
          {workouts.map(({ name, detail, time, icon: Icon, from, to }) => (
            <li key={name} className="relative flex items-center gap-3 px-4 py-3 after:absolute after:right-0 after:bottom-0 after:left-[68px] after:h-px after:bg-(color:--ios-sep) last:after:hidden">
              <span className={cn("flex size-10 items-center justify-center rounded-full bg-gradient-to-b text-black", from, to)}>
                <Icon className="size-5" />
              </span>
              <span className="min-w-0 flex-1">
                <span className="block text-[17px] leading-[22px] font-semibold">{name}</span>
                <span className="block text-[14px] text-(color:--ios-label2)">{detail}</span>
              </span>
              <span className="text-[14px] text-(color:--ios-label2)">{time}</span>
            </li>
          ))}
        </ul>
      </IOSPage>
      <IOSTabBar items={tabs} value={tab} onValueChange={setTab} dark />
    </IOSDevice>
  )
}
```
