Browse Source

Prev/next week buttons in header on desktop

main
Kevin Mok 6 days ago
parent
commit
2f71ec5d72
  1. 4
      package.json
  2. 57
      src/components/Calendar.tsx
  3. 39
      src/components/CalendarHeader.tsx
  4. 69
      src/components/DayColumn.tsx
  5. 2
      src/types.ts

4
package.json

@ -23,9 +23,9 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4",
"postcss": "^8.0.0",
"autoprefixer": "^10.0.0",
"postcss": "^8.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
}

57
src/components/Calendar.tsx

@ -18,6 +18,7 @@ import { useWindowSize } from '@/hooks/useWindowSize';
import EventModal from '@/components/EventModal';
import eventsData from '@/data/events';
import { Event, EventsByDate } from '@/types';
import CalendarHeader from '@/components/CalendarHeader';
const Calendar = () => {
const { width } = useWindowSize();
@ -28,10 +29,7 @@ const Calendar = () => {
const [direction, setDirection] = useState<'left' | 'right'>('left');
const [isEventDragging, setIsEventDragging] = useState(false);
const [currentDate, setCurrentDate] = useState(() => {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
});
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
const [events, setEvents] = useState<EventsByDate>(eventsData);
@ -98,13 +96,13 @@ const Calendar = () => {
return Array.from({ length: 7 }, (_, i) => addDays(start, i));
};
const handlePreviousWeek = useCallback(() => {
const handlePrevWeek = () => {
setCurrentDate(prev => addDays(prev, -7));
}, []);
};
const handleNextWeek = useCallback(() => {
const handleNextWeek = () => {
setCurrentDate(prev => addDays(prev, 7));
}, []);
};
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@ -112,7 +110,7 @@ const Calendar = () => {
if (event.key === 'ArrowLeft') {
event.preventDefault();
handlePreviousWeek();
handlePrevWeek();
} else if (event.key === 'ArrowRight') {
event.preventDefault();
handleNextWeek();
@ -121,12 +119,12 @@ const Calendar = () => {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [handlePreviousWeek, handleNextWeek, selectedEvent]);
}, [handlePrevWeek, handleNextWeek, selectedEvent]);
const handleSwipe = (dir: 'left' | 'right') => {
if (isMobile) {
setDirection(dir);
setCurrentDate(prev => dir === 'left' ? addDays(prev, 1) : addDays(prev, -1));
setCurrentDate(prev => addDays(prev, dir === 'left' ? 7 : -7));
}
};
@ -178,9 +176,7 @@ const Calendar = () => {
const handleDayChange = useCallback((direction: 'left' | 'right') => {
setCurrentDate(prev => {
const newDate = addDays(prev, direction === 'left' ? -1 : 1);
// Reset dragging state to enable future swipes
setIsEventDragging(false);
// Smooth transition
setOffset(direction === 'left' ? -window.innerWidth : window.innerWidth);
setTimeout(() => setOffset(0), 10);
return newDate;
@ -229,9 +225,10 @@ const Calendar = () => {
return (
<div className="h-screen flex flex-col">
<Header
currentDate={currentDate}
isMobile={isMobile}
<CalendarHeader
currentDate={currentDate}
onPrevWeek={handlePrevWeek}
onNextWeek={handleNextWeek}
/>
{isMobile && <WeekHeader currentDate={currentDate} />}
@ -375,6 +372,9 @@ const DayColumn = ({
return cleanup;
}, [date]);
console.log('Date value:', date);
console.log('Is valid date:', date instanceof Date && !isNaN(date));
return (
<motion.div
ref={columnRef}
@ -505,31 +505,6 @@ const DraggableEvent = ({
);
};
const Header = ({
currentDate,
isMobile
}: {
currentDate: Date;
isMobile: boolean;
}) => {
const weekRange = useMemo(() => {
if (!isMobile) return '';
const start = startOfWeek(currentDate, { weekStartsOn: 0 });
const end = addDays(start, 6);
return `${format(start, 'MMM d')} - ${format(end, 'MMM d')}`;
}, [currentDate, isMobile]);
return (
<div className="p-4 border-b">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-black">
{isMobile ? weekRange : format(currentDate, 'MMMM yyyy')}
</h2>
</div>
</div>
);
};
const WeekHeader = ({ currentDate }: { currentDate: Date }) => {
const weekDays = useMemo(() => {
const start = startOfWeek(currentDate, { weekStartsOn: 0 }); // Start week on Sunday

39
src/components/CalendarHeader.tsx

@ -0,0 +1,39 @@
import { format } from 'date-fns';
interface CalendarHeaderProps {
currentDate: Date;
onPrevWeek: () => void;
onNextWeek: () => void;
}
function CalendarHeader({ currentDate, onPrevWeek, onNextWeek }: CalendarHeaderProps) {
return (
<div className="flex items-center justify-center gap-4">
<button
onClick={onPrevWeek}
className="p-2 rounded-full hover:bg-gray-100"
>
{/* Left arrow icon */}
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<h2 className="text-xl font-semibold">
{format(currentDate, 'MMMM yyyy')}
</h2>
<button
onClick={onNextWeek}
className="p-2 rounded-full hover:bg-gray-100"
>
{/* Right arrow icon */}
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
);
}
export default CalendarHeader;

69
src/components/DayColumn.tsx

@ -0,0 +1,69 @@
import React, { useRef } from 'react';
import { motion } from 'framer-motion';
import { format, addDays } from 'date-fns';
import { DraggableEvent } from './DraggableEvent';
interface DayColumnProps {
date: Date;
events: Event[];
isMobile: boolean;
index: number;
onEventClick: (event: Event) => void;
onDragStart: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
onDragEnd: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
handleEventMove: (eventId: string, newDate: Date) => void;
onDayChange: (dir: 'left' | 'right') => void;
isClient: boolean;
}
const DayColumn: React.FC<DayColumnProps> = ({
date,
events,
isMobile,
index,
onEventClick,
onDragStart,
onDragEnd,
handleEventMove,
onDayChange,
isClient
}) => {
const columnRef = useRef<HTMLDivElement>(null);
const sortedEvents = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
return (
<motion.div
ref={columnRef}
className={`flex-1 ${isMobile ? 'min-w-[calc(100vw-32px)]' : ''} bg-gray-50 rounded-lg p-2 relative`}
data-day={index}
>
<div className="font-bold mb-2 text-black text-xl">
{format(date, 'EEE, MMM d')}
</div>
<div className="relative z-10">
{sortedEvents
.filter(event => event && event.id)
.map((event, index) => (
<DraggableEvent
key={event.id}
event={event}
date={format(date, 'yyyy-MM-dd')}
onEventClick={onEventClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDayChange={(dir) => {
onDayChange(dir);
const newDate = addDays(date, dir === 'left' ? -1 : 1);
handleEventMove(event.id, newDate);
}}
isClient={isClient}
/>
))}
</div>
</motion.div>
);
};
export default DayColumn;

2
src/types.ts

@ -2,7 +2,7 @@ export interface Event {
id: string;
title: string;
time: string;
date: string;
date: Date;
description?: string;
imageUrl?: string;
// add other properties as needed

Loading…
Cancel
Save