Skip to content

stvalentin/react-native-range-date-picker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

React Native Range Date Picker

A flexible and feature-rich React Native date range picker component supporting Week, Month, Year, and Custom date selection modes with smooth animations and comprehensive accessibility support.

npm version License: MIT

✨ Features

  • πŸ“… 4 Selection Modes: Week, Month, Year, and Custom date range picking
  • 🎯 Interactive Selectors: Tap to select from lists and grids (not just arrow navigation)
  • πŸ—“οΈ Calendar View: Full calendar grid for custom date range selection
  • 🎨 Customizable Design: Extensive styling options and theme support
  • ✨ Smooth Animations: Beautiful transitions powered by React Native Animated
  • β™Ώ Accessibility First: Full screen reader and keyboard navigation support
  • 🌍 Internationalization: Locale-aware formatting and date calculations
  • πŸ“± TypeScript: 100% TypeScript with comprehensive type definitions
  • πŸš€ Production Ready: Optimized performance with memoization

πŸ“Έ Screenshots

Week Mode

Select from a scrollable list of weeks with date ranges.

Week Mode

Month Mode

Pick any month from a 12-month grid.

Month Mode

Year Mode

Choose from a scrollable grid of years.

Year Mode

Custom Mode

Select custom date ranges with an interactive calendar.

Custom Mode - No Selection Custom Mode - With Selection

πŸ“¦ Installation

npm install react-native-range-date-picker date-fns
# or
yarn add react-native-range-date-picker date-fns

Peer Dependencies

npm install react react-native date-fns

The component also requires react-native-safe-area-context which is typically included in Expo projects.

πŸš€ Quick Start

import React, { useState } from 'react';
import { View } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';

export default function App() {
  const [startDate, setStartDate] = useState<Date | null>(null);
  const [endDate, setEndDate] = useState<Date | null>(null);
  const [mode, setMode] = useState<RangeMode>('month');

  const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
    setStartDate(start);
    setEndDate(end);
    setMode(mode);
    console.log(`Selected ${mode}:`, start, 'to', end);
  };

  return (
    <View style={{ padding: 20 }}>
      <RangeDatePicker
        initialMode="month"
        onRangeChange={handleRangeChange}
        animationEnabled={true}
      />
      
      {startDate && endDate && (
        <View style={{ marginTop: 20 }}>
          <Text>Start: {startDate.toLocaleDateString()}</Text>
          <Text>End: {endDate.toLocaleDateString()}</Text>
          <Text>Mode: {mode}</Text>
        </View>
      )}
    </View>
  );
}

πŸ’‘ Practical Examples

Example 1: Booking System

Perfect for hotel, flight, or event booking systems.

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';

export default function BookingScreen() {
  const [checkIn, setCheckIn] = useState<Date | null>(null);
  const [checkOut, setCheckOut] = useState<Date | null>(null);

  const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
    setCheckIn(start);
    setCheckOut(end);
  };

  const handleBooking = () => {
    if (checkIn && checkOut) {
      const nights = Math.ceil((checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60 * 24));
      console.log(`Booking ${nights} nights from ${checkIn.toDateString()}`);
      // Process booking...
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Select Your Stay</Text>
      
      <RangeDatePicker
        initialMode="custom"
        onRangeChange={handleRangeChange}
        animationEnabled={true}
      />

      {checkIn && checkOut && (
        <View style={styles.summary}>
          <Text>Check-in: {checkIn.toLocaleDateString()}</Text>
          <Text>Check-out: {checkOut.toLocaleDateString()}</Text>
          <Text>Total Nights: {Math.ceil((checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60 * 24))}</Text>
          <Button title="Book Now" onPress={handleBooking} />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 20 },
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
  summary: { marginTop: 20, padding: 15, backgroundColor: '#f0f0f0', borderRadius: 8 },
});

Example 2: Analytics Dashboard

Select different time periods for data analysis.

import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';

export default function AnalyticsDashboard() {
  const [startDate, setStartDate] = useState<Date>(new Date());
  const [endDate, setEndDate] = useState<Date>(new Date());
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState(null);

  const fetchAnalytics = async (start: Date, end: Date) => {
    setLoading(true);
    try {
      const response = await fetch(`/api/analytics?start=${start.toISOString()}&end=${end.toISOString()}`);
      const result = await response.json();
      setData(result);
    } finally {
      setLoading(false);
    }
  };

  const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
    setStartDate(start);
    setEndDate(end);
    fetchAnalytics(start, end);
  };

  return (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 20, fontWeight: 'bold', marginBottom: 10 }}>
        Analytics Dashboard
      </Text>

      {/* Quick period selection with Week/Month/Year modes */}
      <RangeDatePicker
        initialMode="month"
        onRangeChange={handleRangeChange}
        animationEnabled={true}
      />

      <View style={{ marginTop: 20 }}>
        {loading ? (
          <ActivityIndicator size="large" />
        ) : data ? (
          <Text>Revenue: ${data.revenue}</Text>
        ) : (
          <Text>Select a date range to view analytics</Text>
        )}
      </View>
    </View>
  );
}

Example 3: Report Generator

Generate reports for different time periods.

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';

type ReportType = 'sales' | 'inventory' | 'customers';

export default function ReportGenerator() {
  const [reportType, setReportType] = useState<ReportType>('sales');
  const [startDate, setStartDate] = useState<Date | null>(null);
  const [endDate, setEndDate] = useState<Date | null>(null);
  const [selectedMode, setSelectedMode] = useState<RangeMode>('month');

  const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
    setStartDate(start);
    setEndDate(end);
    setSelectedMode(mode);
  };

  const generateReport = () => {
    if (!startDate || !endDate) return;
    
    console.log(`Generating ${reportType} report for ${selectedMode}`);
    console.log(`Period: ${startDate.toDateString()} to ${endDate.toDateString()}`);
    // Generate report logic...
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Generate Report</Text>

      {/* Report Type Selection */}
      <View style={styles.reportTypes}>
        {['sales', 'inventory', 'customers'].map((type) => (
          <TouchableOpacity
            key={type}
            style={[styles.typeButton, reportType === type && styles.typeButtonActive]}
            onPress={() => setReportType(type as ReportType)}
          >
            <Text style={[styles.typeText, reportType === type && styles.typeTextActive]}>
              {type.charAt(0).toUpperCase() + type.slice(1)}
            </Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* Date Range Picker - Week for weekly reports, Month for monthly, Year for annual */}
      <RangeDatePicker
        initialMode="week"
        onRangeChange={handleRangeChange}
        animationEnabled={true}
        style={styles.picker}
      />

      {startDate && endDate && (
        <TouchableOpacity style={styles.generateButton} onPress={generateReport}>
          <Text style={styles.generateButtonText}>
            Generate {selectedMode.charAt(0).toUpperCase() + selectedMode.slice(1)}ly Report
          </Text>
        </TouchableOpacity>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 20 },
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
  reportTypes: { flexDirection: 'row', gap: 10, marginBottom: 20 },
  typeButton: { flex: 1, padding: 12, backgroundColor: '#f0f0f0', borderRadius: 8, alignItems: 'center' },
  typeButtonActive: { backgroundColor: '#007AFF' },
  typeText: { fontWeight: '600', color: '#666' },
  typeTextActive: { color: '#fff' },
  picker: { marginBottom: 20 },
  generateButton: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, alignItems: 'center' },
  generateButtonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});

Example 4: Calendar Integration

Integrate with existing calendar or scheduling system.

import React, { useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { RangeDatePicker, RangeMode } from 'react-native-range-date-picker';

interface Event {
  id: string;
  title: string;
  date: Date;
}

export default function CalendarView() {
  const [events, setEvents] = useState<Event[]>([]);
  const [selectedStart, setSelectedStart] = useState<Date | null>(null);
  const [selectedEnd, setSelectedEnd] = useState<Date | null>(null);

  const handleRangeChange = (start: Date, end: Date, mode: RangeMode) => {
    setSelectedStart(start);
    setSelectedEnd(end);
    
    // Filter events within selected range
    const filtered = allEvents.filter(
      event => event.date >= start && event.date <= end
    );
    setEvents(filtered);
  };

  return (
    <View style={styles.container}>
      <RangeDatePicker
        initialMode="week"
        onRangeChange={handleRangeChange}
        animationEnabled={true}
      />

      <View style={styles.eventsList}>
        <Text style={styles.eventsTitle}>
          Events ({events.length})
        </Text>
        <FlatList
          data={events}
          keyExtractor={item => item.id}
          renderItem={({ item }) => (
            <View style={styles.eventItem}>
              <Text style={styles.eventTitle}>{item.title}</Text>
              <Text style={styles.eventDate}>
                {item.date.toLocaleDateString()}
              </Text>
            </View>
          )}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20 },
  eventsList: { marginTop: 20, flex: 1 },
  eventsTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 10 },
  eventItem: { padding: 12, backgroundColor: '#f9f9f9', borderRadius: 8, marginBottom: 8 },
  eventTitle: { fontSize: 16, fontWeight: '600' },
  eventDate: { fontSize: 14, color: '#666', marginTop: 4 },
});

πŸ“– API Reference

RangeDatePickerProps

Prop Type Default Description
initialMode 'week' | 'month' | 'year' | 'custom' 'month' Initial selection mode
onRangeChange (start: Date, end: Date, mode: RangeMode) => void Required Callback fired when range changes
initialDate Date new Date() Initial date to display
locale string undefined Locale for date formatting (e.g., 'en-US', 'fr-FR')
theme 'light' | 'dark' | 'auto' 'light' Color theme
animationEnabled boolean false Enable animated transitions
style ViewStyle undefined Main container style
segmentedControlStyle ViewStyle undefined Style for mode selector
navigationButtonStyle ViewStyle undefined Style for navigation buttons
rangeDisplayStyle TextStyle undefined Style for range display text

RangeMode

type RangeMode = 'week' | 'month' | 'year' | 'custom';

DateRange

interface DateRange {
  start: Date;
  end: Date;
  mode: RangeMode;
}

🎨 Customization

Custom Styling

<RangeDatePicker
  onRangeChange={handleRangeChange}
  style={{
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 4,
  }}
  segmentedControlStyle={{
    marginBottom: 16,
  }}
  navigationButtonStyle={{
    backgroundColor: '#4A90E2',
    width: 40,
    height: 40,
  }}
  rangeDisplayStyle={{
    fontSize: 18,
    fontWeight: '700',
    color: '#1F2937',
  }}
  animationEnabled={true}
/>

Localization

<RangeDatePicker
  onRangeChange={handleRangeChange}
  locale="fr-FR"  // French locale
  initialDate={new Date()}
/>

// Supported locales: any valid BCP 47 language tag
// Examples: 'en-US', 'es-ES', 'de-DE', 'ja-JP', etc.

Dark Theme Example

<View style={{ backgroundColor: '#1F2937', padding: 20 }}>
  <RangeDatePicker
    theme="dark"
    onRangeChange={handleRangeChange}
    style={{
      backgroundColor: '#374151',
    }}
    rangeDisplayStyle={{
      color: '#F9FAFB',
    }}
  />
</View>

🧩 Advanced Features

Using Sub-Components

For complete control, you can use individual components:

import {
  WeekSelector,
  MonthSelector,
  YearSelector,
  CalendarGrid,
  SegmentedControl,
} from 'react-native-range-date-picker';

// Use WeekSelector standalone
<WeekSelector
  currentDate={new Date()}
  selectedWeek={selectedWeek}
  onWeekSelect={(date) => setSelectedWeek(date)}
/>

// Use MonthSelector standalone
<MonthSelector
  currentDate={new Date()}
  selectedMonth={selectedMonth}
  onMonthSelect={(date) => setSelectedMonth(date)}
/>

// Use CalendarGrid for custom implementations
<CalendarGrid
  currentDate={new Date()}
  selectedStart={startDate}
  selectedEnd={endDate}
  onDayPress={(date) => handleDayPress(date)}
/>

Utility Functions

import {
  formatRange,
  formatWeekRange,
  formatMonthRange,
  formatYearRange,
  formatCustomRange,
  getAccessibleDescription,
  navigateNext,
  navigatePrev,
  getRangeForMode,
} from 'react-native-range-date-picker';

// Format different range types
const weekText = formatWeekRange(dateRange, { locale: 'en-US' });
const monthText = formatMonthRange(dateRange);
const yearText = formatYearRange(dateRange);

// Navigation
const nextMonth = navigateNext(currentDate, 'month');
const prevYear = navigatePrev(currentDate, 'year');

// Get range for any date and mode
const range = getRangeForMode(new Date(), 'week');
console.log(range.start, range.end);

β™Ώ Accessibility

This component is built with accessibility as a priority:

  • βœ… Full screen reader support (VoiceOver, TalkBack)
  • βœ… Proper accessibility labels and hints
  • βœ… Semantic roles for all interactive elements
  • βœ… Keyboard navigation support
  • βœ… High contrast color ratios (WCAG AA compliant)
  • βœ… Focus management
// The component automatically provides accessibility features
<RangeDatePicker
  onRangeChange={handleRangeChange}
  // All buttons and interactive elements include:
  // - accessibilityRole="button"
  // - accessibilityLabel="descriptive label"
  // - accessibilityState={{ selected: boolean }}
  // - accessibilityHint="helpful hint"
/>

🎭 Animations

Smooth animations are powered by React Native's Animated API:

<RangeDatePicker
  animationEnabled={true}
  onRangeChange={handleRangeChange}
/>

Animation features:

  • Segmented control indicator slides smoothly
  • Date range display fades in/out on change
  • Navigation buttons scale on press
  • All animations use native driver for 60 FPS

πŸ“± TypeScript Support

Full TypeScript support with comprehensive type definitions:

import type {
  RangeDatePickerProps,
  DateRange,
  RangeMode,
  SegmentedControlProps,
  NavigationButtonProps,
  CalendarGridProps,
  WeekSelectorProps,
  MonthSelectorProps,
  YearSelectorProps,
  FormatOptions,
  DateCalculationOptions,
} from 'react-native-range-date-picker';

// All types are exported and fully documented
const handleChange = (start: Date, end: Date, mode: RangeMode): void => {
  const range: DateRange = { start, end, mode };
  console.log(range);
};

πŸ§ͺ Testing

The component is fully testable. Here's an example using React Native Testing Library:

import { render, fireEvent } from '@testing-library/react-native';
import { RangeDatePicker } from 'react-native-range-date-picker';

test('calls onRangeChange when date is selected', () => {
  const mockHandler = jest.fn();
  
  const { getByText } = render(
    <RangeDatePicker
      initialMode="month"
      onRangeChange={mockHandler}
    />
  );

  // Switch to month mode
  fireEvent.press(getByText('Month'));
  
  // Select a month
  fireEvent.press(getByText('January'));
  
  expect(mockHandler).toHaveBeenCalled();
});

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License

MIT Β© React Native RangeDatePicker Team

πŸ™ Credits

  • Built with date-fns for robust date calculations
  • Inspired by native iOS and Android date pickers
  • Designed for React Native and Expo projects

πŸ“ž Support


Made with ❀️ for the React Native community

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published