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
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Section heading. |
days | StreakDay[] | — | One entry per grid cell, oldest first. |
currentStreak | number | — | Current streak length — drives the daily celebration trigger. |
weekdayLabels | string[] | — | Already-localized weekday labels (7 entries). |
streakLabel | string | — | Already-formatted streak summary text, e.g. "14-day streak". |
legend | { none, logged, protein, both } | — | Already-localized legend labels. |
celebrateDailyProgress | boolean | true | Enables 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
currentStreakincreases versus its previous value (not per-tier) — gated bycelebrateDailyProgress. - 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.