npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-smart-scheduler

v0.1.5

Published

A production-ready, open-source React scheduler/calendar component with Day, Week, and Month views — drag-and-drop, resize, and a clean controlled-component API.

Readme

📅 react-smart-scheduler

A production-ready, open-source React scheduler / calendar component

Day · Week · Month views · Drag & drop · Resize · TypeScript · Zero UI framework dependency

npm version npm downloads bundle size license TypeScript

Live Demo →  |  GitHub →  |  npm →


❤️ Support the project

react-smart-scheduler is free and open-source (MIT). If it saves you development time, please consider supporting it — every contribution keeps the project alive and actively maintained.

| | | |---|---| | ☕ Buy Me a Coffee | One-time tip — any amount helps | | 🩷 GitHub Sponsors | Monthly support with perks |

Star the repo · 🐛 Report a bug · 🔁 Share with a teammate


✨ Features

| Feature | Status | |---|---| | Day / Week / Month views | ✅ | | Drag events to move (time + day) | ✅ | | Drag bottom edge to resize | ✅ | | Click empty slot → create event modal | ✅ | | Overlap / concurrent event layout | ✅ | | Current time indicator ("now" line) | ✅ | | Controlled component API | ✅ | | Accessible (ARIA roles, keyboard nav) | ✅ | | CSS custom-property theming | ✅ | | TypeScript — fully typed | ✅ | | Zero heavy UI framework deps | ✅ | | Source maps included | ✅ |


📦 Installation

npm install react-smart-scheduler
# or
yarn add react-smart-scheduler
# or
pnpm add react-smart-scheduler

Peer dependencies — make sure these are already in your project:

npm install react react-dom

🚀 Quick start

// 1. Import the component and CSS
import { Scheduler, CalendarEvent, generateId } from 'react-smart-scheduler';
import 'react-smart-scheduler/dist/scheduler.css';

// 2. Manage your own events (controlled pattern)
import { useState } from 'react';

export default function App() {
  const [events, setEvents] = useState<CalendarEvent[]>([]);

  return (
    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
      <Scheduler
        events={events}
        view="week"
        onEventAdd={(partial) =>
          setEvents((prev) => [...prev, { ...partial, id: generateId() }])
        }
        onEventChange={(updated) =>
          setEvents((prev) =>
            prev.map((e) => (e.id === updated.id ? updated : e))
          )
        }
        onEventDelete={(id) =>
          setEvents((prev) => prev.filter((e) => e.id !== id))
        }
      />
    </div>
  );
}

🎨 Styling options

react-smart-scheduler ships with a zero-dependency default theme and two optional CSS adapters. All three are drop-in replacements for <Scheduler /> — just swap the import.

| Adapter | Import | Accent | Font | |---|---|---|---| | Default | import { Scheduler } | Blue #3b82f6 | System UI | | Tailwind | import { TailwindScheduler } | Indigo #6366f1 | Inter | | MUI | import { MuiScheduler } | MUI Blue #1976d2 | Roboto |

Default (built-in)

import { Scheduler } from 'react-smart-scheduler';
import 'react-smart-scheduler/dist/scheduler.css';

<Scheduler events={events} onEventAdd={...} onEventChange={...} onEventDelete={...} />

Tailwind-inspired adapter

No Tailwind installation required — this is CSS-only styling inspired by the Tailwind design system.

import { TailwindScheduler } from 'react-smart-scheduler';
import 'react-smart-scheduler/dist/scheduler.css';

<TailwindScheduler events={events} onEventAdd={...} onEventChange={...} onEventDelete={...} />

MUI-inspired adapter

No @mui/material installation required — pure CSS that mirrors Material Design aesthetics.

import { MuiScheduler } from 'react-smart-scheduler';
import 'react-smart-scheduler/dist/scheduler.css';

<MuiScheduler events={events} onEventAdd={...} onEventChange={...} onEventDelete={...} />

Headless (bring your own styles)

Use HeadlessScheduler when you want the scheduler's logic with a completely custom CSS layer.

import { HeadlessScheduler } from 'react-smart-scheduler';
import 'react-smart-scheduler/dist/scheduler.css'; // structural styles only

<HeadlessScheduler events={events} className="my-custom-theme" ... />

Custom CSS tokens

All adapters expose the same CSS custom properties. Override any token on a parent element:

/* your-theme.css */
.rss-root {
  --rss-primary:        #0ea5e9; /* sky-500 */
  --rss-primary-light:  #f0f9ff;
  --rss-today-bg:       #f0f9ff;
  --rss-radius:         10px;
  --rss-event-radius:   8px;
}

Slots — swap built-in sub-components

For deeper customisation, swap the Header or EventModal with your own components:

import { Scheduler, HeaderSlotProps, EventModalSlotProps } from 'react-smart-scheduler';

const MyHeader: React.FC<HeaderSlotProps> = ({ view, date, onViewChange, onDateChange }) => (
  // your custom header JSX
);

<Scheduler
  events={events}
  slots={{ Header: MyHeader }}
  ...
/>

📖 API

<Scheduler /> Props

| Prop | Type | Default | Description | |---|---|---|---| | events | CalendarEvent[] | required | Controlled list of events | | view | 'day' \| 'week' \| 'month' | 'week' | Active view | | date | Date | new Date() | Anchor date for the current view | | onEventAdd | (e: Omit<CalendarEvent, 'id'>) => void | — | Fired when user creates an event | | onEventChange | (e: CalendarEvent) => void | — | Fired after drag-move or resize | | onEventDelete | (id: string) => void | — | Fired when user deletes an event | | onViewChange | (v: ViewType) => void | — | Fired when the view changes | | onDateChange | (d: Date) => void | — | Fired when the user navigates | | hourHeight | number | 64 | Pixel height of each hour row | | startHour | number | 0 | First visible hour (0–23) | | endHour | number | 24 | Last visible hour (1–24) | | className | string | '' | Extra CSS class on the root element | | slots | SchedulerSlots | — | Swap Header or EventModal with custom components |

CalendarEvent type

Every event on the calendar is a plain JavaScript object that satisfies this interface:

interface CalendarEvent {
  id:     string;   // unique identifier
  title:  string;   // text shown on the event chip
  start:  Date;     // inclusive start — must be earlier than end
  end:    Date;     // exclusive end   — must be later than start
  color?: string;   // any valid CSS colour (optional, falls back to palette)
}

Field reference

| Field | Type | Required | Notes | |---|---|---|---| | id | string | ✅ | Must be unique across all events. Use generateId() for new events or your own UUID/nanoid. | | title | string | ✅ | Displayed on the chip. Long titles are truncated with on short events. | | start | Date | ✅ | JavaScript Date object. Must be strictly before end. Timezone is whatever local time the Date represents. | | end | Date | ✅ | JavaScript Date object. Must be strictly after start. Same-day is fine; multi-day is supported in month view. | | color | string | ❌ | Any valid CSS colour string. If omitted, the library picks from EVENT_COLORS using pickColor(id). |

Constraints & rules

✅  start < end
✅  Minimum duration: 1 minute (shorter events still render at minimum chip height)
✅  Multi-day events: supported in Month view; in Day/Week views only the same-day portion is shown
✅  Overlapping events are automatically laid out side-by-side
❌  start === end  →  undefined behaviour, avoid
❌  start > end   →  event will not render

Creating events

import { generateId } from 'react-smart-scheduler';

// From a date + hour offsets
const event: CalendarEvent = {
  id:    generateId(),
  title: 'Team standup',
  start: new Date(2025, 0, 15, 9, 0),   // Jan 15 2025 09:00
  end:   new Date(2025, 0, 15, 9, 30),  // Jan 15 2025 09:30
  color: '#3b82f6',
};

// From ISO strings (common when coming from an API / database)
const fromApi: CalendarEvent = {
  id:    apiEvent.id,
  title: apiEvent.title,
  start: new Date(apiEvent.startIso),   // new Date('2025-01-15T09:00:00')
  end:   new Date(apiEvent.endIso),
  color: apiEvent.color ?? '#8b5cf6',
};

// All-day style (midnight-to-midnight)
const allDay: CalendarEvent = {
  id:    generateId(),
  title: 'Company holiday',
  start: new Date(2025, 0, 20, 0, 0, 0),
  end:   new Date(2025, 0, 20, 23, 59, 59),
  color: '#10b981',
};

Color reference

color accepts any valid CSS colour string:

color: '#3b82f6'          // hex (recommended — predictable across browsers)
color: '#3b82f680'        // hex with alpha (50% transparent)
color: 'royalblue'        // named colour
color: 'rgb(59,130,246)'  // rgb()
color: 'hsl(217,91%,60%)' // hsl()

Built-in palette (used when color is omitted):

import { EVENT_COLORS, pickColor } from 'react-smart-scheduler';

// EVENT_COLORS — the full string[] palette
console.log(EVENT_COLORS);
// ['#3b82f6', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444', '#06b6d4', ...]

// pickColor(id) — deterministic colour assignment so the same event
// always gets the same colour, even across re-renders
const color = pickColor(event.id);  // returns one of EVENT_COLORS

Extending with custom fields

To attach your own metadata (room ID, attendees, status, etc.) without losing TypeScript safety, extend the interface:

// types.ts
import type { CalendarEvent } from 'react-smart-scheduler';

export interface MyEvent extends CalendarEvent {
  roomId:      string;
  attendees:   string[];
  status:      'confirmed' | 'tentative' | 'cancelled';
  description: string;
}

// Your component — cast when passing to Scheduler
const events: MyEvent[] = [...];

<Scheduler
  events={events as CalendarEvent[]}  // widening cast — safe, Scheduler only reads base fields
  onEventAdd={(partial) => {
    // partial is Omit<CalendarEvent, 'id'> — add your extra fields before saving
    const newEvent: MyEvent = {
      ...partial,
      id:          generateId(),
      roomId:      selectedRoom,
      attendees:   [],
      status:      'confirmed',
      description: '',
    };
    setEvents((prev) => [...prev, newEvent]);
  }}
  onEventChange={(updated) =>
    // updated is CalendarEvent — merge back with your extra fields
    setEvents((prev) =>
      prev.map((e) => (e.id === updated.id ? { ...e, ...updated } : e))
    )
  }
  onEventDelete={(id) =>
    setEvents((prev) => prev.filter((e) => e.id !== id))
  }
/>

Serialising to / from a database

// Saving to DB — convert Date → ISO string
const toDb = (e: CalendarEvent) => ({
  ...e,
  start: e.start.toISOString(),
  end:   e.end.toISOString(),
});

// Loading from DB — convert ISO string → Date
const fromDb = (row: DbRow): CalendarEvent => ({
  ...row,
  start: new Date(row.start),
  end:   new Date(row.end),
});

Exported utilities

import {
  Scheduler,      // the main component
  generateId,     // generate a unique event id
  pickColor,      // pick a colour from the palette deterministically
  EVENT_COLORS,   // the default colour palette (string[])
  VERSION,        // current library version string
} from 'react-smart-scheduler';

import type {
  CalendarEvent,  // event shape
  SchedulerProps, // props interface
  ViewType,       // 'day' | 'week' | 'month'
} from 'react-smart-scheduler';

🎨 Theming

Override CSS custom properties on .rss-root to customise appearance without touching source:

.rss-root {
  --rss-primary:       #6366f1;   /* accent colour */
  --rss-primary-light: #eef2ff;
  --rss-border:        #d1d5db;   /* grid lines */
  --rss-bg:            #ffffff;   /* surface */
  --rss-bg-alt:        #f9fafb;   /* alternate bg */
  --rss-today-bg:      #eef2ff;   /* today column tint */
  --rss-text:          #111827;
  --rss-text-muted:    #9ca3af;
  --rss-time-gutter-w: 52px;
  --rss-radius:        6px;
}

💡 Usage patterns

Controlled view + date (recommended)

const [view, setView] = useState<ViewType>('week');
const [date, setDate] = useState(new Date());

<Scheduler
  view={view}
  date={date}
  onViewChange={setView}
  onDateChange={setDate}
  events={events}
  onEventAdd={...}
  onEventChange={...}
  onEventDelete={...}
/>

Semi-controlled (internal navigation state)

// Omit view/date/onViewChange/onDateChange — scheduler manages them internally
<Scheduler
  events={events}
  onEventAdd={handleAdd}
  onEventChange={handleChange}
  onEventDelete={handleDelete}
/>

Custom business hours

<Scheduler
  events={events}
  startHour={7}
  endHour={20}
  hourHeight={80}
  ...
/>

With Zustand

// store.ts
import { create } from 'zustand';
import { CalendarEvent, generateId } from 'react-smart-scheduler';

const useCalendarStore = create<{
  events: CalendarEvent[];
  add: (p: Omit<CalendarEvent, 'id'>) => void;
  update: (e: CalendarEvent) => void;
  remove: (id: string) => void;
}>((set) => ({
  events: [],
  add:    (p)  => set((s) => ({ events: [...s.events, { ...p, id: generateId() }] })),
  update: (e)  => set((s) => ({ events: s.events.map((x) => x.id === e.id ? e : x) })),
  remove: (id) => set((s) => ({ events: s.events.filter((x) => x.id !== id) })),
}));

🛠 Development

# Clone
git clone https://github.com/satthish/react-smart-scheduler.git
cd react-smart-scheduler/packages/react-smart-scheduler

# Install
npm install

# Start demo playground (hot-reload at http://localhost:5173)
npm run dev

# Build library → /dist  (runs tsc + vite)
npm run build

# Type-check only
npm run type-check

# Preview built demo
npm run preview

Publishing to npm

# Log in (first time)
npm login

# Publish (prepublishOnly runs type-check + build automatically)
npm publish

The prepublishOnly hook ensures the published package is always a fresh, type-safe build.


📸 Demo

👉 Open live demo

Features you can try:

  • Switch between Day, Week, and Month views
  • Click any empty time slot to open the Create Event modal
  • Drag an event to move it (changes time and, in week view, the day)
  • Drag the bottom edge of an event to resize its duration
  • Click an event chip to edit its title, time, color, or delete it
  • + Add random event button to quickly populate the calendar

🏗 Architecture

src/
├── index.ts           — public API entry point
├── types.ts           — all TypeScript interfaces
├── Scheduler.tsx      — root + single DndContext + drag math
├── scheduler.css      — all library styles
│
├── views/
│   ├── DayView.tsx    — 1-column TimeGrid wrapper
│   ├── WeekView.tsx   — 7-column TimeGrid wrapper
│   └── MonthView.tsx  — 6×7 month grid + draggable pills
│
├── components/
│   ├── Header.tsx     — navigation + view switcher
│   ├── TimeGrid.tsx   — shared hour-grid backbone
│   ├── EventItem.tsx  — chip: dnd-kit drag + Pointer Capture resize
│   └── EventModal.tsx — add / edit / delete modal
│
├── hooks/
│   └── useScheduler.ts — UI-only state (modal, pending slot)
│
└── utils/
    ├── dateUtils.ts   — date-fns wrappers, grid math, navigation
    └── eventUtils.ts  — overlap detection, column layout, colours

Key decisions:

| Decision | Why | |---|---| | Single DndContext at root | Centralised drag-end math; avoids nested context issues | | Delta-based drag (not per-cell droppables) | Sub-cell time precision, minimal DOM nodes | | Pointer Capture API for resize | Tracks cursor anywhere on screen without document listeners | | Controlled component | Consumer owns state — works with any store | | CSS custom properties | Zero-runtime theming, no CSS-in-JS required |


🗺 Roadmap

v0.2 — Core polish

  • [ ] All-day events row
  • [ ] Multi-day spanning events in week view
  • [ ] Keyboard shortcut to delete selected event
  • [ ] Mini-month navigation widget

v0.3 — Power features

  • [ ] Drag-to-create (click-drag on empty slot)
  • [ ] Recurring events (RRULE / iCal)
  • [ ] Timezone-aware rendering
  • [ ] External .ics import / export

Future Pro features (opt-in add-ons, core stays free)

  • [ ] Resource / room view (multi-column by resource)
  • [ ] Gantt-style timeline
  • [ ] Custom event render slot (render prop)
  • [ ] Virtual scroll for large event volumes
  • [ ] Backend sync hooks (optimistic updates)

🤝 Contributing

All contributions are welcome — bug fixes, features, docs, tests.

  1. Fork & clone the repo
  2. npm install in packages/react-smart-scheduler
  3. Make your changes (npm run dev for hot-reload)
  4. npm run type-check && npm run build must pass
  5. Open a pull request with a clear description of why, not just what

📄 License

MIT © react-smart-scheduler contributors


Built with ❤️ and TypeScript  ·  If this saves you time, consider starring ⭐ the repo