horizontal-calendar: switch months by arrow buttons
This commit is contained in:
parent
0cb9e6b6ee
commit
d085a3d24d
@ -2,115 +2,61 @@
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Button } from '../ui/button';
|
||||
import { addDays, format, isSameDay, subDays } from 'date-fns';
|
||||
import {
|
||||
addMonths,
|
||||
eachDayOfInterval,
|
||||
endOfMonth,
|
||||
format,
|
||||
isSameDay,
|
||||
startOfMonth,
|
||||
} from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
|
||||
type HorizontalCalendarProps = {
|
||||
readonly className?: string;
|
||||
readonly numberOfDays?: number;
|
||||
readonly numberOfDaysBefore?: number;
|
||||
readonly onDateChange: (date: Date) => void;
|
||||
readonly selectedDate: Date;
|
||||
};
|
||||
|
||||
export function HorizontalCalendar({
|
||||
className,
|
||||
numberOfDays = 14,
|
||||
numberOfDaysBefore = 7,
|
||||
onDateChange,
|
||||
selectedDate,
|
||||
}: HorizontalCalendarProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [baseDate, setBaseDate] = useState(addDays(new Date(), -numberOfDaysBefore));
|
||||
const [currentMonthDate, setCurrentMonthDate] = useState(new Date());
|
||||
const [currentMonthDate, setCurrentMonthDate] = useState(startOfMonth(new Date()));
|
||||
|
||||
// Получаем список всех дней текущего месяца
|
||||
const dates = useMemo(() => {
|
||||
return Array.from({ length: numberOfDays }, (_, index) => {
|
||||
return addDays(baseDate, index);
|
||||
return eachDayOfInterval({
|
||||
end: endOfMonth(currentMonthDate),
|
||||
start: startOfMonth(currentMonthDate),
|
||||
});
|
||||
}, [baseDate, numberOfDays]);
|
||||
}, [currentMonthDate]);
|
||||
|
||||
const updateCurrentMonth = (newBaseDate: Date) => {
|
||||
setBaseDate(newBaseDate);
|
||||
setCurrentMonthDate(newBaseDate);
|
||||
setTimeout(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollLeft = 0;
|
||||
}
|
||||
}, 0);
|
||||
const scrollToStart = () => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTo({
|
||||
behavior: 'smooth',
|
||||
left: 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const scrollPrevious = () => {
|
||||
const newDate = subDays(baseDate, numberOfDays);
|
||||
updateCurrentMonth(newDate);
|
||||
const newDate = addMonths(currentMonthDate, -1);
|
||||
setCurrentMonthDate(newDate);
|
||||
setTimeout(scrollToStart, 0);
|
||||
};
|
||||
|
||||
const scrollNext = () => {
|
||||
const newDate = addDays(baseDate, numberOfDays);
|
||||
updateCurrentMonth(newDate);
|
||||
const newDate = addMonths(currentMonthDate, 1);
|
||||
setCurrentMonthDate(newDate);
|
||||
setTimeout(scrollToStart, 0);
|
||||
};
|
||||
|
||||
// Обработчик scroll — вычисляет дату ближайшую к левому краю
|
||||
const handleScroll = () => {
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const children = Array.from(container.children) as HTMLElement[];
|
||||
|
||||
let closestChild: HTMLElement | null = null;
|
||||
let minOffset = Infinity;
|
||||
|
||||
for (const child of children) {
|
||||
const offset = Math.abs(child.offsetLeft - container.scrollLeft);
|
||||
if (offset < minOffset) {
|
||||
minOffset = offset;
|
||||
closestChild = child;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestChild) {
|
||||
const dateString = closestChild.dataset.date;
|
||||
if (dateString) {
|
||||
const date = new Date(dateString);
|
||||
setCurrentMonthDate(date);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const scrollElement = scrollRef.current;
|
||||
if (scrollElement) {
|
||||
scrollElement.addEventListener('scroll', handleScroll, { passive: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (scrollElement) {
|
||||
scrollElement.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const selectedChild = container.querySelector(
|
||||
`[data-date="${selectedDate.toISOString()}"]`,
|
||||
) as HTMLElement;
|
||||
if (selectedChild) {
|
||||
// Прокрутка так, чтобы элемент оказался в центре
|
||||
const offsetLeft = selectedChild.offsetLeft;
|
||||
const centerOffset = offsetLeft - container.clientWidth / 2 + selectedChild.clientWidth / 2;
|
||||
|
||||
container.scrollTo({
|
||||
behavior: 'auto', // можно 'smooth', если хочешь плавно
|
||||
left: centerOffset,
|
||||
});
|
||||
}
|
||||
}, []); // <- только при монтировании
|
||||
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
@ -131,6 +77,7 @@ export function HorizontalCalendar({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="scrollbar-hide flex snap-x overflow-x-auto pb-2" ref={scrollRef}>
|
||||
{dates.map((date) => (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user