149 lines
4.2 KiB
TypeScript
149 lines
4.2 KiB
TypeScript
|
|
import React, { useEffect, useRef, useState } from "react";
|
||
|
|
import {
|
||
|
|
bgInputPrimaryColor,
|
||
|
|
mobileBorders,
|
||
|
|
textColor,
|
||
|
|
} from "../../data/getColorBasedOnMode";
|
||
|
|
import { getSizeStyles } from "../../data/getInputSizes";
|
||
|
|
import { checkIsMobile } from "../../utils/checkIsMobile";
|
||
|
|
|
||
|
|
interface TimePickerProps {
|
||
|
|
value?: string;
|
||
|
|
onChange?: (value: string) => void;
|
||
|
|
label?: string;
|
||
|
|
disabled?: boolean;
|
||
|
|
className?: string;
|
||
|
|
size?: "small" | "medium" | "large";
|
||
|
|
}
|
||
|
|
|
||
|
|
const TimePicker: React.FC<TimePickerProps> = ({
|
||
|
|
onChange,
|
||
|
|
label = "ساعت",
|
||
|
|
disabled = false,
|
||
|
|
className = "",
|
||
|
|
size = "medium",
|
||
|
|
}) => {
|
||
|
|
const pickerRef = useRef<HTMLDivElement>(null!);
|
||
|
|
const hourRef = useRef<HTMLDivElement>(null!);
|
||
|
|
const minuteRef = useRef<HTMLDivElement>(null!);
|
||
|
|
|
||
|
|
const [isOpen, setIsOpen] = useState(false);
|
||
|
|
|
||
|
|
const now = new Date();
|
||
|
|
const initialHour = now.getHours();
|
||
|
|
const initialMinute = now.getMinutes();
|
||
|
|
|
||
|
|
const [selectedHour, setSelectedHour] = useState<number>(initialHour);
|
||
|
|
const [selectedMinute, setSelectedMinute] = useState<number>(initialMinute);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handleClickOutside = (event: MouseEvent) => {
|
||
|
|
if (
|
||
|
|
pickerRef.current &&
|
||
|
|
!pickerRef.current.contains(event.target as Node)
|
||
|
|
) {
|
||
|
|
setIsOpen(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
document.addEventListener("mousedown", handleClickOutside);
|
||
|
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const timeString = `${String(selectedHour).padStart(2, "0")}:${String(
|
||
|
|
selectedMinute
|
||
|
|
).padStart(2, "0")}:00`;
|
||
|
|
|
||
|
|
if (onChange) {
|
||
|
|
onChange(timeString);
|
||
|
|
}
|
||
|
|
}, [selectedHour, selectedMinute, onChange]);
|
||
|
|
|
||
|
|
const scrollToSelected = (
|
||
|
|
ref: React.RefObject<HTMLDivElement>,
|
||
|
|
selected: number
|
||
|
|
) => {
|
||
|
|
const el = ref.current?.querySelector(`[data-value="${selected}"]`);
|
||
|
|
el?.scrollIntoView({ behavior: "auto", block: "center" });
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (isOpen) {
|
||
|
|
scrollToSelected(hourRef, selectedHour);
|
||
|
|
scrollToSelected(minuteRef, selectedMinute);
|
||
|
|
}
|
||
|
|
}, [isOpen]);
|
||
|
|
|
||
|
|
const renderColumn = (
|
||
|
|
title: string,
|
||
|
|
range: number,
|
||
|
|
selected: number,
|
||
|
|
setSelected: (val: number) => void,
|
||
|
|
ref: React.RefObject<HTMLDivElement>
|
||
|
|
) => (
|
||
|
|
<div className="h-60 w-30 flex flex-col items-center">
|
||
|
|
<div className={`py-1 text-xs font-semibold ${textColor}`}>{title}</div>
|
||
|
|
<div
|
||
|
|
ref={ref}
|
||
|
|
className="flex-1 overflow-x-hidden w-full overflow-y-auto overflow-hidden xs:scrollbar-hide text-center"
|
||
|
|
>
|
||
|
|
<ul className="space-y-1">
|
||
|
|
{Array.from({ length: range }, (_, i) => (
|
||
|
|
<li
|
||
|
|
key={i}
|
||
|
|
data-value={i}
|
||
|
|
className={`py-2 cursor-pointer ${
|
||
|
|
i === selected
|
||
|
|
? "text-amber-900 dark:text-amber-200 font-bold scale-105"
|
||
|
|
: textColor
|
||
|
|
}`}
|
||
|
|
onClick={() => setSelected(i)}
|
||
|
|
>
|
||
|
|
{String(i).padStart(2, "0")}
|
||
|
|
</li>
|
||
|
|
))}
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={`relative w-28 justify-center ${className}`}
|
||
|
|
ref={pickerRef}
|
||
|
|
>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<label className={`text-sm ${textColor}`}>{label}</label>
|
||
|
|
<input
|
||
|
|
readOnly
|
||
|
|
value={`${String(selectedHour).padStart(2, "0")}:${String(
|
||
|
|
selectedMinute
|
||
|
|
).padStart(2, "0")}`}
|
||
|
|
onClick={() => setIsOpen(!isOpen)}
|
||
|
|
className={`w-full border rounded-xl bg-white text-sm text-center text-dark-800 shadow-sm focus:border-blue-400 focus:ring focus:ring-blue-200 ${
|
||
|
|
checkIsMobile() && mobileBorders
|
||
|
|
} ${getSizeStyles(size).padding} ${bgInputPrimaryColor} ${textColor}`}
|
||
|
|
disabled={disabled}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{isOpen && (
|
||
|
|
<div
|
||
|
|
className={`absolute z-50 mt-2 flex rounded-lg shadow-lg text-white text-sm w-40 ${bgInputPrimaryColor}`}
|
||
|
|
>
|
||
|
|
{renderColumn(
|
||
|
|
"دقیقه",
|
||
|
|
60,
|
||
|
|
selectedMinute,
|
||
|
|
setSelectedMinute,
|
||
|
|
minuteRef
|
||
|
|
)}
|
||
|
|
{renderColumn("ساعت", 24, selectedHour, setSelectedHour, hourRef)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default TimePicker;
|