← Components/Data Display

HeatmapCalendar

heatmapcalendar-1779379671883.tsx
'use client'
import { useState } from 'react'

const WEEKS = 26
const DAYS = 7

function randomActivity(): number {
  const r = Math.random()
  if (r < 0.3) return 0
  if (r < 0.55) return 1
  if (r < 0.75) return 2
  if (r < 0.9) return 3
  return 4
}

const DATA = Array.from({ length: WEEKS }, () => Array.from({ length: DAYS }, randomActivity))

const COLORS = ['rgba(255,255,255,0.06)', 'rgba(201,168,76,0.25)', 'rgba(201,168,76,0.5)', 'rgba(201,168,76,0.75)', '#C9A84C']
const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
const DAY_LABELS = ['', 'Mon', '', 'Wed', '', 'Fri', '']

export default function HeatmapCalendar() {
  const [tooltip, setTooltip] = useState<{ text: string; x: number; y: number } | null>(null)

  const total = DATA.flat().reduce((a, v) => a + v, 0)
  const days = DATA.flat().filter(v => v > 0).length

  return (
    <div style={{ background: '#0D0D0D', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 16, padding: 24, position: 'relative' }}>
      <div style={{ color: '#F5F5F0', fontSize: 14, fontWeight: 600, marginBottom: 16 }}>
        {total} contributions in the last 6 months
      </div>

      <div style={{ display: 'flex', gap: 24 }}>
        {/* Day labels */}
        <div style={{ display: 'grid', gridTemplateRows: `repeat(7, 12px)`, gap: 3, marginTop: 20 }}>
          {DAY_LABELS.map((d, i) => (
            <div key={i} style={{ color: 'rgba(255,255,255,0.3)', fontSize: 10, lineHeight: '12px', height: 12 }}>{d}</div>
          ))}
        </div>

        <div style={{ flex: 1 }}>
          {/* Month labels */}
          <div style={{ display: 'grid', gridTemplateColumns: `repeat(${WEEKS}, 12px)`, gap: 3, marginBottom: 6 }}>
            {Array.from({ length: WEEKS }, (_, i) => (
              <div key={i} style={{ color: 'rgba(255,255,255,0.3)', fontSize: 10, height: 12 }}>
                {i % 4 === 0 && i < WEEKS ? MONTH_LABELS[Math.floor(i / 4)] : ''}
              </div>
            ))}
          </div>

          {/* Grid */}
          <div style={{ display: 'grid', gridTemplateColumns: `repeat(${WEEKS}, 12px)`, gridTemplateRows: `repeat(7, 12px)`, gap: 3 }}>
            {Array.from({ length: WEEKS }, (_, w) =>
              Array.from({ length: DAYS }, (_, d) => {
                const v = DATA[w][d]
                return (
                  <div key={`${w}-${d}`}
                    style={{ width: 12, height: 12, borderRadius: 2, background: COLORS[v], cursor: 'pointer', transition: 'opacity 0.15s' }}
                    onMouseEnter={e => setTooltip({ text: v === 0 ? 'No activity' : `${v} ${v === 1 ? 'component' : 'components'}`, x: e.clientX, y: e.clientY })}
                    onMouseLeave={() => setTooltip(null)} />
                )
              })
            )}
          </div>

          {/* Legend */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 12, justifyContent: 'flex-end' }}>
            <span style={{ color: 'rgba(255,255,255,0.3)', fontSize: 11 }}>Less</span>
            {COLORS.map((c, i) => <div key={i} style={{ width: 12, height: 12, borderRadius: 2, background: c }} />)}
            <span style={{ color: 'rgba(255,255,255,0.3)', fontSize: 11 }}>More</span>
          </div>
        </div>
      </div>

      {/* Stats */}
      <div style={{ display: 'flex', gap: 20, marginTop: 16, flexWrap: 'wrap' }}>
        <div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 12 }}>Active days: <strong style={{ color: '#F5F5F0' }}>{days}</strong></div>
        <div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 12 }}>Best streak: <strong style={{ color: '#C9A84C' }}>12 days</strong></div>
        <div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 12 }}>Avg/week: <strong style={{ color: '#F5F5F0' }}>{(total / WEEKS).toFixed(1)}</strong></div>
      </div>

      {/* Tooltip */}
      {tooltip && (
        <div style={{ position: 'fixed', top: tooltip.y - 36, left: tooltip.x - 40, background: '#1a1a1a', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 6, padding: '4px 10px', fontSize: 11, color: '#F5F5F0', pointerEvents: 'none', zIndex: 9999 }}>
          {tooltip.text}
        </div>
      )}
    </div>
  )
}

Component info

CategoryData Display
Frameworkreact
TierFREE
Views1
Copies0

About

GitHub-style contribution heatmap calendar with week labels, tooltip, and activity stats

More from Data Display

'use client'
import { useState, useRef, useCallback, useEffect } from 'react'

export default function ComparisonSlider() {
  const [position, setPosition] = useState(50)
  const [dragging, setDragging] = useState(false)
  const containerRef = useRef
ComparisonSlider
Data Display
'use client'
import { useState } from 'react'

const events = [
  { id: 1, icon: '🚀', title: 'Wave 7 generation complete', desc: '57 new components published across 14 categories', time: '14:32', type: 'success', day: 'Today' },
  { id: 2, icon: '👥
TimelineActivity
Data Display