← Components/Utility

PomodoroTimer

pomodorotimer-1779388706163.tsx
'use client';
import { useState, useEffect, useRef } from 'react';

export default function PomodoroTimer() {
  const WORK = 25 * 60, BREAK = 5 * 60;
  const [seconds, setSeconds] = useState(WORK);
  const [running, setRunning] = useState(false);
  const [isWork, setIsWork] = useState(true);
  const [sessions, setSessions] = useState(0);
  const intervalRef = useRef(null);

  useEffect(() => {
    if (running) {
      intervalRef.current = setInterval(() => {
        setSeconds(s => {
          if (s <= 1) {
            const nextWork = !isWork;
            setIsWork(nextWork);
            if (!nextWork) setSessions(n => n + 1);
            setSeconds(nextWork ? WORK : BREAK);
            return nextWork ? WORK : BREAK;
          }
          return s - 1;
        });
      }, 1000);
    } else clearInterval(intervalRef.current);
    return () => clearInterval(intervalRef.current);
  }, [running, isWork]);

  const min = Math.floor(seconds / 60), sec = seconds % 60;
  const total = isWork ? WORK : BREAK;
  const pct = 1 - seconds / total;
  const r = 72, circ = 2 * Math.PI * r;
  const dash = circ * pct;

  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', textAlign: 'center', padding: '24px' }}>
      <div style={{ display: 'flex', gap: '8px', justifyContent: 'center', marginBottom: '20px' }}>
        {['Focus', 'Break'].map((m, i) => (
          <button key={m} onClick={() => { setIsWork(i === 0); setSeconds(i === 0 ? WORK : BREAK); setRunning(false); }} style={{
            background: ((isWork && i === 0) || (!isWork && i === 1)) ? 'rgba(201,168,76,0.15)' : 'transparent',
            border: `1px solid ${((isWork && i === 0) || (!isWork && i === 1)) ? 'rgba(201,168,76,0.3)' : 'rgba(255,255,255,0.1)'}`,
            borderRadius: '8px', padding: '6px 16px',
            color: ((isWork && i === 0) || (!isWork && i === 1)) ? '#C9A84C' : 'rgba(245,245,240,0.4)',
            fontSize: '13px', cursor: 'pointer',
          }}>{m}</button>
        ))}
      </div>
      <svg width="180" height="180" style={{ display: 'block', margin: '0 auto 16px' }}>
        <circle cx="90" cy="90" r={r} fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="8" />
        <circle cx="90" cy="90" r={r} fill="none"
          stroke={isWork ? '#C9A84C' : '#6366f1'} strokeWidth="8" strokeLinecap="round"
          strokeDasharray={`${dash} ${circ}`}
          transform="rotate(-90 90 90)"
          style={{ transition: 'stroke-dasharray 1s linear', filter: `drop-shadow(0 0 8px ${isWork ? '#C9A84C' : '#6366f1'})` }}
        />
        <text x="90" y="85" textAnchor="middle" fill="#F5F5F0" fontSize="32" fontWeight="800" fontFamily="monospace">
          {String(min).padStart(2,'0')}:{String(sec).padStart(2,'0')}
        </text>
        <text x="90" y="110" textAnchor="middle" fill="rgba(245,245,240,0.4)" fontSize="13" fontFamily="system-ui">
          {isWork ? 'Focus Time' : 'Break Time'}
        </text>
      </svg>
      <button onClick={() => setRunning(r => !r)} style={{
        background: running ? 'rgba(239,68,68,0.15)' : 'linear-gradient(135deg, #C9A84C, #e8c96d)',
        border: running ? '1px solid rgba(239,68,68,0.3)' : 'none',
        borderRadius: '50px', padding: '12px 40px',
        color: running ? '#ef4444' : '#0A0A0A',
        fontSize: '15px', fontWeight: 700, cursor: 'pointer', marginBottom: '16px',
      }}>{running ? '⏸ Pause' : '▶ Start'}</button>
      <div style={{ display: 'flex', justifyContent: 'center', gap: '6px' }}>
        {Array.from({ length: 4 }, (_, i) => (
          <div key={i} style={{ width: '10px', height: '10px', borderRadius: '50%', background: i < sessions % 4 ? '#C9A84C' : 'rgba(255,255,255,0.1)' }} />
        ))}
      </div>
      <p style={{ color: 'rgba(245,245,240,0.35)', fontSize: '12px', marginTop: '8px' }}>{sessions} sessions completed</p>
    </div>
  );
}

Component info

CategoryUtility
Frameworkreact
TierFREE
Views0
Copies0

About

Pomodoro focus timer with sessions counter

More from Utility

'use client'
import { useState } from 'react'

const PRESETS = [
  { name: 'Gold', primary: '#C9A84C', bg: '#0A0A0A', accent: '#6366f1' },
  { name: 'Neon', primary: '#22d3ee', bg: '#030712', accent: '#a78bfa' },
  { name: 'Crimson', primary: '#ef444
ThemeCustomizer
Utility
'use client'
import { useState, useCallback } from 'react'

interface UploadedFile {
  id: number
  name: string
  size: number
  type: string
  progress: number
  done: boolean
  error?: string
}

const ALLOWED = ['image/png', 'image/jpeg', 'image/w
FileUploadZone
Utility
'use client'
import { useState } from 'react'

interface ClipItem {
  id: number
  content: string
  type: 'text' | 'code' | 'url' | 'email'
  pinned: boolean
  time: number
}

const INITIAL: ClipItem[] = [
  { id: 1, content: 'npx empire-ui-mcp --st
ClipboardManager
Utility
'use client';
import { useState } from 'react';

export default function NeonToggle({ label = "Enable AI Mode", defaultOn = false, color = "#6366f1" }) {
  const [on, setOn] = useState(defaultOn);
  const [pressing, setPressing] = useState(false);

 
NeonToggle
Utility