DEV Community

Jayce Li
Jayce Li

Posted on

Building DayFlow: A Modern React Calendar Library with Temporal API and Advanced Drag-and-Drop

When I set out to build DayFlow, I wanted to solve a problem I'd encountered repeatedly: existing calendar libraries either oversimplified the problem or became tangled messes of legacy code. After exploring the source code of popular solutions, I found they struggled with three key areas:

  1. Date handling - Most relied on heavyweight libraries or buggy Date implementations
  2. Multi-day events - Spanning events across weeks looked broken or required hacky workarounds
  3. Extensibility - Adding custom behavior meant forking the library or fighting the API

DayFlow tackles these head-on with modern web standards, a plugin architecture, and sophisticated layout algorithms. Let me show you what makes it different.

What is DayFlow?

DayFlow is a feature-rich React calendar component library built with TypeScript, designed for applications that need professional scheduling interfaces. Think employee scheduling systems, appointment booking platforms, project timelines, or event management tools.

The Four Core Views

Month View - Virtual Scrolling at Scale

The month view renders 156 weeks (3 years of data) but only displays what's visible. Using virtual scrolling with a 2-week buffer, it handles thousands of events without performance degradation.

Month View

Week View - Hourly Time Grid

A 7-day week view with configurable hourly slots (0-24 hours), all-day event sections, and real-time current time indicators.

Week View

Day View - Single Day Focus

Identical to week view but focused on a single day, with a mini-calendar sidebar for quick date navigation.

Day View

The Temporal API Advantage

DayFlow is built on the Temporal API - the modern JavaScript date/time standard that solves timezone nightmares and calendar math bugs.

import { Temporal } from '@js-temporal/polyfill';
import { createEvent } from '@dayflow/core';

// Create a timezone-aware event
const event = createEvent({
  id: '1',
  title: 'Team Standup',
  start: Temporal.ZonedDateTime.from('2025-11-09T10:00:00[America/New_York]'),
  end: Temporal.ZonedDateTime.from('2025-11-09T10:30:00[America/New_York]'),
  calendarId: 'work'
});

Enter fullscreen mode Exit fullscreen mode

DayFlow supports:

  • Temporal.PlainDate - Calendar dates without time
  • Temporal.PlainDateTime - Local date-times
  • Temporal.ZonedDateTime - Timezone-aware date-times
  • Legacy Date objects (auto-converted)

No more wrestling with timezone conversions or installing moment.js.

Multi-Day Event Handling

Rendering multi-day events correctly is surprisingly complex. Events spanning multiple weeks need to:

  • Split into visual segments
  • Handle week boundaries intelligently
  • Position correctly in crowded layouts
  • Respond to drag operations

DayFlow implements 7 segment types to handle this:

type EventSegmentType =
  | 'single'          // Event within one day
  | 'start'           // First day of multi-day event
  | 'middle'          // Middle days
  | 'end'             // Last day
  | 'start-week-end'  // Starts mid-week, ends at week boundary
  | 'end-week-start'  // Starts at week boundary, ends mid-week
  | 'full-week';      // Occupies entire week

Enter fullscreen mode Exit fullscreen mode

This enables proper rendering across any time span:

Stack level

Intelligent event stacking with overlap detection

The layout algorithm detects parallel events (15-min threshold), handles nested hierarchies (30-min threshold), and prevents collisions using a sophisticated positioning system I built after studying Google Calendar's approach.

The Plugin Architecture

DayFlow uses a modular plugin system. Want drag-and-drop? Add the drag plugin. Need event storage? Add the events plugin.

Events Plugin - CRUD Operations

import { createEventsPlugin } from '@dayflow/core';

const eventsPlugin = createEventsPlugin({
  events: initialEvents,
  enableValidation: true,
  maxEventsPerDay: 50,
  onEventCreate: (event) => {
    console.log('Event created:', event);
    // Save to backend
  },
  onEventUpdate: (event) => {
    console.log('Event updated:', event);
    // Update backend
  },
  onEventDelete: (eventId) => {
    console.log('Event deleted:', eventId);
    // Delete from backend
  }
});

Enter fullscreen mode Exit fullscreen mode

Services provided:

  • add(), update(), delete(), getById(), getAll()
  • Range queries: getByDate(), getByDateRange(), getByDay()
  • Filtering: filterEvents() with custom predicates
  • Validation: validateEvent() with error reporting
  • Auto-calculation: recalculateEventDays() for week positioning

Validation rules:

  • Title required
  • Start/end timestamps required
  • Start must be before end (except all-day events)
  • ID must be a string

Drag Plugin - Move, Resize, Create

The drag plugin is 2,339 lines of carefully architected code split across 6 modular hooks:

import { createDragPlugin } from '@dayflow/core';

const dragPlugin = createDragPlugin();

Enter fullscreen mode Exit fullscreen mode

Hook composition pattern:

  • useDragCommon - Pixel↔hour conversion, position calculations
  • useDragState - State management (drag refs, active state)
  • useDragManager - Visual indicator creation/updates
  • useDragHandlers - Event handlers (the core logic)
  • useWeekDayDrag - Week/Day view specifics (cross-day creation)
  • useMonthDrag - Month view specifics (date calculations)

Three drag modes:

  1. Move - Relocate events to different times/days
  2. Resize - Adjust duration (top/bottom handles)
  3. Create - Click and drag to create new events

Performance optimizations include throttled drag moves (120fps), memoized calculations, and batched state updates.

Customization: Three Levels of Control

Level 1: Custom Detail Panel Content

Replace just the content while keeping the default panel UI:

const CustomDetailContent = ({ event, onEventUpdate, onClose }) => (
  <div>
    <h3>{event.title}</h3>
    <p>{event.description}</p>
    <button onClick={() => {
      onEventUpdate({ ...event, title: 'Updated!' });
      onClose();
    }}>
      Save
    </button>
  </div>
);

<DayFlowCalendar
  calendar={calendar}
  customDetailPanelContent={CustomDetailContent}
/>

Enter fullscreen mode Exit fullscreen mode

Even detail pop

Level 2: Custom Detail Dialog

Replace the entire modal implementation:

const CustomDialog = ({ event, isOpen, onClose, onEventUpdate }) => {
  if (!isOpen) return null;

  return (
    <MyCustomModal>
      {/* Your custom dialog UI */}
    </MyCustomModal>
  );
};

<DayFlowCalendar
  calendar={calendar}
  customEventDetailDialog={CustomDialog}
/>

Enter fullscreen mode Exit fullscreen mode

Even detail dialog

Level 3: Custom Sidebar

Full control over the calendar type sidebar:

<DayFlowCalendar
  calendar={calendar}
  sidebarConfig={{
    enabled: true,
    width: 280,
    initialCollapsed: false,
    render: ({ calendars, toggleCalendarVisibility }) => (
      <div>
        {calendars.map(cal => (
          <label key={cal.id}>
            <input
              type="checkbox"
              checked={cal.visible}
              onChange={() => toggleCalendarVisibility(cal.id, !cal.visible)}
            />
            {cal.name}
          </label>
        ))}
      </div>
    )
  }}
/>

Enter fullscreen mode Exit fullscreen mode

Customer sidebar

The Calendar Type System

DayFlow supports multiple calendars with independent colors and visibility toggles:

const calendar = useCalendarApp({
  views: [createMonthView(), createWeekView()],
  calendarTypes: [
    { id: 'work', name: 'Work', color: '#3b82f6', icon: 'briefcase' },
    { id: 'personal', name: 'Personal', color: '#10b981', icon: 'home' },
    { id: 'events', name: 'Events', color: '#f59e0b', icon: 'calendar' }
  ],
  initialDate: new Date()
});

Enter fullscreen mode Exit fullscreen mode

Features:

  • 6 default calendar types (Blue, Green, Purple, Yellow, Red, Orange)
  • Light/dark mode color variants
  • Show/hide calendars independently
  • Icon support (Lucide React icons)
  • Color resolution with fallbacks

Quick Start: 2 Minutes to a Working Calendar

Installation

npm install @dayflow/core lucide-react

Enter fullscreen mode Exit fullscreen mode

Minimal Setup

import { useCalendarApp, DayFlowCalendar } from '@dayflow/core';
import { createMonthView, createWeekView, createDayView } from '@dayflow/core';
import '@dayflow/core/dist/styles.css';

function App() {
  const calendar = useCalendarApp({
    views: [createMonthView(), createWeekView(), createDayView()],
    initialDate: new Date(),
  });

  return <DayFlowCalendar calendar={calendar} />;
}

Enter fullscreen mode Exit fullscreen mode

Full-Featured Setup

import {
  useCalendarApp,
  DayFlowCalendar,
  createMonthView,
  createWeekView,
  createDayView,
  createEventsPlugin,
  createDragPlugin
} from '@dayflow/core';
import '@dayflow/core/dist/styles.css';

function App() {
  const calendar = useCalendarApp({
    views: [
      createMonthView(),
      createWeekView(),
      createDayView()
    ],
    plugins: [
      createEventsPlugin({
        events: [
          {
            id: '1',
            title: 'Team Meeting',
            start: new Date('2025-11-09T10:00:00'),
            end: new Date('2025-11-09T11:00:00'),
            calendarId: 'work'
          }
        ],
        onEventCreate: (event) => console.log('Created:', event),
        onEventUpdate: (event) => console.log('Updated:', event),
        onEventDelete: (id) => console.log('Deleted:', id)
      }),
      createDragPlugin({
        enableDrag: true,
        enableResize: true,
        enableCreate: true
      })
    ],
    calendarTypes: [
      { id: 'work', name: 'Work', color: '#3b82f6' },
      { id: 'personal', name: 'Personal', color: '#10b981' }
    ],
    initialDate: new Date()
  });

  return <DayFlowCalendar calendar={calendar} />;
}

Enter fullscreen mode Exit fullscreen mode

Architecture Highlights

CalendarApp: The State Container

The CalendarApp class is the central state manager:

const calendar = useCalendarApp(config);

// Navigation
calendar.setCurrentView('Week');
calendar.setCurrentDate(new Date('2025-12-01'));

// Event operations
calendar.addEvent(newEvent);
calendar.updateEvent(updatedEvent);
calendar.deleteEvent(eventId);

// Registry operations
calendar.toggleCalendarVisibility('work', false);
calendar.setThemeMode('dark');

Enter fullscreen mode Exit fullscreen mode

The useCalendarApp hook provides React state synchronization through method interception - changes to CalendarApp automatically trigger React re-renders.

Real-World Use Cases

Scheduling Applications

  • Employee shift scheduling with drag-to-reassign
  • Appointment booking with availability visualization
  • Class timetables with room conflicts
  • Doctor scheduling with patient management

Event Management

  • Conference schedules with session tracks (calendar types)
  • Festival planners with multi-day events
  • Community event calendars with category filtering
  • Venue booking systems with resource conflicts

Project Management

  • Timeline views for project milestones
  • Task scheduling with dependencies
  • Sprint planning calendars
  • Resource allocation across projects

Business Tools

  • Meeting room booking with availability
  • Equipment reservation systems
  • Team availability calendars
  • Deadline tracking across departments

Technical Stack

  • ⚛️ React 18+ - Latest concurrent features
  • 📘 TypeScript - Strict mode, full type safety
  • Temporal API - Modern date/time handling
  • 🎨 Tailwind CSS - Utility-first styling
  • 🪝 Hooks Architecture - Composable, testable
  • 🧪 Jest + React Testing Library - Comprehensive tests

Minimal dependencies: React, React-DOM, Lucide React, Temporal Polyfill

Try It Yourself

Contributing

DayFlow is open source and welcomes contributions:

  • 🐛 Bug reports - Help us improve stability
  • Feature requests - Share your ideas
  • 📖 Documentation - Improve guides and examples
  • 💡 Code contributions - Submit pull requests

Check out the GitHub repository to get started.

Top comments (0)