Skip to content

StreakHeatmap

A GitHub-contribution-style grid for a logging streak, with a daily reward celebration.

Your streak6-day streak
MonTueWedThuFriSatSun
NoneLoggedProtein hitBoth goals

"Simulate next day" appends a new day and drops the oldest (a genuine sliding window, not growing backward into history) and only that path triggers the celebration burst — the slider jumps currentStreak directly for exploring tier colors without repeated clicking. Turn off celebrateDailyProgress to see a plain heatmap with no reward animation.

vue
<script setup>
import { StreakHeatmap } from 'yemek'
</script>

<template>
  <StreakHeatmap
    title="Your streak"
    :days="heatmapDays"
    :current-streak="14"
    :weekday-labels="['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']"
    streak-label="14-day streak"
    :legend="{ none: 'None', logged: 'Logged', protein: 'Protein hit', both: 'Both goals' }"
  />
</template>

Types

ts
type StreakCellState = 'none' | 'logged' | 'protein' | 'both' | 'future'
type StreakTier = 'cold' | 'warm' | 'hot' | 'blazing' | 'inferno' | 'stellar'

interface StreakDay {
  date: string
  state: StreakCellState
  /** Set only for days inside the currently active streak — overrides the plain state color
   * with the matching heat tier. Classification (what counts as a "streak", tier thresholds) is
   * the consumer's domain logic; the component only renders what it's given. */
  tier?: StreakTier
  isToday?: boolean
}

Props

PropTypeDefaultDescription
titlestringSection heading.
daysStreakDay[]One entry per grid cell, oldest first.
currentStreaknumberCurrent streak length — drives the daily celebration trigger.
weekdayLabelsstring[]Already-localized weekday labels (7 entries).
streakLabelstringAlready-formatted streak summary text, e.g. "14-day streak".
legend{ none, logged, protein, both }Already-localized legend labels.
celebrateDailyProgressbooleantrueEnables the daily particle-burst reward. Set false to render a plain heatmap with no celebration.

Behavior notes

  • No date math, streak/tier classification, or pluralization happens inside the component — all of that is the consumer's domain logic (in meal-tracker's case, app/lib/streak.ts).
  • Tier colors use a single warm hue-family ramp (--yemek-heat-1/2/3, gold → rust → ember) instead of an unrelated color jump at the top tier — see Design tokens.
  • The 'cold' tier deliberately does not override the cell's plain state color — only the flame icon shows muted for that tier.
  • A daily particle-burst celebration fires when currentStreak increases versus its previous value (not per-tier) — gated by celebrateDailyProgress.
  • Simplification vs. an earlier internal version: inside an active streak, every day shows its tier color solid, without also distinguishing logged-only vs. protein-only within that tier.