116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import * as React from 'react';
|
||
|
|
import { Clock } from 'lucide-react';
|
||
|
|
import { format } from 'date-fns';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import DatePickerLib from 'react-datepicker';
|
||
|
|
import 'react-datepicker/dist/react-datepicker.css';
|
||
|
|
|
||
|
|
interface TimePickerProps {
|
||
|
|
time: string; // HH:mm format (e.g., "09:00")
|
||
|
|
setTime: (time: string) => void;
|
||
|
|
label?: string;
|
||
|
|
isDark?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function TimePicker({ time, setTime, label, isDark = false }: TimePickerProps) {
|
||
|
|
const [isOpen, setIsOpen] = React.useState(false);
|
||
|
|
const wrapperRef = React.useRef<HTMLDivElement>(null);
|
||
|
|
|
||
|
|
// Convert HH:mm string to Date object for the time picker
|
||
|
|
const timeValue = React.useMemo(() => {
|
||
|
|
if (!time) return null;
|
||
|
|
const [hours, minutes] = time.split(':').map(Number);
|
||
|
|
const date = new Date();
|
||
|
|
date.setHours(hours || 9);
|
||
|
|
date.setMinutes(minutes || 0);
|
||
|
|
date.setSeconds(0);
|
||
|
|
return date;
|
||
|
|
}, [time]);
|
||
|
|
|
||
|
|
// Handle time change from the picker
|
||
|
|
const handleTimeChange = (date: Date | null) => {
|
||
|
|
if (date) {
|
||
|
|
const hours = date.getHours().toString().padStart(2, '0');
|
||
|
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
|
|
setTime(`${hours}:${minutes}`);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// 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]);
|
||
|
|
|
||
|
|
// Format display time
|
||
|
|
const displayTime = timeValue
|
||
|
|
? format(timeValue, 'h:mm a') // e.g., "9:00 AM"
|
||
|
|
: 'Select time';
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-2">
|
||
|
|
{label && (
|
||
|
|
<label className={cn(
|
||
|
|
"text-sm font-medium",
|
||
|
|
isDark ? "text-gray-300" : "text-gray-700"
|
||
|
|
)}>
|
||
|
|
{label}
|
||
|
|
</label>
|
||
|
|
)}
|
||
|
|
<div className="relative" ref={wrapperRef}>
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="outline"
|
||
|
|
onClick={() => setIsOpen(!isOpen)}
|
||
|
|
className={cn(
|
||
|
|
"w-full justify-start text-left font-normal h-12 text-base",
|
||
|
|
!timeValue && "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"
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<Clock className="mr-2 h-5 w-5" />
|
||
|
|
{displayTime}
|
||
|
|
</Button>
|
||
|
|
{isOpen && (
|
||
|
|
<div className={cn(
|
||
|
|
"absolute z-[9999] mt-2 rounded-lg shadow-lg border",
|
||
|
|
isDark
|
||
|
|
? "bg-gray-800 border-gray-700"
|
||
|
|
: "bg-white border-gray-200"
|
||
|
|
)}>
|
||
|
|
<DatePickerLib
|
||
|
|
selected={timeValue}
|
||
|
|
onChange={handleTimeChange}
|
||
|
|
showTimeSelect
|
||
|
|
showTimeSelectOnly
|
||
|
|
timeIntervals={15}
|
||
|
|
timeCaption="Time"
|
||
|
|
dateFormat="h:mm aa"
|
||
|
|
inline
|
||
|
|
className="time-picker"
|
||
|
|
wrapperClassName="time-picker-wrapper"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|