website/components/ClockDurationPicker.tsx

178 lines
6.1 KiB
TypeScript
Raw Normal View History

'use client';
import * as React from 'react';
import { Timer } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
interface ClockDurationPickerProps {
duration: number; // Duration in minutes
setDuration: (duration: number) => void;
label?: string;
isDark?: boolean;
options?: number[]; // Optional custom duration options
}
export function ClockDurationPicker({
duration,
setDuration,
label,
isDark = false,
options = [15, 30, 45, 60, 75, 90, 105, 120]
}: ClockDurationPickerProps) {
const [isOpen, setIsOpen] = React.useState(false);
const wrapperRef = React.useRef<HTMLDivElement>(null);
// Close picker when clicking outside
React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
// Handle duration selection
const handleDurationClick = (selectedDuration: number) => {
setDuration(selectedDuration);
setIsOpen(false);
};
// Calculate position for clock numbers
const getClockPosition = (index: number, total: number, radius: number = 130) => {
const angle = (index * 360) / total - 90; // Start from top (-90 degrees)
const radian = (angle * Math.PI) / 180;
const x = Math.cos(radian) * radius;
const y = Math.sin(radian) * radius;
return { x, y };
};
// Format duration display
const formatDuration = (minutes: number) => {
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
};
const displayDuration = duration ? formatDuration(duration) : 'Select duration';
return (
<div className="space-y-2">
{label && (
<label className={cn(
"text-sm font-semibold",
isDark ? "text-gray-300" : "text-gray-700"
)}>
{label}
</label>
)}
<div className="relative w-full" ref={wrapperRef}>
<Button
type="button"
variant="outline"
onClick={() => setIsOpen(!isOpen)}
className={cn(
"w-full justify-start text-left font-normal h-12 text-base",
!duration && "text-muted-foreground",
isDark
? "bg-gray-800 border-gray-600 text-white hover:bg-gray-700"
: "bg-white border-gray-300 text-gray-900 hover:bg-gray-50"
)}
>
<Timer className="mr-2 h-5 w-5" />
{displayDuration}
</Button>
{isOpen && (
<div className={cn(
"absolute z-[9999] top-full left-0 right-0 mt-2 rounded-lg shadow-xl border p-6 w-[420px] mx-auto overflow-visible",
isDark
? "bg-gray-800 border-gray-700"
: "bg-white border-gray-200"
)}>
{/* Clock face */}
<div className="relative w-[360px] h-[360px] mx-auto my-6 overflow-visible">
{/* Clock circle */}
<div className={cn(
"absolute inset-0 rounded-full border-2",
isDark ? "border-gray-600" : "border-gray-300"
)} />
{/* Center dot */}
<div className={cn(
"absolute top-1/2 left-1/2 w-2 h-2 rounded-full -translate-x-1/2 -translate-y-1/2 z-10",
isDark ? "bg-gray-400" : "bg-gray-600"
)} />
{/* Duration options arranged in a circle */}
{options.map((option, index) => {
const { x, y } = getClockPosition(index, options.length, 130);
const isSelected = duration === option;
return (
<button
key={option}
type="button"
onClick={() => handleDurationClick(option)}
className={cn(
"absolute w-16 h-16 rounded-full flex items-center justify-center text-xs font-semibold transition-all z-20 whitespace-nowrap",
isSelected
? isDark
? "bg-blue-600 text-white scale-110 shadow-lg ring-2 ring-blue-400"
: "bg-blue-600 text-white scale-110 shadow-lg ring-2 ring-blue-400"
: isDark
? "bg-gray-700 text-gray-200 hover:bg-gray-600 hover:scale-105"
: "bg-gray-100 text-gray-700 hover:bg-gray-200 hover:scale-105"
)}
style={{
left: `calc(50% + ${x}px)`,
top: `calc(50% + ${y}px)`,
transform: 'translate(-50%, -50%)',
}}
title={`${option} minutes`}
>
{formatDuration(option)}
</button>
);
})}
</div>
{/* Quick select buttons for common durations */}
<div className="flex gap-2 mt-4 justify-center flex-wrap">
{[30, 60, 90, 120].map((quickDuration) => (
<button
key={quickDuration}
type="button"
onClick={() => handleDurationClick(quickDuration)}
className={cn(
"px-3 py-1.5 rounded text-sm font-medium transition-colors",
duration === quickDuration
? isDark
? "bg-blue-600 text-white"
: "bg-blue-600 text-white"
: isDark
? "bg-gray-700 text-gray-300 hover:bg-gray-600"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
)}
>
{formatDuration(quickDuration)}
</button>
))}
</div>
</div>
)}
</div>
</div>
);
}