Calendar Picker
The CalendarPicker
component provides an elegant date range selection interface with a bottom sheet calendar. It supports single date selection, date ranges, and includes features like month navigation, date validation, and customizable styling.
Import
Section titled “Import”import { CalendarPicker } from '@space-uy/pulsar-ui';
Basic usage
Section titled “Basic usage”const [dateRange, setDateRange] = useState<{ fromDate?: string; toDate?: string;}>({});
<CalendarPicker label="Select dates" value={dateRange} onChange={setDateRange} placeholder="Choose date range"/>;
Properties
Section titled “Properties”Property | Type | Required | Default value | Description |
---|---|---|---|---|
value | { fromDate?: string; toDate?: string } | ❌ | {} | Current selected date range |
onChange | (dateRange: DateRange) => void | ✅ | - | Callback when date range changes |
label | string | ❌ | - | Label text displayed above the picker |
placeholder | string | ❌ | 'Seleccionar fechas' | Placeholder text when no dates selected |
disabled | boolean | ❌ | false | Whether the picker is disabled |
error | boolean | ❌ | false | Whether the picker is in error state |
hint | string | ❌ | - | Hint text displayed below the picker |
style | StyleProp<ViewStyle> | ❌ | - | Custom styles for the container |
DateRange Type
Section titled “DateRange Type”The date range object structure:
Property | Type | Description |
---|---|---|
fromDate | string | Start date in ISO format |
toDate | string | End date in ISO format |
Basic examples
Section titled “Basic examples”Single date selection
Section titled “Single date selection”const [selectedDate, setSelectedDate] = useState<{ fromDate?: string; toDate?: string;}>({});
<CalendarPicker label="Event date" value={selectedDate} onChange={setSelectedDate} placeholder="Select event date"/>;
Date range selection
Section titled “Date range selection”const [dateRange, setDateRange] = useState<{ fromDate?: string; toDate?: string;}>({});
<CalendarPicker label="Travel dates" value={dateRange} onChange={setDateRange} placeholder="Select check-in and check-out dates" hint="Choose your stay duration"/>;
With validation
Section titled “With validation”const [bookingDates, setBookingDates] = useState<{ fromDate?: string; toDate?: string;}>({});const [hasError, setHasError] = useState(false);
const handleDateChange = (dateRange: { fromDate?: string; toDate?: string;}) => { setBookingDates(dateRange);
// Validate minimum stay if (dateRange.fromDate && dateRange.toDate) { const from = new Date(dateRange.fromDate); const to = new Date(dateRange.toDate); const daysDiff = Math.ceil( (to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24) ); setHasError(daysDiff < 2); } else { setHasError(false); }};
<CalendarPicker label="Booking dates" value={bookingDates} onChange={handleDateChange} placeholder="Select your stay dates" error={hasError} hint={ hasError ? 'Minimum stay is 2 nights' : 'Select check-in and check-out dates' }/>;
Advanced examples
Section titled “Advanced examples”Vacation booking system
Section titled “Vacation booking system”const [vacationDates, setVacationDates] = useState<{ fromDate?: string; toDate?: string;}>({});const [isValidating, setIsValidating] = useState(false);
const handleDateSelection = async (dateRange: { fromDate?: string; toDate?: string;}) => { setVacationDates(dateRange);
if (dateRange.fromDate && dateRange.toDate) { setIsValidating(true); try { await validateAvailability(dateRange); // Handle success } catch (error) { // Handle unavailable dates } finally { setIsValidating(false); } }};
<Card style={{ padding: 16 }}> <Text variant="h4" style={{ marginBottom: 16 }}> Plan Your Vacation </Text>
<CalendarPicker label="Travel dates" value={vacationDates} onChange={handleDateSelection} placeholder="When would you like to travel?" disabled={isValidating} hint={ isValidating ? 'Checking availability...' : 'Select your preferred dates' } />
{vacationDates.fromDate && vacationDates.toDate && ( <View style={{ marginTop: 16, padding: 12, backgroundColor: colors.altBackground, borderRadius: 8, }} > <Text variant="pm">Selected dates:</Text> <Text variant="h5"> {format(new Date(vacationDates.fromDate), 'MMM dd')} -{' '} {format(new Date(vacationDates.toDate), 'MMM dd, yyyy')} </Text> <Text variant="ps" style={{ opacity: 0.7 }}> {getDaysDifference(vacationDates.fromDate, vacationDates.toDate)} nights </Text> </View> )}</Card>;
Event scheduling
Section titled “Event scheduling”const [eventDates, setEventDates] = useState<{ fromDate?: string; toDate?: string;}>({});const [eventType, setEventType] = useState<'single' | 'multi'>('single');
const handleEventTypeChange = (type: 'single' | 'multi') => { setEventType(type); if (type === 'single' && eventDates.toDate) { setEventDates({ fromDate: eventDates.fromDate }); }};
<View style={{ gap: 16 }}> <Text variant="h4">Schedule Event</Text>
<View style={{ flexDirection: 'row', gap: 12 }}> <Chip text="Single Day" onPress={() => handleEventTypeChange('single')} style={{ backgroundColor: eventType === 'single' ? colors.primary : colors.altBackground, }} /> <Chip text="Multi Day" onPress={() => handleEventTypeChange('multi')} style={{ backgroundColor: eventType === 'multi' ? colors.primary : colors.altBackground, }} /> </View>
<CalendarPicker label={eventType === 'single' ? 'Event date' : 'Event duration'} value={eventDates} onChange={setEventDates} placeholder={ eventType === 'single' ? 'Select event date' : 'Select start and end dates' } hint={ eventType === 'single' ? 'Choose the day for your event' : 'Select the duration of your event' } /></View>;
Recurring events
Section titled “Recurring events”const [recurringEvent, setRecurringEvent] = useState<{ fromDate?: string; toDate?: string;}>({});const [recurringPattern, setRecurringPattern] = useState<string>('weekly');
const recurringOptions = [ { value: 'daily', label: 'Daily' }, { value: 'weekly', label: 'Weekly' }, { value: 'monthly', label: 'Monthly' },];
<Card> <Text variant="h4">Create Recurring Event</Text>
<View style={{ marginTop: 16, gap: 16 }}> <CalendarPicker label="Event dates" value={recurringEvent} onChange={setRecurringEvent} placeholder="Select event period" hint="Choose start and end dates for the recurring event" />
<Select label="Repeat pattern" options={recurringOptions} value={recurringOptions.find((opt) => opt.value === recurringPattern)} onChange={(option) => setRecurringPattern(option.value)} placeholder="Select pattern" />
{recurringEvent.fromDate && recurringEvent.toDate && ( <View style={{ padding: 12, backgroundColor: colors.altBackground, borderRadius: 8, }} > <Text variant="pm">Preview:</Text> <Text variant="ps" style={{ opacity: 0.7 }}> Event will repeat {recurringPattern} from{' '} {format(new Date(recurringEvent.fromDate), 'MMM dd')} to{' '} {format(new Date(recurringEvent.toDate), 'MMM dd, yyyy')} </Text> </View> )} </View></Card>;
Implementation notes
Section titled “Implementation notes”- The calendar uses a bottom sheet interface for better mobile experience
- Date range selection is intuitive with visual feedback for start and end dates
- Month navigation is smooth with proper animations
- The component respects locale settings for date formatting
- Past dates can be disabled based on configuration
- The calendar automatically handles different month lengths and leap years
Calendar Features
Section titled “Calendar Features”- Month Navigation: Smooth transitions between months with arrow controls
- Date Range Selection: Visual feedback for selecting start and end dates
- Today Highlighting: Current date is clearly marked
- Disabled Dates: Past dates or custom disabled dates are visually distinct
- Responsive Design: Adapts to different screen sizes
Accessibility
Section titled “Accessibility”- Full keyboard navigation support for calendar interaction
- Screen reader compatibility with proper date announcements
- High contrast mode support for better visibility
- Touch targets meet minimum size requirements for easy interaction
- Focus management ensures smooth navigation through calendar dates