react-vite-themes
Version:
A test/experimental React theme system created for learning purposes. Features atomic design components, SCSS variables, and dark/light theme support. Not intended for production use.
119 lines (118 loc) • 6.22 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import React, { useState, useRef, useEffect } from 'react';
import { Icon } from '../Icon';
import { Button } from '../Button';
import { cn } from '../../../utils';
import './Calendar.scss';
export const Calendar = ({ value, onChange, minDate, maxDate, className, size = 'md', variant = 'default', isOpen = false, onOpenChange, placeholder = 'Select date', disabled = false }) => {
const [currentDate, setCurrentDate] = useState(value || new Date());
const [selectedDate, setSelectedDate] = useState(value || null);
const [isCalendarOpen, setIsCalendarOpen] = useState(isOpen);
const calendarRef = useRef(null);
useEffect(() => {
setCurrentDate(value || new Date());
setSelectedDate(value || null);
}, [value]);
useEffect(() => {
setIsCalendarOpen(isOpen);
}, [isOpen]);
useEffect(() => {
const handleClickOutside = (event) => {
if (calendarRef.current && !calendarRef.current.contains(event.target)) {
setIsCalendarOpen(false);
onOpenChange?.(false);
}
};
if (isCalendarOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isCalendarOpen, onOpenChange]);
const toggleCalendar = () => {
if (!disabled) {
const newState = !isCalendarOpen;
setIsCalendarOpen(newState);
onOpenChange?.(newState);
}
};
const formatDate = (date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};
const getDaysInMonth = (date) => {
const year = date.getFullYear();
const month = date.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const daysInMonth = lastDay.getDate();
const startingDayOfWeek = firstDay.getDay();
const days = [];
// Add previous month's days
for (let i = startingDayOfWeek - 1; i >= 0; i--) {
const prevDate = new Date(year, month, -i);
days.push(prevDate);
}
// Add current month's days
for (let i = 1; i <= daysInMonth; i++) {
days.push(new Date(year, month, i));
}
// Add next month's days to fill the grid
const remainingDays = 42 - days.length; // 6 rows * 7 days
for (let i = 1; i <= remainingDays; i++) {
days.push(new Date(year, month + 1, i));
}
return days;
};
const isToday = (date) => {
const today = new Date();
return date.toDateString() === today.toDateString();
};
const isSelected = (date) => {
return selectedDate ? date.toDateString() === selectedDate.toDateString() : false;
};
const isCurrentMonth = (date) => {
return date.getMonth() === currentDate.getMonth();
};
const isDisabled = (date) => {
if (minDate && date < minDate)
return true;
if (maxDate && date > maxDate)
return true;
return false;
};
const handleDateClick = (date) => {
if (!isDisabled(date)) {
setSelectedDate(date);
onChange?.(date);
setIsCalendarOpen(false);
onOpenChange?.(false);
}
};
const goToPreviousMonth = () => {
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
};
const goToNextMonth = () => {
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1));
};
const goToToday = () => {
const today = new Date();
setCurrentDate(today);
if (!isDisabled(today)) {
setSelectedDate(today);
onChange?.(today);
}
};
const days = getDaysInMonth(currentDate);
const monthYear = currentDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long'
});
// Remove unused variable
const calendarClasses = cn('calendar', `calendar--${size}`, `calendar--${variant}`, isCalendarOpen && 'calendar--open', disabled && 'calendar--disabled', className);
return (_jsxs("div", { className: calendarClasses, ref: calendarRef, children: [_jsxs("div", { className: "calendar-trigger", onClick: toggleCalendar, children: [_jsxs("div", { className: "calendar-input", children: [_jsx(Icon, { name: "calendar", size: "sm" }), _jsx("span", { className: "calendar-value", children: selectedDate ? formatDate(selectedDate) : placeholder })] }), _jsx(Icon, { name: isCalendarOpen ? 'chevron-up' : 'chevron-down', size: "sm" })] }), isCalendarOpen && (_jsxs("div", { className: "calendar-dropdown", children: [_jsxs("div", { className: "calendar-header", children: [_jsx(Button, { variant: "secondary", size: "sm", onClick: goToPreviousMonth, className: "calendar-nav-button", children: _jsx(Icon, { name: "chevron-left", size: "sm" }) }), _jsx("div", { className: "calendar-month-year", children: monthYear }), _jsx(Button, { variant: "secondary", size: "sm", onClick: goToNextMonth, className: "calendar-nav-button", children: _jsx(Icon, { name: "chevron-right", size: "sm" }) })] }), _jsx("div", { className: "calendar-weekdays", children: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (_jsx("div", { className: "calendar-weekday", children: day }, day))) }), _jsx("div", { className: "calendar-grid", children: days.map((date, index) => (_jsx("button", { className: cn('calendar-day', isToday(date) && 'calendar-day--today', isSelected(date) && 'calendar-day--selected', !isCurrentMonth(date) && 'calendar-day--other-month', isDisabled(date) && 'calendar-day--disabled'), onClick: () => handleDateClick(date), disabled: isDisabled(date), children: date.getDate() }, index))) }), _jsx("div", { className: "calendar-footer", children: _jsx(Button, { variant: "primary", size: "sm", onClick: goToToday, className: "calendar-today-button", children: "Today" }) })] }))] }));
};