【问题标题】:React Date Picker color effect on hover and set date rangeReact Date Picker color effect on hover and set date range
【发布时间】:2020-08-01 20:25:28
【问题描述】:

我目前正在使用 React 构建一个日期选择器组件,您可以在其中设置日期范围。 作为灵感和模型,我使用了airbnb react-dates package。 现在,我设法构建了日期选择器的功能,但我真的不知道如何在开始日期和结束日期之间实现所有日期的颜色变化。 因为在airbnb版本中,一旦您设置了开始日期,它会自动将开始日期和鼠标光标之间的所有天数设置为另一种颜色。当同时设置了开始日期和结束日期时,也会出现这种效果(参见附件)。

这是我的代码,我也为每一个改进建议感到高兴:)

import React, { useState, useEffect } from 'react';

//component
function Test() {
  const currentDate = new Date();

  //state for the datepicker month and year heading
  let [datePicker, setDatePicker] = useState({
    currentMonth: currentDate.getMonth(),
    currentYear: currentDate.getFullYear(),
  });

  //state that stores start and end date and keeps track which date is currently selected to be overridden
  let [dateRange, setDateRange] = useState({
    selectStartDate: true,
    selectEndDate: false,
    startDate: null,
    endDate: null,
  });

  //useEffect that colors the current start and end date in a different color
  useEffect(() => {
    let startClass = document.querySelector('.start-date');
    let endClass = document.querySelector('.end-date');

    if (startClass) startClass.classList.remove('start-date');
    if (endClass) endClass.classList.remove('end-date');

    if (dateRange.startDate) {
      let startDateAttribute = getDateFormatted(dateRange.startDate);
      let start = document.querySelectorAll(`[day="${startDateAttribute}"]`)[0];
      if (start) start.classList.add('start-date');
    }
    if (dateRange.endDate) {
      let endDateAttribute = getDateFormatted(dateRange.endDate);
      let end = document.querySelectorAll(`[day="${endDateAttribute}"]`)[0];
      if (end) end.classList.add('end-date');
    }
  }, [dateRange.startDate, dateRange.endDate, datePicker.currentMonth, datePicker.currentYear]);

  //initializes the grid and gets the days of the current month and year
  const monthGrid = [];
  getMonthGrid(datePicker.currentYear, datePicker.currentMonth, monthGrid);

  //takes the monthgrid and maps it to day elements that are then displayed on the page
  const days = monthGrid.map((element, id) => {
    if (element === null) {
      return <div className='day empty-day' key={id}></div>;
    }
    let date = new Date(datePicker.currentYear, datePicker.currentMonth, element);
    date = getDateFormatted(date);
    return (
      <div className='day' key={date} day={date} onClick={(e) => onClickDateRange(e)}>
        {element}
      </div>
    );
  });

  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
        height: '100vh',
      }}
    >
      <div className='date-picker'>
        <div className='input-container'>
          <input
            type='text'
            name='start-date'
            className='date-picker__input'
            placeholder='Start Date'
            value={dateRange.startDate ? getDateFormatted(dateRange.startDate) : ''}
            onClick={onInputStartClick}
          />
          <input
            type='text'
            name='end-date'
            className='date-picker__input'
            placeholder='End Date'
            value={dateRange.endDate ? getDateFormatted(dateRange.endDate) : ''}
            onClick={onInputEndClick}
          />
        </div>

        <div className='year-container'>
          <button className='prev-month' onClick={prevMonth}>
            Prev
          </button>
          <h2>{`${getMonthTitle(datePicker.currentMonth)} ${datePicker.currentYear}`}</h2>
          <button className='next-month' onClick={nextMonth}>
            Next
          </button>
        </div>
        <div className='week-container'>
          <div>Mo</div>
          <div>Tu</div>
          <div>We</div>
          <div>Th</div>
          <div>Fr</div>
          <div>Sa</div>
          <div>So</div>
        </div>
        <div className='days'>{days}</div>
        <button onClick={getNumberOfDays}>Submit</button>
        <button onClick={clearDateRange}>Clear</button>
      </div>
    </div>
  );

  //MAIN FUNCTIONS
  //gets the days of the current month and year
  function getMonthGrid(year, month, monthGrid) {
    const firstDayType = new Date(year, month, 1).getDay();
    const lastDay = new Date(year, month + 1, 0);
    let lastDayType = lastDay.getDay();
    const numberOfDays = lastDay.getDate();

    if (firstDayType === 0) {
      for (let i = 1; i < 7; i++) {
        monthGrid.push(null);
      }
    } else {
      for (let i = 1; i < firstDayType; i++) {
        monthGrid.push(null);
      }
    }

    for (let i = 0; i < numberOfDays; i++) {
      monthGrid.push(i + 1);
    }

    if (lastDayType !== 0) {
      for (lastDayType; lastDayType < 7; lastDayType++) {
        monthGrid.push(null);
      }
    }
  }

  //on click function that is called whenever a day element is clicked
  function onClickDateRange(e) {
    //gets the date of the element that was clicked
    let date = new Date(datePicker.currentYear, datePicker.currentMonth, e.target.innerText);

    //end date is selected to be overridden, but date is out of bounds (smaller than start date)
    if (dateRange.selectEndDate && dateRange.startDate && date < dateRange.startDate) {
      setDateRange({
        selectStartDate: false,
        selectEndDate: true,
        startDate: date,
        endDate: null,
      });
      return;
    }

    //start date is selected to be overridden, but date is out of bounds (bigger than end date)
    if (dateRange.selectStartDate && dateRange.endDate && date > dateRange.endDate) {
      setDateRange({
        selectStartDate: false,
        selectEndDate: true,
        startDate: date,
        endDate: null,
      });
      return;
    }

    //there is no start date but an end date and date is out of bounds (bigger than end date)
    if (!dateRange.startDate && dateRange.endDate && date > dateRange.endDate) {
      setDateRange({
        selectStartDate: false,
        selectEndDate: true,
        startDate: date,
        endDate: null,
      });
      return;
    }

    //there is no start date but an end date
    if (!dateRange.startDate && dateRange.endDate) {
      setDateRange({
        ...dateRange,
        selectStartDate: false,
        selectEndDate: true,
        startDate: date,
      });
      return;
    }

    //base case if end date is selected
    if (dateRange.selectEndDate) {
      setDateRange({
        ...dateRange,
        endDate: date,
      });
      return;
    }

    //base case if start date is selected
    if (dateRange.selectStartDate) {
      setDateRange({
        ...dateRange,
        selectStartDate: false,
        selectEndDate: true,
        startDate: date,
      });
      return;
    }
  }

  //Handles clicks on the Inputs => sets which dates have to be overwritten
  function onInputStartClick() {
    setDateRange({ ...dateRange, selectStartDate: true, selectEndDate: false });
  }

  function onInputEndClick() {
    setDateRange({ ...dateRange, selectStartDate: false, selectEndDate: true });
  }

  //HELPER FUNCTIONS
  //gets the name of the current month
  function getMonthTitle(month) {
    const months = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December',
    ];
    return months[month];
  }

  //onclick function that sets the state of the datepicker to preview or next month
  function prevMonth() {
    if (datePicker.currentMonth - 1 < 0) {
      setDatePicker({ currentMonth: 11, currentYear: datePicker.currentYear - 1 });
      return;
    }
    setDatePicker({ ...datePicker, currentMonth: datePicker.currentMonth - 1 });
  }

  function nextMonth() {
    if (datePicker.currentMonth + 1 > 11) {
      setDatePicker({ currentMonth: 0, currentYear: datePicker.currentYear + 1 });
      return;
    }
    setDatePicker({ ...datePicker, currentMonth: datePicker.currentMonth + 1 });
  }

  //onclick function of submmit button that calculates the number of days set in the date range
  function getNumberOfDays() {
    const difference = (dateRange.endDate - dateRange.startDate) / (1000 * 60 * 60 * 24);
    console.log(difference);
  }

  //onclick function that clears the date range
  function clearDateRange() {
    setDateRange({
      selectStartDate: true,
      selectEndDate: false,
      startDate: null,
      endDate: null,
    });
  }

  //function that takes a date object as imput and formats it as dd/mm/yyyy
  function getDateFormatted(date) {
    const day = String(date.getDate()).padStart(2, '0');
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const year = date.getFullYear();
    return `${day}/${month}/${year}`;
  }
}

export default Test;

感谢我能得到的任何帮助,因为我不知道如何实现这种效果。 谢谢:)

【问题讨论】:

  • 首先,不要使用document.querySelector,而是使用Refs。它会特别导致同一窗口中的多个组件出现问题。其次,如果您可以添加css,它将有所帮助。活生生的例子会更好。像这样的东西:codesandbox.io/s/relaxed-jones-572cy

标签: javascript css reactjs


【解决方案1】:

像这样将data-* 属性添加到您的日期元素

<div 
  className='day' 
  key={date} 
  day={date} 
  data-date={UTCdateString} 
  onClick={(e) => onClickDateRange(e)}>
  {element}
</div>

用日期的日期 utc 字符串分配它。在你的 CSS 中为你想要的范围内的样式创建一个类。在您的 useEffect 回调中,添加一个条件来检查何时选择了开始日期和结束日期。在条件内部,使用 querySelectorAll 选择所有日期元素,通过 for 循环中的那些元素或者你有什么从每个元素中获取数据日期值 (element.dataset.date -- MDN) ,创建一个函数stack-over-flow solution 来检查日期是否在开始/结束日期之间,如果是,则将类(.in-range 或您命名的任何名称)添加到该元素。

如果你觉得勇敢,我会为所有业务逻辑创建一个日期组件,然后返回一个映射的日期组件数组,这些组件接受道具来呈现样式。然后摆脱 useEffect querySelector 的东西。

【讨论】:

    猜你喜欢
    • 2022-12-19
    • 1970-01-01
    • 2022-12-28
    • 1970-01-01
    • 2019-07-25
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多