My-Weekly-ToDo-List/src/components/SimpleDatePicker.tsx

158 lines
7.0 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { format, addMonths, subMonths, startOfMonth, endOfMonth, startOfWeek, endOfWeek, addDays, isSameMonth, isSameDay } from 'date-fns';
import { enUS, de } from 'date-fns/locale';
interface SimpleDatePickerProps {
selected: Date;
onSelect: (date: Date) => void;
onClose: () => void;
language?: string;
}
export default function SimpleDatePicker({ selected, onSelect, onClose, language = 'en' }: SimpleDatePickerProps) {
const [currentMonth, setCurrentMonth] = useState(new Date(selected));
const modalRef = useRef<HTMLDivElement>(null);
const locale = language === 'de' ? de : enUS;
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [onClose]);
const renderHeader = () => (
<div className="datepicker-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1rem', padding: '0 0.5rem' }}>
<button onClick={() => setCurrentMonth(subMonths(currentMonth, 1))} style={{ padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', color: '#9ca3af', transition: 'color 0.2s' }}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
</button>
<div style={{ fontWeight: 'bold', fontSize: '0.875rem', letterSpacing: '0.05em', textTransform: 'uppercase', color: '#374151' }}>
{format(currentMonth, 'MMMM yyyy', { locale })}
</div>
<button onClick={() => setCurrentMonth(addMonths(currentMonth, 1))} style={{ padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', color: '#9ca3af', transition: 'color 0.2s' }}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
</button>
</div>
);
const renderDays = () => {
const days = [];
const startDate = startOfWeek(currentMonth, { weekStartsOn: 1 });
for (let i = 0; i < 7; i++) {
days.push(
<div key={i} style={{ textAlign: 'center', fontSize: '0.75rem', fontWeight: 'bold', color: '#9ca3af', padding: '0.5rem 0' }}>
{format(addDays(startDate, i), 'EEEEEE', { locale })}
</div>
);
}
return <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', marginBottom: '0.5rem' }}>{days}</div>;
};
const renderCells = () => {
const monthStart = startOfMonth(currentMonth);
const monthEnd = endOfMonth(monthStart);
const startDate = startOfWeek(monthStart, { weekStartsOn: 1 });
const endDate = endOfWeek(monthEnd, { weekStartsOn: 1 });
const cells = [];
let day = startDate;
while (day <= endDate) {
const row = [];
for (let i = 0; i < 7; i++) {
const cloneDay = day;
const isSelected = isSameDay(day, selected);
const isToday = isSameDay(day, new Date());
const isCurrentMonth = isSameMonth(day, monthStart);
const isDaySelected = isSelected;
row.push(
<div
key={day.toString()}
onClick={() => {
onSelect(cloneDay);
onClose();
}}
style={{
height: '2rem',
width: '2rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.875rem',
borderRadius: '50%',
cursor: 'pointer',
transition: 'all 0.2s',
margin: '0 auto',
color: !isCurrentMonth ? '#d1d5db' : isDaySelected ? 'white' : '#374151',
background: isDaySelected ? 'black' : 'transparent',
fontWeight: isDaySelected ? 'bold' : 'normal',
boxShadow: isDaySelected ? '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)' : 'none',
transform: isDaySelected ? 'scale(1.1)' : 'none',
...(isToday && !isDaySelected ? { color: '#ef4444', fontWeight: 'bold' } : {})
}}
onMouseEnter={(e) => {
if (!isDaySelected) e.currentTarget.style.backgroundColor = '#f3f4f6';
}}
onMouseLeave={(e) => {
if (!isDaySelected) e.currentTarget.style.backgroundColor = 'transparent';
}}
>
{format(day, 'd')}
</div>
);
day = addDays(day, 1);
}
cells.push(row);
}
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '0.5rem 0' }}>
{cells.flat()}
</div>
);
};
return (
<div ref={modalRef} style={{
position: 'absolute',
top: '100%',
right: 0,
marginTop: '0.5rem',
backgroundColor: 'white',
borderRadius: '0.5rem',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
padding: '1rem',
zIndex: 50,
width: '18rem',
border: '1px solid #f3f4f6',
animation: 'fadeIn 0.15s ease-out'
}}>
{/* Decorative triangle */}
<div style={{
position: 'absolute',
top: '-0.3rem',
right: '1rem',
width: '0.75rem',
height: '0.75rem',
backgroundColor: 'white',
transform: 'rotate(45deg)',
borderTop: '1px solid #f3f4f6',
borderLeft: '1px solid #f3f4f6'
}}></div>
{renderHeader()}
{renderDays()}
{renderCells()}
<style jsx>{`
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div>
);
}