pomo-tui
Version:
> A beautiful terminal-based Pomodoro timer built with React Ink
122 lines (121 loc) • 4.93 kB
JavaScript
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;