From 6cb006cc136f1fc5c83537cc30c64d223d1755e4 Mon Sep 17 00:00:00 2001 From: Solstice Date: Tue, 9 Jun 2026 00:22:18 -0700 Subject: feat: frontend view, state management, and user interface --- src/components/TimerView.tsx | 236 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 src/components/TimerView.tsx (limited to 'src/components/TimerView.tsx') diff --git a/src/components/TimerView.tsx b/src/components/TimerView.tsx new file mode 100644 index 0000000..b56e6a8 --- /dev/null +++ b/src/components/TimerView.tsx @@ -0,0 +1,236 @@ +import { invoke } from '@tauri-apps/api/core'; +import { useTimerStore, TimerPhase } from '../store/timerStore'; +import { useTaskStore } from '../store/taskStore'; + +const RING_SIZE = 280; +const STROKE = 8; +const RADIUS = (RING_SIZE - STROKE) / 2; +const CIRCUMFERENCE = 2 * Math.PI * RADIUS; + +function phaseLabel(phase: TimerPhase): string { + switch (phase) { + case 'work': return 'Focus'; + case 'short_break': return 'Short Break'; + case 'long_break': return 'Long Break'; + } +} + +function phaseColor(phase: TimerPhase): string { + switch (phase) { + case 'work': return 'var(--brass)'; + case 'short_break': return 'var(--positive)'; + case 'long_break': return 'var(--info)'; + } +} + +function eyebrowText(phase: TimerPhase, sessionCount: number): string { + if (phase === 'work') { + return `WORK ยท SESSION ${sessionCount + 1}`; + } + if (phase === 'short_break') return 'SHORT BREAK'; + return 'LONG BREAK'; +} + +function formatTime(secs: number): string { + const m = Math.floor(secs / 60); + const s = secs % 60; + return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; +} + +export function TimerView() { + const { phase, remainingSecs, totalSecs, running, sessionCount, currentTaskId } = + useTimerStore(); + const tasks = useTaskStore((s) => s.tasks); + + const currentTask = tasks.find((t) => t.id === currentTaskId) ?? null; + + const progress = totalSecs > 0 ? remainingSecs / totalSecs : 1; + const dashOffset = CIRCUMFERENCE * (1 - progress); + const arcColor = phaseColor(phase); + + const handleStart = () => invoke('start_timer'); + const handlePause = () => invoke('pause_timer'); + const handleSkip = () => invoke('skip_phase'); + const handleReset = () => invoke('reset_timer'); + + return ( +
+ {/* Eyebrow */} + + {eyebrowText(phase, sessionCount)} + + + {/* Circular ring */} +
+ + {/* Background track */} + + {/* Progress arc */} + + + + {/* Center content */} +
+ + {formatTime(remainingSecs)} + + + {phaseLabel(phase)} + +
+
+ + {/* Current task name */} + {currentTask && ( + + {currentTask.name} + + )} + + {/* Controls */} +
+ + + Skip + Reset +
+
+ ); +} + +function GhostButton({ + onClick, + children, +}: { + onClick: () => void; + children: React.ReactNode; +}) { + return ( + + ); +} -- cgit v1.3-2-g0d8e