简体   繁体   English

在 hover 上反应日期选择器颜色效果并设置日期范围

[英]React Date Picker color effect on hover and set date range

I am currently using React to build a Date Picker Component, where you can set a date range.我目前正在使用 React 构建一个日期选择器组件,您可以在其中设置日期范围。 As inspiratoin and model I used the airbnb react-dates package .作为 inspiratoin 和 model 我使用了airbnb react-dates package Now, I managed to build the funcitonality of the date picker, but I don't really know how to achieve the color change of all dates in between the start and end date.现在,我设法构建了日期选择器的功能,但我真的不知道如何在开始日期和结束日期之间实现所有日期的颜色变化。 Because in the airbnb version as soon as you set a start date, it automatically sets all the days between the start date and the mouse cursor in another color.因为在airbnb版本中,一旦你设置了开始日期,它会自动设置开始日期和鼠标cursor之间的所有天数。 This effect is also present, when both a start date and an end date are set (see attachment for reference).当同时设置了开始日期和结束日期时,也会出现这种效果(参见附件以供参考)。

日期选择器悬停效果

Here is my code, I am also happy for every imporvement suggestion:)这是我的代码,我也为每一个改进建议感到高兴:)

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;

I appretiate any help I can get with this, as I hava no clue how to achieve this effect.我感谢我能得到的任何帮助,因为我不知道如何达到这种效果。 Thanks:)谢谢:)

add a data-* attribute to your date element like so像这样向您的日期元素添加一个data-*属性

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

assign it with the date utc string of the date.用日期的日期 utc 字符串分配它。 create a class in your css for the in-range styles you want.在 css 中为您想要的范围内 styles 创建一个 class。 inside of your useEffect callback, add a condition to check when both start and end dates are selected.在你的useEffect回调中,添加一个条件来检查何时选择了开始日期和结束日期。 inside of the condition, use querySelectorAll to select all date elements, go thru those elements in a for-loop or what have you to get the data-date value from each element (element.dataset.date -- MDN ), create a function stack-over-flow solution to check if the date falls between the start/end dates, if so, add the class (.in-range or whatever you named it) to that element. inside of the condition, use querySelectorAll to select all date elements, go thru those elements in a for-loop or what have you to get the data-date value from each element (element.dataset.date -- MDN ), create a function stack-over-flow 解决方案检查日期是否在开始/结束日期之间,如果是,则将 class(.in-range 或您命名的任何名称)添加到该元素。

if you feel brave, i would create a dates component for all the business logic then return a mapped array of date components that take in props to render the styles.如果你觉得勇敢,我会为所有业务逻辑创建一个日期组件,然后返回一个映射的日期组件数组,这些组件接收道具来渲染 styles。 then get rid of the useEffect querySelector stuff.然后摆脱 useEffect querySelector 的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM