How to create a customized hours and minutes picker using the react.js

Using the below code, we can create a customized time picker with dropdown based on the selection

import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import './CustomTimePicker.css'; // Import custom CSS for styling


interface CustomTimePickerProps {
  value: number;
  onChange: (value: number) => void;
  disabled: boolean;
}


const CustomTimePicker: React.FC<CustomTimePickerProps> = ({ value, onChange, disabled }) => {
  const [hours, setHours] = useState<number>(0);
  const [minutes, setMinutes] = useState<number>(0);
  const [showDropdown, setShowDropdown] = useState(false);
  const [activeDropdown, setActiveDropdown] = useState<'hours' | 'minutes' | null>(null);
  const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
  const inputRef = useRef<HTMLInputElement>(null);
  const dropdownRef = useRef<HTMLDivElement>(null);


  useEffect(() => {
    const totalMinutes = Math.max(0, value);
    const h = Math.floor(totalMinutes / 60);
    const m = totalMinutes % 60;
    setHours(h);
    setMinutes(m);
  }, [value]);


  const handleTimeChange = (h: number, m: number) => {
    setHours(h);
    setMinutes(m);
    onChange(h * 60 + m);
    setShowDropdown(false);
  };


  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const [h, m] = e.target.value.split(':').map(Number);
    if (Number.isInteger(h) && Number.isInteger(m)) {
      handleTimeChange(h, m);
    }
  };


  const handleBlur = () => {
    if (inputRef.current) {
      const [h, m] = inputRef.current.value.split(':').map(Number);
      if (!Number.isInteger(h) || !Number.isInteger(m)) {
        inputRef.current.value = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
      }
    }
  };


  const updateDropdownPosition = () => {
    if (!dropdownRef.current || !inputRef.current) return;
    const inputRect = inputRef.current.getBoundingClientRect();
    setDropdownStyle({
      top: `${inputRect.bottom + window.scrollY+4}px`,
      left: `${inputRect.left + window.scrollX}px`,
      width: `${inputRect.width}px`,
    });
  };


  useEffect(() => {
    if (showDropdown) {
      updateDropdownPosition();
      const handleClickOutside = (event: MouseEvent) => {
        if (
          dropdownRef.current &&
          !dropdownRef.current.contains(event.target as Node) &&
          inputRef.current &&
          !inputRef.current.contains(event.target as Node)
        ) {
          setShowDropdown(false);
        }
      };


      document.addEventListener('mousedown', handleClickOutside);
      return () => {
        document.removeEventListener('mousedown', handleClickOutside);
      };
    }
  }, [showDropdown]);


  const handleClick = (event: React.MouseEvent<HTMLInputElement>) => {
    if (!disabled) {
      const { offsetWidth } = inputRef.current!;
      const clickPosition = event.nativeEvent.offsetX;


      // Calculate position to determine if clicking on hours or minutes
      if (clickPosition < offsetWidth / 2) {
        setActiveDropdown('hours');
      } else {
        setActiveDropdown('minutes');
      }


      setShowDropdown(!showDropdown);
      updateDropdownPosition();
    }
  };


  return (
    <div className="custom-time-picker">
      <div className="time-picker-input">
        <input
          type="text"
          value={`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`}
          ref={inputRef}
          onChange={handleInputChange}
          onBlur={handleBlur}
          onClick={handleClick}
          className={`time-picker-display ${disabled ? 'disabled' : ''}`}
          readOnly={disabled}
        />
      </div>
      {showDropdown &&
        ReactDOM.createPortal(
          <div className="dropdown" style={dropdownStyle} ref={dropdownRef}>
            <div className="dropdown-buttons">
              <button
                className={`dropdown-button ${activeDropdown === 'hours' ? 'active' : ''}`}
                onClick={() => setActiveDropdown('hours')}
              >
                HH
              </button>
              <button
                className={`dropdown-button ${activeDropdown === 'minutes' ? 'active' : ''}`}
                onClick={() => setActiveDropdown('minutes')}
              >
                MM
              </button>
            </div>
            {activeDropdown === 'hours' && (
              <div className="dropdown-options">
                {Array.from({ length: 24 }, (_, h) => (
                  <div
                    key={h}
                    className={`dropdown-option ${h === hours ? 'selected' : ''}`}
                    onClick={() => handleTimeChange(h, minutes)}
                  >
                    {h.toString().padStart(2, '0')}
                  </div>
                ))}
              </div>
            )}
            {activeDropdown === 'minutes' && (
              <div className="dropdown-options">
                {Array.from({ length: 60 }, (_, m) => (
                  <div
                    key={m}
                    className={`dropdown-option ${m === minutes ? 'selected' : ''}`}
                    onClick={() => handleTimeChange(hours, m)}
                  >
                    {m.toString().padStart(2, '0')}
                  </div>
                ))}
              </div>
            )}
          </div>,
          document.body
        )}
    </div>
  );
};


export default CustomTimePicker;


Leave a comment

Your email address will not be published. Required fields are marked *