I built a date picker that shows a list of dates, with three previous dates, the current date in the middle, and three next dates. On either side, there are buttons:
The Previous button moves the list backward.
The Next button moves the list forward.
The idea is that this should work in a circular way so when I reach the last date in the list and click Next, it should loop back to the first few dates, and the same goes in reverse when clicking Previous.
For example, if my current date list looks like this:
< 01/22 02/22 03/22 04/22 (Current) 05/22 06/22 07/22 >
Clicking Next should ideally wrap around and continue like this:
< 02/22 03/22 04/22 05/22 (Current) 06/22 07/22 08/22 >
But instead, my implementation just goes to the next available dates after 07/22 and the list is 08/22 and onwards.
Here is my code:
import { DatePicker, Button } from 'antd';
import './ButtonedAntDDatePicker.css';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { useState, useEffect } from 'react';
dayjs.extend(customParseFormat);
function ButtonedAntDDatePicker({
selectedDate,
disabledDate,
isDatePickerDisabled,
enabledDates,
setSelectedDate
}) {
const [isAnimating, setIsAnimating] = useState(false);
const [prevButtonsStyle, setPrevButtonsStyle] = useState({});
const [nextButtonsStyle, setNextButtonsStyle] = useState({});
const [datePickerInput, setDatePickerInput] = useState(null);
// handle date picker input field
useEffect(() => {
const input = document.querySelector('.date-picker-container .ant-picker-input input');
if (input) {
setDatePickerInput(input);
// If selectedDate is already set (from URL), update the input field
if (selectedDate) {
input.value = selectedDate.format('DD/MM/YYYY');
}
const handleInputEvent = (e) => {
const inputValue = e.target.value;
const formats = ['DD/MM/YYYY', 'D/M/YYYY', 'DD/M/YYYY', 'D/MM/YYYY'];
for (const format of formats) {
const parsedDate = dayjs(inputValue, format, true);
if (parsedDate.isValid()) {
setSelectedDate(parsedDate);
e.target.value = parsedDate.format('DD/MM/YYYY');
break;
}
}
};
input.addEventListener('blur', handleInputEvent);
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleInputEvent(e);
}
});
return () => {
input.removeEventListener('blur', handleInputEvent);
input.removeEventListener('keypress', handleInputEvent);
};
}
}, [setSelectedDate, selectedDate]);
const findSurroundingDates = (currentDate, count) => {
if (enabledDates.length === 0 || !currentDate) return { prevDates: [], nextDates: [] };
const sortedDates = [...enabledDates].sort((a, b) => dayjs(a).diff(dayjs(b)));
const currentIndex = sortedDates.findIndex(date =>
dayjs(date).isSame(currentDate, 'day')
);
const effectiveIndex = currentIndex === -1
? sortedDates.findIndex(date => dayjs(date).isAfter(currentDate))
: currentIndex;
const prevDates = sortedDates
.slice(Math.max(0, effectiveIndex - count), effectiveIndex)
.map(date => dayjs(date));
const nextDates = sortedDates
.slice(effectiveIndex + 1, effectiveIndex + count + 1)
.map(date => dayjs(date));
let hasFirstDate = false;
let firstDateIndex = -1;
const isNearEnd = effectiveIndex >= sortedDates.length - count;
if (nextDates.length < count) {
const remainingDates = count - nextDates.length;
const firstDates = sortedDates.slice(0, remainingDates).map(date => dayjs(date));
if (firstDates.length > 0) {
hasFirstDate = true;
firstDateIndex = nextDates.length;
}
nextDates.push(...firstDates);
}
//if it is first date in the list prev dates should be last dates in the list
if (prevDates.length < count) {
const remainingDates = count - prevDates.length;
const lastDates = sortedDates.slice(-remainingDates).map(date => dayjs(date));
prevDates.unshift(...lastDates);
}
return { prevDates, nextDates, hasFirstDate, firstDateIndex, isNearEnd };
};
const { prevDates, nextDates, hasFirstDate, firstDateIndex, isNearEnd } = findSurroundingDates(selectedDate, 4);
const handleDatePickerChange = (date) => {
if (!date) return;
setSelectedDate(date);
};
const handleDateButtonClick = (date) => {
if (isAnimating || !date) return;
setIsAnimating(true);
const isGoingLeft = date.isAfter(selectedDate);
if (isGoingLeft) {
setPrevButtonsStyle({ animation: 'moveLeftOut 0.15s cubic-bezier(0.33, 1.0, 0.68, 1.0) forwards' });
setNextButtonsStyle({ animation: 'moveLeftIn 0.15s cubic-bezier(0.22, 1.0, 0.36, 1.0) forwards' });
} else {
setPrevButtonsStyle({ animation: 'moveRightIn 0.15s cubic-bezier(0.22, 1.0, 0.36, 1.0) forwards' });
setNextButtonsStyle({ animation: 'moveRightOut 0.15s cubic-bezier(0.33, 1.0, 0.68, 1.0) forwards' });
}
setTimeout(() => {
setSelectedDate(date);
setIsAnimating(false);
setPrevButtonsStyle({});
setNextButtonsStyle({});
}, 200);
};
const formatButtonDate = (date) => {
return date.format('DD/MM/YYYY');
};
// checks if it is first date in the sorted dates list
const isFirstDate = (date) => {
if (!date || enabledDates.length === 0) return false;
const sortedDates = [...enabledDates].sort((a, b) => dayjs(a).diff(dayjs(b)));
const firstDate = dayjs(sortedDates[0]);
return date.isSame(firstDate, 'day');
};
// checks if it is the last date in the sorted dates list
const isLastDate = (date) => {
if (!date || enabledDates.length === 0) return false;
const sortedDates = [...enabledDates].sort((a, b) => dayjs(a).diff(dayjs(b)));
const lastDate = dayjs(sortedDates[sortedDates.length - 1]);
return date.isSame(lastDate, 'day');
};
const renderPrevButtons = () => {
if (!prevDates.length) return null;
return prevDates.map((date, index) => {
const isLastDateIndicator = isLastDate(date);
return (
<div
key={`prev-${index}`}
style={{ position: 'relative' }}
>
<Button
onClick={() => handleDateButtonClick(date)}
className={`date-button prev-date ${index === 0 ? 'first-button' : ''}`}
>
{index === 0 ? <i className="fa-solid fa-caret-left"></i> : formatButtonDate(date)}
</Button>
</div>
);
});
};
const renderNextButtons = () => {
if (!nextDates.length) return null;
return nextDates.map((date, index) => {
return (
<div
key={`next-${index}`}
style={{ position: 'relative' }}
>
<Button
onClick={() => handleDateButtonClick(date)}
className={`date-button next-date ${index === nextDates.length - 1 ? 'last-button' : ''}`}
>
{index === nextDates.length - 1 ? <i className="fa-solid fa-caret-right"></i> : formatButtonDate(date)}
</Button>
</div>
);
});
};
if (isDatePickerDisabled) {
return null;
}
return (
<div className="date-picker-container">
<div
className="date-buttons-container prev-dates"
style={prevButtonsStyle}
>
{renderPrevButtons()}
</div>
<DatePicker
value={selectedDate}
style={{ zIndex: 9999, margin: '0px' }}
format={'DD/MM/YYYY'}
onChange={handleDatePickerChange}
disabledDate={disabledDate}
allowClear={true}
disabled={isDatePickerDisabled}
inputReadOnly={false}
/>
<div
className="date-buttons-container next-dates"
style={nextButtonsStyle}
>
{renderNextButtons()}
</div>
</div>
);
}
export default ButtonedAntDDatePicker;
<div className='datepickers'>
<ButtonedAntDDatePicker
selectedDate={startDate}
disabledDate={disabledDate}
isDatePickerDisabled={isDatePickerDisabled}
enabledDates={enabledDates}
setSelectedDate={setStartDate}
/>
</div>
I expect that when I click on next/prev button it will only move to the prev date in the list not move whole thing. I've tried shifting the dates but it didn't quite work. Anyone has any idea on how to approach this issue?