CalendarHeatmap
heatmap/calendar-heatmapFreeComponent
GitHub-style yearly contribution heatmap with multi-year, weekStart, and i18n support.
Live preview
live · rendered by the registry
Rendered by @heatmap on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://heatmap.chingru.com/r/calendar-heatmap.jsonSource
1import type { Locale, Day as WeekDay } from "date-fns";2import {3 differenceInCalendarDays,4 eachDayOfInterval,5 format,6 formatISO,7 getDay,8 getMonth,9 getYear,10 nextDay,11 parseISO,12 subWeeks,13} from "date-fns";14import type { CSSProperties, HTMLAttributes, ReactNode } from "react";15import { createContext, Fragment, use, useMemo } from "react";16import { cn } from "@/lib/utils";1718export type Activity = {19 date: string;20 value: number;21};2223type ActivityWithLevel = Activity & {24 level: number;25};2627type Week = Array<ActivityWithLevel | undefined>;2829type YearRow = {30 year: number;31 startMonth: number;32 weeks: Week[];33};3435export type Labels = {36 months?: string[];37 weekdays?: string[];38 stat?: string; // Stat text template. Placeholders: {{value}}, {{year}}39 cellLabel?: string; // aria-label template. Placeholders: {{date}}, {{value}}40 heatmapLabel?: string; // aria-label for the heatmap SVG. Placeholder: {{year}}41 legendLabel?: string; // aria-label for the legend fieldset42 legendLevelLabel?: string; // aria-label template for legend swatches. Placeholder: {{level}}43};4445export type ColorConfig = {46 empty?: string;47 scale?: string;48};4950type MonthLabel = {51 weekIndex: number;52 label: string;53 year?: number;54};5556// Non-normalized: 1 empty + (levels-1) colored steps. Normalized: levels colored steps, no empty.57const colorStepCount = (levels: number, isNormalized: boolean) =>58 Math.max(1, isNormalized ? levels : levels - 1);5960const getLevelFill = (61 level: number,62 levels: number,63 isNormalized: boolean,64 highlighted = false,65 colors?: ColorConfig,66): string => {67 const emptyColor = colors?.empty ?? "var(--color-secondary)";68 const scaleColor = colors?.scale ?? "var(--color-chart-1)";6970 if (level === 0) return emptyColor;71 const steps = colorStepCount(levels, isNormalized);72 const opacity =73 steps === 1 ? 100 : Math.round(20 + ((level - 1) * 80) / (steps - 1));74 const finalOpacity = highlighted ? Math.round(opacity * 0.6) : opacity;75 return `color-mix(in oklch, ${scaleColor} ${finalOpacity}%, transparent)`;76};7778const calculateLevel = (79 value: number,80 minValue: number,81 maxValue: number,82 levels: number,83 isNormalized: boolean,84): number => {85 const steps = colorStepCount(levels, isNormalized);86 if (!Number.isFinite(value)) return isNormalized ? 1 : 0;87 if (isNormalized) {88 if (maxValue <= minValue) return 1;89 const percentage = (value - minValue) / (maxValue - minValue);90// … truncated
What it pulls in
npm packages
Files it writes
More from @heatmap
All 4 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| DateHeatmapdate-heatmap | @heatmap | Components | Free | 3 deps | |
| StatusHeatmapstatus-heatmap | @heatmap | Components | Free | 3 deps | |
| WeekdayHeatmapweekday-heatmap | @heatmap | Components | Free | 2 deps |
