UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

420 lines (417 loc) 15.4 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { Calendar, X, ChevronLeft, ChevronRight } from 'lucide-react'; import React, { useState, useRef, useEffect } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { GlassButton } from '../button/GlassButton.js'; import '../button/GlassFab.js'; import '../button/GlassMagneticButton.js'; import { CardContent } from '../card/index.js'; import { GlassCard } from '../card/GlassCard.js'; /** * GlassDateRangePicker component * A glassmorphism date range picker with calendar interface */ const GlassDateRangePicker = ({ value, defaultValue, onChange, placeholder = "Select date range", dateFormat = "short", locale = "en-US", minDate, maxDate, disabled = false, size = "md", className, popoverClassName, showClear = true, presets = [{ label: "Today", getValue: () => { const today = new Date(); return { from: today, to: today }; } }, { label: "Yesterday", getValue: () => { const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); return { from: yesterday, to: yesterday }; } }, { label: "Last 7 days", getValue: () => { const today = new Date(); const lastWeek = new Date(); lastWeek.setDate(today.getDate() - 6); return { from: lastWeek, to: today }; } }, { label: "Last 30 days", getValue: () => { const today = new Date(); const lastMonth = new Date(); lastMonth.setDate(today.getDate() - 29); return { from: lastMonth, to: today }; } }, { label: "This month", getValue: () => { const today = new Date(); const firstDay = new Date(today.getFullYear(), today.getMonth(), 1); return { from: firstDay, to: today }; } }, { label: "Last month", getValue: () => { const today = new Date(); const firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1); const lastDay = new Date(today.getFullYear(), today.getMonth(), 0); return { from: firstDay, to: lastDay }; } }], rangeLabel = "to", ...props }) => { const [isOpen, setIsOpen] = useState(false); const [currentRange, setCurrentRange] = useState(value || defaultValue || { from: null, to: null }); const [currentMonth, setCurrentMonth] = useState(new Date()); const [selectingFrom, setSelectingFrom] = useState(true); const triggerRef = useRef(null); const popoverRef = useRef(null); // Size configurations const sizeConfigs = { sm: { trigger: "h-8 glass-px-3 glass-text-sm", calendar: "w-64" }, md: { trigger: "h-10 glass-px-4 glass-text-base", calendar: "w-80" }, lg: { trigger: "h-12 glass-px-6 glass-text-lg", calendar: "w-96" } }; // Update current range when value prop changes useEffect(() => { if (value) { setCurrentRange(value); } }, [value]); // Handle range change const handleRangeChange = newRange => { setCurrentRange(newRange); onChange?.(newRange); }; // Handle date selection const handleDateSelect = date => { const newRange = { ...currentRange }; if (selectingFrom || !currentRange.from) { newRange.from = date; newRange.to = null; setSelectingFrom(false); } else if (!currentRange.to || date < currentRange.from) { newRange.from = date; newRange.to = null; setSelectingFrom(false); } else { newRange.to = date; setSelectingFrom(true); setIsOpen(false); } handleRangeChange(newRange); }; // Handle preset selection const handlePresetSelect = preset => { const newRange = preset.getValue(); handleRangeChange(newRange); setIsOpen(false); }; // Clear selection const handleClear = () => { handleRangeChange({ from: null, to: null }); setSelectingFrom(true); }; // Format date for display const formatDate = date => { if (!date) return ""; const options = { year: "numeric", month: dateFormat === "numeric" ? "2-digit" : dateFormat === "long" ? "long" : "short", day: "2-digit" }; return new Intl.DateTimeFormat(locale, options).format(date); }; // Get display value const getDisplayValue = () => { if (!currentRange.from && !currentRange.to) { return placeholder; } if (currentRange.from && !currentRange.to) { return `${formatDate(currentRange.from)} ${rangeLabel} ...`; } if (currentRange.from && currentRange.to) { return `${formatDate(currentRange.from)} ${rangeLabel} ${formatDate(currentRange.to)}`; } return placeholder; }; // Check if date is in range const isDateInRange = date => { if (!currentRange.from || !currentRange.to) return false; return date >= currentRange.from && date <= currentRange.to; }; // Check if date is range start or end const isRangeBoundary = date => { if (!currentRange.from && !currentRange.to) return false; return currentRange.from && date.toDateString() === currentRange.from.toDateString() || currentRange.to && date.toDateString() === currentRange.to.toDateString(); }; // Generate calendar data const calendarData = React.useMemo(() => { const year = currentMonth.getFullYear(); const month = currentMonth.getMonth(); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const startDate = new Date(firstDay); startDate.setDate(startDate.getDate() - firstDay.getDay()); const weeks = []; let currentWeek = []; const currentDateIter = new Date(startDate); // Build weeks until we've passed the last day and flushed any partial week while (currentDateIter <= lastDay || currentWeek.length > 0) { currentWeek.push(new Date(currentDateIter)); if (currentWeek.length === 7) { weeks.push([...currentWeek]); currentWeek = []; // If we've already passed the month end and just flushed the trailing week, break if (currentDateIter > lastDay) break; } currentDateIter.setDate(currentDateIter.getDate() + 1); } return { year, month, monthName: new Intl.DateTimeFormat(locale, { month: "long" }).format(currentMonth), weeks }; }, [currentMonth, locale]); // Navigate months const navigateMonth = direction => { setCurrentMonth(prev => { const newDate = new Date(prev); if (direction === "prev") { newDate.setMonth(newDate.getMonth() - 1); } else { newDate.setMonth(newDate.getMonth() + 1); } return newDate; }); }; // Check if date is disabled const isDisabled = date => { if (minDate && date < minDate) return true; if (maxDate && date > maxDate) return true; return false; }; // Check if date is today const isToday = date => { const today = new Date(); return date.toDateString() === today.toDateString(); }; // Check if date is in current month const isCurrentMonth = date => { return date.getMonth() === currentMonth.getMonth(); }; // Close popover when clicking outside useEffect(() => { const handleClickOutside = event => { if (triggerRef.current && !triggerRef.current.contains(event.target) && popoverRef.current && !popoverRef.current.contains(event.target)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const config = sizeConfigs[size]; return jsxs("div", { "data-glass-component": true, className: 'relative', children: [jsxs("div", { ref: triggerRef, className: cn("relative flex items-center justify-between bg-white/10 glass-backdrop-blur-md border border-white/20", "glass-radius-lg cursor-pointer transition-all duration-200", "hover:bg-white/15 hover:border-white/30 focus-within:bg-white/15 focus-within:border-white/30", config.trigger, disabled && "opacity-50 cursor-not-allowed", className), onClick: e => !disabled && setIsOpen(!isOpen), children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2 glass-flex-1 glass-min-w-0", children: [jsx(Calendar, { className: 'w-4 h-4 text-primary/60 glass-flex-shrink-0' }), jsx("span", { className: cn("truncate", !currentRange.from && !currentRange.to ? "glass-text-primary/50" : "glass-text-primary"), children: getDisplayValue() })] }), jsx("div", { className: "glass-flex glass-items-center glass-gap-1", children: showClear && (currentRange.from || currentRange.to) && jsx("button", { onClick: e => { e.stopPropagation(); handleClear(); }, className: 'glass-p-1 hover:glass-surface-subtle/20 glass-radius-md transition-colors', children: jsx(X, { className: 'w-3 h-3 text-primary/60' }) }) })] }), isOpen && jsx(MotionFramer, { preset: "fadeIn", className: 'absolute z-50 glass-mt-2', children: jsx("div", { ref: popoverRef, className: cn("bg-black/20 glass-backdrop-blur-md border border-white/20 glass-radius-xl shadow-2xl", config.calendar, popoverClassName), children: jsx(GlassCard, { variant: "outline", className: 'glass-border-0 bg-transparent', children: jsx(CardContent, { className: "glass-p-4", children: jsxs("div", { className: "glass-flex glass-gap-6", children: [presets && presets.length > 0 && jsxs("div", { className: "glass-flex-shrink-0", children: [jsx("h4", { className: 'glass-text-sm font-medium text-primary/80 mb-3', children: "Quick Select" }), jsx("div", { className: "glass-gap-1", children: presets.map((preset, index) => jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => handlePresetSelect(preset), className: 'glass-w-full glass-justify-start text-left', children: preset.label }, preset.label)) })] }), jsxs("div", { className: "glass-flex-1", children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-4', children: [jsxs("h3", { className: 'glass-text-lg font-semibold text-primary', children: [calendarData.monthName, " ", calendarData.year] }), jsxs("div", { className: "glass-flex glass-gap-1", children: [jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => navigateMonth("prev"), children: jsx(ChevronLeft, { className: 'w-4 h-4' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => navigateMonth("next"), children: jsx(ChevronRight, { className: 'w-4 h-4' }) })] })] }), jsxs("div", { className: "glass-gap-2", children: [jsx("div", { className: "glass-grid glass-grid-cols-7 glass-gap-1", children: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map(day => jsx("div", { className: 'text-center glass-text-sm font-medium text-primary/60 glass-py-2', children: day }, day)) }), jsx("div", { className: "glass-grid glass-grid-cols-7 glass-gap-1", children: calendarData.weeks.flat().map((date, index) => { const isInRange = isDateInRange(date); const isBoundary = isRangeBoundary(date); const isDisabledDate = isDisabled(date); const isCurrentMonthDate = isCurrentMonth(date); const isTodayDate = isToday(date); return jsx(MotionFramer, { preset: "scaleIn", className: 'aspect-square', children: jsx("button", { onClick: e => !isDisabledDate && handleDateSelect(date), disabled: isDisabledDate, className: cn("w-full h-full glass-radius-lg glass-text-sm font-medium transition-all duration-200", "flex items-center justify-center", "hover:bg-white/20 focus:bg-white/25 focus:outline-none", "disabled:opacity-50 disabled:cursor-not-allowed", { "glass-text-primary/60": !isCurrentMonthDate, "glass-text-primary": isCurrentMonthDate, "bg-primary/20 text-primary-foreground": isBoundary, "bg-primary/10": isInRange && !isBoundary, "bg-white/10 glass-text-primary font-semibold": isTodayDate && !isInRange && !isBoundary, "ring-2 ring-primary/50": isBoundary }), children: date.getDate() }) }, index); }) })] }), jsxs("div", { className: 'glass-flex glass-justify-between glass-items-center glass-mt-4 pt-4 glass-border-t glass-border-white/10', children: [jsx("div", { className: 'glass-text-sm text-primary/60', children: selectingFrom ? "Select start date" : "Select end date" }), jsxs("div", { className: "glass-flex glass-gap-2", children: [jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => setIsOpen(false), children: "Cancel" }), jsx(GlassButton, { variant: "primary", size: "sm", onClick: e => setIsOpen(false), children: "Apply" })] })] })] })] }) }) }) }) })] }); }; export { GlassDateRangePicker, GlassDateRangePicker as default }; //# sourceMappingURL=GlassDateRangePicker.js.map