Untitled

 avatar
unknown
plain_text
15 days ago
6.0 kB
3
Indexable
import { Clock } from "lucide-react";
import React from "react";
import { AnimatePresence, motion } from "framer-motion";

const TimePicker = () => {
  const [timeInput, setTimeInput] = React.useState(
    new Date().toLocaleTimeString("en-US", { hour12: true }).slice(0, 8)
  );
  const [suffix, setSuffix] = React.useState<"AM" | "PM">(
    new Date().getHours() >= 12 ? "PM" : "AM"
  );
  const [hour, setHour] = React.useState(new Date().getHours() % 12 || 12);
  const [minute, setMinute] = React.useState(new Date().getMinutes());
  const [second, setSecond] = React.useState(new Date().getSeconds());
  const [showSelector, setShowSelector] = React.useState(false);

  const formatTimeInput = (value: string): string => {
    const digitsOnly = value.replace(/[^0-9]/g, "");
    const segments = [];
    for (let i = 0; i < digitsOnly.length; i += 2) {
      segments.push(digitsOnly.slice(i, i + 2));
    }
    return segments.join(":").slice(0, 8);
  };

  const validateTime = (value: string): boolean => {
    const regex = /^([0-9]{1,2}):([0-5][0-9]):([0-5][0-9])$/; // Regex for HH:MM:SS format
    if (!regex.test(value)) return false;

    const [h, m, s] = value.split(":").map(Number);

    // Check hour, minute, and second constraints
    if (h < 1 || h > 12 || m < 0 || m > 59 || s < 0 || s > 59) {
      return false;
    }

    return true;
  };

  const changeTime = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = formatTimeInput(e.target.value);
    setTimeInput(value);

    if (validateTime(value)) {
      const [h, m, s] = value.split(":").map(Number);
      setHour(h);
      setMinute(m);
      setSecond(s);
      setSuffix(h >= 12 ? "PM" : "AM");
    }
  };

  const toggleSuffix = () => {
    const newSuffix = suffix === "AM" ? "PM" : "AM";
    setSuffix(newSuffix);

    const updatedHour = (hour % 12) + (newSuffix === "PM" ? 12 : 0);
    setHour(updatedHour);
    setTimeInput(
      `${String(updatedHour % 12 || 12).padStart(2, "0")}:${String(minute).padStart(2, "0")}:${String(second).padStart(2, "0")}`
    );
  };

  const generateValues = (start: number, end: number) =>
    Array.from({ length: end - start + 1 }, (_, i) => i + start);

  const toggleSelector = () => setShowSelector(!showSelector);

  return (
    <div className="flex flex-col space-y-4 border border-gray-300 rounded-md px-2 py-1 m-4">
      <div className="flex items-center space-x-2 justify-between">
        <input
          aria-label="Time"
          type="text"
          value={timeInput}
          onChange={changeTime}
          maxLength={8}
          className="flex-grow rounded-md px-2 py-1 text-sm outline-none border-b"
        />
        <div className="flex items-center space-x-2">
          <button
            onClick={toggleSuffix}
            className="text-sm font-semibold border px-2 py-1 rounded-md"
          >
            {suffix}
          </button>
          <button
            aria-label="Toggle clock"
            onClick={toggleSelector}
            className="flex items-center justify-center w-8 h-8 border rounded-md"
          >
            <Clock size={16} />
          </button>
        </div>
      </div>
      <AnimatePresence>
        {showSelector && (
          <motion.div
            initial={{ opacity: 0, y: -10 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -10 }}
            className="relative"
          >
            <div className="grid grid-cols-3 gap-2">
              <p className="mb-2 text-sm font-semibold sticky top-0 bg-white py-1">
                Hours
              </p>
              <p className="mb-2 text-sm font-semibold sticky top-0 bg-white py-2">
                Minutes
              </p>
              <p className="mb-2 text-sm font-semibold sticky top-0 bg-white py-2">
                Seconds
              </p>
            </div>
            <div className="grid grid-cols-3 gap-4 h-96 overflow-y-scroll">
              <div>
                <div className="grid grid-cols-1 gap-2">
                  {generateValues(1, 12).map((h) => (
                    <button
                      key={h}
                      className={`px-3 py-1 text-sm border rounded-md ${
                        h === hour ? "bg-blue-500 text-white" : "bg-gray-100"
                      }`}
                      onClick={() => setHour(h)}
                    >
                      {String(h).padStart(2, "0")}
                    </button>
                  ))}
                </div>
              </div>

              <div>
                <div className="grid grid-cols-1 gap-2">
                  {generateValues(0, 59).map((m) => (
                    <button
                      key={m}
                      className={`px-3 py-1 text-sm border rounded-md ${
                        m === minute ? "bg-blue-500 text-white" : "bg-gray-100"
                      }`}
                      onClick={() => setMinute(m)}
                    >
                      {String(m).padStart(2, "0")}
                    </button>
                  ))}
                </div>
              </div>

              <div>
                <div className="grid grid-cols-1 gap-2">
                  {generateValues(0, 59).map((s) => (
                    <button
                      key={s}
                      className={`px-3 py-1 text-sm border rounded-md ${
                        s === second ? "bg-blue-500 text-white" : "bg-gray-100"
                      }`}
                      onClick={() => setSecond(s)}
                    >
                      {String(s).padStart(2, "0")}
                    </button>
                  ))}
                </div>
              </div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
};

export default TimePicker;
Leave a Comment