Skip to content

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 { CalendarPicker } from '@space-uy/pulsar-ui';
const [dateRange, setDateRange] = useState<{
fromDate?: string;
toDate?: string;
}>({});
<CalendarPicker
label="Select dates"
value={dateRange}
onChange={setDateRange}
placeholder="Choose date range"
/>;
PropertyTypeRequiredDefault valueDescription
value{ fromDate?: string; toDate?: string }{}Current selected date range
onChange(dateRange: DateRange) => void-Callback when date range changes
labelstring-Label text displayed above the picker
placeholderstring'Seleccionar fechas'Placeholder text when no dates selected
disabledbooleanfalseWhether the picker is disabled
errorbooleanfalseWhether the picker is in error state
hintstring-Hint text displayed below the picker
styleStyleProp<ViewStyle>-Custom styles for the container

The date range object structure:

PropertyTypeDescription
fromDatestringStart date in ISO format
toDatestringEnd date in ISO format
const [selectedDate, setSelectedDate] = useState<{
fromDate?: string;
toDate?: string;
}>({});
<CalendarPicker
label="Event date"
value={selectedDate}
onChange={setSelectedDate}
placeholder="Select event date"
/>;
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"
/>;
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'
}
/>;
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>;
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>;
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>;
  • 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
  • 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
  • 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