# Vitals

> Body readings from a scale or wearable with BMI and its category, plus progress towards the targets the person set.

Source: https://docs.nasaqui.com/components/vitals

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/vitals.json
```

Weight, BMI with its category, body water, visceral fat, muscle mass, metabolic age and resting heart rate as tiles,
then a card of progress towards targets. The BMI category is a name with an icon and a tone, and it comes with a note
that BMI is a rough screening figure. A missing reading says "Not measured".

## When to use

- A body composition screen fed by a smart scale or a wearable.

## When not to use

- Daily behaviour counts: use [`DailySummary`](https://docs.nasaqui.com/components/daily-summary).

## Import

```tsx
import { Vitals } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { Vitals } from "@fadymondy/nasaq/web";

<Vitals
  vitals={{
    weightKg: 84.2,
    heightCm: 178,
    bodyWaterPercent: 54.1,
    visceralFat: 11,
    hasBaseline: true,
    targets: { weight: { current: 84.2, target: 80, percent: 62, onTarget: false } },
  }}
/>;
```

## Anatomy

```
Vitals                        data-slot="vitals"
├─ header                     title, measured date
├─ StatGrid                   one StatCard per reading (weight and heart rate carry a Sparkline)
├─ BMI note
└─ Card "Targets"             one Meter per target, data-slot="vitals-target"
```

## API

| Prop | Type | Notes |
| --- | --- | --- |
| `vitals` | `VitalsData` | Every reading is optional. `bmi` wins over the one computed from weight and height. |
| `loading` | `boolean` | Skeleton. |
| `hideHeading` | `boolean` | For pages with their own heading. |
| `labels` | `Partial<VitalsLabels>` | Override any string. |

Units: kilograms, percent, years through Intl; bpm, kg/m² and the visceral level are labelled from the vocabulary.
The pure helpers `computeBmi`, `bmiCategory`, `bmiTone`, `clampPercent` and `distanceToTarget` are exported too.

## Code

### React

```tsx
import { Vitals } from "@fadymondy/nasaq/web";

<Vitals
  vitals={{
    weightKg: 84.2,
    heightCm: 178,
    bodyWaterPercent: 54.1,
    visceralFat: 11,
    hasBaseline: true,
    targets: { weight: { current: 84.2, target: 80, percent: 62, onTarget: false } },
  }}
/>;
```

### shadcn

```tsx
import { Vitals } from "@/components/ui/vitals";

<Vitals
  vitals={{
    weightKg: 84.2,
    heightCm: 178,
    bodyWaterPercent: 54.1,
    visceralFat: 11,
    hasBaseline: true,
    targets: { weight: { current: 84.2, target: 80, percent: 62, onTarget: false } },
  }}
/>;
```

### Vue

```vue
<script setup lang="ts">
import { NqVitals } from "@fadymondy/nasaq/vue";
</script>

<template>
  <NqVitals
    :vitals="{
      weightKg: 84.2,
      heightCm: 178,
      bodyWaterPercent: 54.1,
      visceralFat: 11,
      hasBaseline: true,
      targets: { weight: { current: 84.2, target: 80, percent: 62, onTarget: false } },
    }"
  />
</template>
```

### Blade

```blade
<x-nq::vitals
    :vitals="[
        'weightKg' => 84.2,
        'heightCm' => 178,
        'bodyWaterPercent' => 54.1,
        'visceralFat' => 11,
        'hasBaseline' => true,
        'targets' => ['weight' => ['current' => 84.2, 'target' => 80, 'percent' => 62, 'onTarget' => false]],
    ]"
/>
```

### HTML + Alpine

```html
<section data-slot="vitals"  class="flex flex-col gap-4">
                    <header class="flex flex-wrap items-baseline justify-between gap-2">
                <h2 class="text-h3 text-foreground">Vitals</h2>
                            </header>
                    <div data-slot="stat-grid" class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,14rem),1fr))] gap-3"><div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 3v18"/>
  <path d="m19 8 3 8a5 5 0 0 1-6 0zV7"/>
  <path d="M3 7h1a17 17 0 0 0 8-2 17 17 0 0 0 8 2h1"/>
  <path d="m5 8 3 8a5 5 0 0 1-6 0zV7"/>
  <path d="M7 21h10"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Weight</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <bdi data-slot="measure" data-numeric="" dir="ltr" class="tabular-nums [unicode-bidi:isolate]">84.2 kg</bdi>
                                    </div>
                            </div>
                    </div>
    </div>
                <div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Body mass index</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <span class="flex flex-wrap items-center gap-2">
                            <bdi data-slot="measure" data-numeric="" dir="ltr" class="tabular-nums [unicode-bidi:isolate]">26.6 kg/m²</bdi>
                                                            <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-warning/40 bg-nq-warning-soft text-nq-warning-text"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="12" x2="12" y1="8" y2="12"/>
  <line x1="12" x2="12.01" y1="16" y2="16"/>
</svg>Overweight</span>
                                                    </span>
                                    </div>
                            </div>
                    </div>
    </div>
                <div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Body water</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <bdi data-slot="measure" data-numeric="" dir="ltr" class="tabular-nums [unicode-bidi:isolate]">54.1%</bdi>
                                    </div>
                            </div>
                    </div>
    </div>
                <div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 3q1 4 4 6.5t3 5.5a1 1 0 0 1-14 0 5 5 0 0 1 1-3 1 1 0 0 0 5 0c0-2-1.5-3-1.5-5q0-2 2.5-4"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Visceral fat</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <bdi data-slot="measure" data-numeric="" dir="ltr" class="tabular-nums [unicode-bidi:isolate]">11</bdi>
                                    </div>
                            </div>
                    </div>
    </div>
                <div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M17.596 12.768a2 2 0 1 0 2.829-2.829l-1.768-1.767a2 2 0 0 0 2.828-2.829l-2.828-2.828a2 2 0 0 0-2.829 2.828l-1.767-1.768a2 2 0 1 0-2.829 2.829z"/>
  <path d="m2.5 21.5 1.4-1.4"/>
  <path d="m20.1 3.9 1.4-1.4"/>
  <path d="M5.343 21.485a2 2 0 1 0 2.829-2.828l1.767 1.768a2 2 0 1 0 2.829-2.829l-6.364-6.364a2 2 0 1 0-2.829 2.829l1.768 1.767a2 2 0 0 0-2.828 2.829z"/>
  <path d="m9.6 14.4 4.8-4.8"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Muscle mass</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <span class="text-body-sm font-normal text-muted-foreground">Not measured</span>
                                    </div>
                            </div>
                    </div>
    </div>
                <div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 22h14"/>
  <path d="M5 2h14"/>
  <path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/>
  <path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Metabolic age</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <span class="text-body-sm font-normal text-muted-foreground">Not measured</span>
                                    </div>
                            </div>
                    </div>
    </div>
                <div data-slot="stat-card"   class="flex flex-col gap-3 rounded-card border border-border bg-card px-4 py-4 text-card-foreground">
            <div class="flex items-center gap-2">
                            <span data-slot="stat-card-icon" aria-hidden="true" class="grid size-8 shrink-0 place-items-center rounded-control bg-secondary text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"/>
</svg></span>
                        <div data-slot="stat-card-label" class="min-w-0 truncate text-body-sm text-muted-foreground">Resting heart rate</div>
        </div>
        <div class="flex items-end justify-between gap-3">
            <div class="flex min-w-0 flex-col gap-1">
                <div data-slot="stat-card-value" class="text-h2 leading-tight text-foreground tabular-nums">
                                            <span class="text-body-sm font-normal text-muted-foreground">Not measured</span>
                                    </div>
                            </div>
                    </div>
    </div></div>
        <p class="text-caption text-muted-foreground">Body mass index is a rough screening figure, not a diagnosis.</p>
        <div data-slot="card" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground"><div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-foreground text-h3">Targets</h3>
                <div data-slot="card-description" class="text-body-sm text-muted-foreground">How far each figure is from the target you set.</div></div>
            <div data-slot="card-content" class="px-4"><ul class="m-0 grid list-none gap-5 p-0 sm:grid-cols-2">
                                                                                                            <li data-slot="vitals-target"  class="flex flex-col gap-2">
                                <div class="flex items-center justify-end gap-2">
                                    <span data-slot="status" data-tone="info" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-foreground">
            <svg aria-hidden="true" class="size-3.5 shrink-0 text-nq-info-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <circle cx="12" cy="12" r="1"/>
</svg>        <span class="truncate"  title="In progress" >In progress</span>
</span>
                                </div>
                                <div data-slot="meter" data-tone="default" role="meter"  aria-labelledby="nq-meter-8c489d09-label"     aria-valuemin="0" aria-valuemax="100" aria-valuenow="62" aria-valuetext="62%"
    class="flex w-full flex-col gap-1.5">
            <div data-slot="progress-head" class="flex items-baseline justify-between gap-3 text-body-sm">
            <span id="nq-meter-8c489d09-label" class="text-label text-foreground">Weight</span>            <span aria-hidden="true" class="text-muted-foreground tabular-nums">84.2 kg / 80 kg</span>        </div>
        <div data-slot="meter-track" class="relative block w-full overflow-hidden rounded-full bg-nq-surface-soft h-2">
        <div data-slot="meter-indicator" style="inset-inline-start:0;width:62%" class="block h-full rounded-full transition-[width] duration-300 ease-nq motion-reduce:transition-none bg-primary"></div>
    </div>
</div>
                                                                    <span class="text-caption text-muted-foreground">To go <bdi data-slot="measure" data-numeric="" dir="ltr" class="tabular-nums [unicode-bidi:isolate]">4.2 kg</bdi></span>
                                                            </li>
                                                                                                                                </ul></div></div>
    </section>
```
