UNPKG

pomo-tui

Version:

> A beautiful terminal-based Pomodoro timer built with React Ink

122 lines (121 loc) 4.93 kB
import React, { useState, useEffect } from 'react'; import { Box, Text, useInput } from 'ink'; import Gradient from 'ink-gradient'; import BigText from 'ink-big-text'; import { cycleGradient, cyclePreviousGradient } from './colors.js'; const Timer = ({ initialSeconds = 0, onActiveChange, stopTime: initialStopTime, initialTheme = 'mind', onComplete, onPause, isCountdown = false, showStopTime = false, }) => { const [seconds, setSeconds] = useState(initialSeconds); const [isActive, setIsActive] = useState(false); const [stopTime, setStopTime] = useState(initialStopTime); const [theme, setTheme] = useState(initialTheme); useEffect(() => { let interval = null; if (isActive) { interval = setInterval(() => { setSeconds(seconds => { if (stopTime && seconds >= stopTime) { setIsActive(false); onActiveChange?.(false); return seconds; } const newSeconds = isCountdown ? seconds - 1 : seconds + 1; if (isCountdown && newSeconds <= 0) { setIsActive(false); onActiveChange?.(false); onComplete?.(); return 0; } return newSeconds; }); }, 1000); } return () => { if (interval) clearInterval(interval); }; }, [isActive, stopTime, onActiveChange]); const formatTime = () => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; return [ hours.toString().padStart(2, '0'), minutes.toString().padStart(2, '0'), secs.toString().padStart(2, '0'), ].join(':'); }; const toggle = () => { const newActive = !isActive; setIsActive(newActive); onActiveChange?.(newActive); if (!newActive && onPause) { onPause(seconds); } }; const reset = () => { setSeconds(0); setIsActive(false); onActiveChange?.(false); }; useInput((input, key) => { if (input === 'p' || input === ' ') { toggle(); } if (input === 'r') { reset(); } if (!isActive && !isCountdown && (key.leftArrow || key.rightArrow) && stopTime !== undefined) { const step = 60; // Change by 1 minute const currentValue = stopTime || 0; const newValue = key.leftArrow ? Math.max(0, currentValue - step) : currentValue + step; setStopTime(newValue); } if (input === '[' || input === ']') { setTheme(input === '[' ? cyclePreviousGradient(theme) : cycleGradient(theme)); } }); return (React.createElement(Box, { flexDirection: "column", padding: 1, alignItems: "center", justifyContent: "center" }, showStopTime && stopTime !== undefined && (React.createElement(Box, { marginBottom: 1, alignItems: "center" }, React.createElement(Text, null, "Stop Time: ", Math.floor((stopTime || 0) / 60), " minutes (\u2190 \u2192)"))), !isCountdown && (React.createElement(Box, { marginBottom: 1, alignItems: "center" }, React.createElement(Text, null, "Theme: ", theme, " ([ ])"))), React.createElement(Box, { marginBottom: 1, alignItems: "center" }, React.createElement(Gradient, { name: theme }, React.createElement(BigText, { font: "chrome", text: formatTime() }))), React.createElement(Box, { alignItems: "center" }, React.createElement(Text, null, "Status:", ' ', React.createElement(Text, { color: isActive ? 'green' : 'yellow' }, isActive ? 'Running' : 'Paused'))), React.createElement(Box, { marginTop: 1, alignItems: "center" }, React.createElement(Text, null, "Press", ' ', React.createElement(Text, { bold: true, color: "blue" }, isCountdown ? 'space' : 'p'), ' ', "to ", isActive ? 'pause' : 'play', !isCountdown && (React.createElement(React.Fragment, null, ",", ' ', React.createElement(Text, { bold: true, color: "red" }, "r"), ' ', "to reset")))))); }; // Example app wrapper (you can use this to run the timer) const App = () => { return React.createElement(Timer, null); }; export { Timer }; export default App;