feat(timer): make pomodoro timer prominent in header with tomato styling
- Changed timer button to red/tomato themed pill button - Added red background, border, and hover effects - Shows remaining time when running - Green pulsing indicator when active - Made TomatoIcon slightly larger (22px) for visibility
This commit is contained in:
@@ -1,19 +1,20 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { TimerIcon, PlayIcon, PauseIcon, RefreshIcon, XIcon } from '@/components/ui/Icons';
|
import { TomatoIcon, PlayIcon, PauseIcon, RefreshIcon, XIcon } from '@/components/ui/Icons';
|
||||||
import { playTimerEnd } from '@/lib/sounds';
|
import { playTimerEnd } from '@/lib/sounds';
|
||||||
|
|
||||||
interface PomodoroTimerProps {
|
interface PomodoroTimerProps {
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
|
variant?: 'header' | 'floating';
|
||||||
}
|
}
|
||||||
|
|
||||||
const WORK_MINUTES = 25;
|
const WORK_MINUTES = 25;
|
||||||
const SHORT_BREAK_MINUTES = 5;
|
const SHORT_BREAK_MINUTES = 5;
|
||||||
const LONG_BREAK_MINUTES = 15;
|
const LONG_BREAK_MINUTES = 15;
|
||||||
|
|
||||||
export default function PomodoroTimer({ isExpanded, onToggle }: PomodoroTimerProps) {
|
export default function PomodoroTimer({ isExpanded, onToggle, variant = 'header' }: PomodoroTimerProps) {
|
||||||
const [mode, setMode] = useState<'work' | 'shortBreak' | 'longBreak'>('work');
|
const [mode, setMode] = useState<'work' | 'shortBreak' | 'longBreak'>('work');
|
||||||
const [timeLeft, setTimeLeft] = useState(WORK_MINUTES * 60);
|
const [timeLeft, setTimeLeft] = useState(WORK_MINUTES * 60);
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
@@ -124,22 +125,56 @@ export default function PomodoroTimer({ isExpanded, onToggle }: PomodoroTimerPro
|
|||||||
|
|
||||||
// Compact view (icon only)
|
// Compact view (icon only)
|
||||||
if (!isExpanded) {
|
if (!isExpanded) {
|
||||||
|
if (variant === 'floating') {
|
||||||
|
// Floating Action Button for mobile
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
className="relative p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
className="relative w-14 h-14 bg-indigo-600 hover:bg-indigo-700 text-white rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-105 active:scale-95"
|
||||||
aria-label="Open timer"
|
aria-label="Open timer"
|
||||||
>
|
>
|
||||||
<TimerIcon size={20} />
|
<TomatoIcon size={28} className="text-white" />
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
<span className="absolute top-1 right-1 w-2 h-2 bg-indigo-500 rounded-full animate-pulse" />
|
<span className="absolute -top-1 -right-1 w-4 h-4 bg-green-500 rounded-full border-2 border-white dark:border-gray-900 animate-pulse" />
|
||||||
|
)}
|
||||||
|
{/* Time badge */}
|
||||||
|
{isRunning && (
|
||||||
|
<span className="absolute -bottom-1 bg-gray-900 text-white text-xs px-2 py-0.5 rounded-full">
|
||||||
|
{Math.ceil(timeLeft / 60)}m
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header variant - make it pop!
|
||||||
return (
|
return (
|
||||||
<div className="absolute right-0 top-full mt-2 w-72 bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50">
|
<button
|
||||||
|
onClick={onToggle}
|
||||||
|
className="relative flex items-center gap-2 px-3 py-2 bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/30 text-red-600 dark:text-red-400 rounded-full transition-colors border border-red-200 dark:border-red-800"
|
||||||
|
aria-label="Open timer"
|
||||||
|
>
|
||||||
|
<TomatoIcon size={22} />
|
||||||
|
<span className="hidden sm:inline text-sm font-bold">Focus</span>
|
||||||
|
{isRunning && (
|
||||||
|
<>
|
||||||
|
<span className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white dark:border-gray-900 animate-pulse" />
|
||||||
|
<span className="hidden sm:inline text-xs bg-red-100 dark:bg-red-900/40 px-2 py-0.5 rounded-full ml-1">
|
||||||
|
{Math.ceil(timeLeft / 60)}m
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expanded view - position based on variant
|
||||||
|
const positionClasses = variant === 'floating'
|
||||||
|
? "fixed bottom-20 right-4 sm:bottom-auto sm:top-20 sm:right-4 w-[calc(100vw-2rem)] sm:w-80"
|
||||||
|
: "absolute right-0 top-full mt-2 w-72";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${positionClasses} bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 p-4 z-50`}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1247,3 +1247,27 @@ export function ShieldIcon({ className = '', size = 24 }: IconProps) {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function TomatoIcon({ className = '', size = 24 }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{/* Tomato body */}
|
||||||
|
<ellipse cx="12" cy="14" rx="9" ry="8" />
|
||||||
|
{/* Stem */}
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
d="M12 6 L12 3 M12 6 L9 4 M12 6 L15 4 M12 6 L10 8 M12 6 L14 8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user