Skip to content

BodyMetricsCard

Reworks meal-tracker's ProfilTab body-stats card (gender/weight/height key-value rows + an animated body-fat-% reveal with a category badge) into a yemek component. The original was GSAP-driven (count-up tween, background flash, badge spring, two pulse rings) — this keeps the choreography but in pure CSS keyframes, since yemek has no GSAP dependency and doesn't need one.

Playground

Körpermaße

GeschlechtMännlich
Gewicht82 kg
Größe181 cm
Körperfett14.2%Definiert

Click "Neu würfeln" — the % pops in, the row flashes, and the badge springs in with two expanding pulse rings, same choreography as the original GSAP version.

vue
<script setup>
import { BodyMetricsCard } from 'yemek'

const saved = ref(false)

async function saveBodyMetrics() {
  await supabase.auth.updateUser({ data: { /* ... */ } })
  saved.value = true
  setTimeout(() => { saved.value = false }, 2000)
}
</script>

<template>
  <BodyMetricsCard
    title="Körpermaße"
    :rows="[
      { label: 'Geschlecht', value: gender === 'male' ? 'Männlich' : 'Weiblich' },
      { label: 'Gewicht', value: `${storedWeight} kg` },
      { label: 'Größe', value: `${heightCm} cm` }
    ]"
    body-fat-label="Körperfett"
    :body-fat-percent="bodyFatPercent ?? undefined"
    :body-fat-category-label="bodyFatCategory ? t(`profil.bfCategory.${bodyFatCategory}`) : undefined"
    :body-fat-status="bodyFatStatus"
    incomplete-label="Miss Bauch-, Hals- und Hüftumfang für eine Körperfett-Schätzung."
    edit-label="Bearbeiten"
    saved-label="Gespeichert"
    :saved="saved"
    @edit="openBodyDialog"
  />
</template>

Props

PropTypeDefaultDescription
titlestringCard heading.
rows{ label: string; value: string }[]Already-formatted key/value rows (gender, weight, height, ...). Generic — not limited to exactly those three.
bodyFatLabelstringundefinedLabel for the body-fat row. Row only renders when bodyFatPercent is set.
bodyFatPercentnumberundefinedBody-fat percentage. Omit entirely (not 0/null) to show incompleteLabel instead.
bodyFatCategoryLabelstringundefinedAlready-localized category text for the badge (e.g. "Definiert").
bodyFatStatus'good' | 'warn' | 'danger''good'Generic 3-tier status the consumer maps its own classification down to (e.g. meal-tracker's 4-category defined/athleticgood, slightlyElevatedwarn, elevateddanger) — same pattern as MetricRing's status prop.
incompleteLabelstringundefinedShown instead of the body-fat row when bodyFatPercent is omitted.
editLabelstringEdit button label.
savedLabelstringundefinedShown (with a checkmark) instead of editLabel while saved is true.
savedbooleanfalseSwaps the edit button for a checkmark + savedLabel — the consumer flips this after its own save call resolves (no in-flight spinner state here, unlike MealReviewCard's saving).

Events

EventPayloadFired when
editThe edit button is clicked.

Behavior notes

  • No body-fat classification or unit-conversion logic lives here — rows are already-formatted strings, and bodyFatStatus is the consumer's own classification already mapped to a generic 3-tier status.
  • bodyFatStatus="warn" deliberately uses yemek's terracotta highlight token, not the same red as "danger" — "slightly elevated" and "elevated" shouldn't read as the same severity.
  • The reveal (value pop, row flash, badge spring, two staggered pulse rings) re-triggers any time bodyFatPercent actually changes value — not on every render, and not on initial mount (a returning user sees their stored value immediately, the animation only plays right after a fresh save).
  • prefers-reduced-motion: reduce disables the flash/spring/pulse animations entirely — the new value/badge still swap in, just without the accompanying motion.