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-resource-calendar

v0.1.0

Published

A resource calendar component for React.

Downloads

172

Readme

React Resource Calendar

A typed React scheduling grid for appointments distributed across rooms, staff, equipment, or any other resources.

Install

npm install react-resource-calendar

React 18 and 19 are supported. Import the compiled stylesheet once in your application entry point; your application does not need Tailwind CSS.

import 'react-resource-calendar/styles.css';

The grid fills its parent, so render it inside an element with an explicit height.

Basic usage

import {
  ResourceCalendar,
  type GridEvent,
  type GridResource,
  type GridSlotClickInfo,
} from 'react-resource-calendar';
import 'react-resource-calendar/styles.css';

const resources: GridResource[] = [
  { id: 'room-a', label: 'Studio A', subTitle: 'Main floor', colorTheme: 'blue' },
  { id: 'room-b', label: 'Studio B', subTitle: 'Second floor', colorTheme: 'teal' },
];

const events: GridEvent[] = [
  {
    id: 'booking-1',
    title: 'Product shoot',
    resourceIds: ['room-a'],
    startsAt: '2026-09-04T09:00:00',
    endsAt: '2026-09-04T10:30:00',
    subTitle: 'Northwind Studio',
    colorTheme: 'blue',
  },
];

export function Schedule() {
  const handleEmptySlot = (slot: GridSlotClickInfo) => {
    console.log('Create at', slot.date, 'for', slot.resourceId);
  };

  return (
    <div style={{ height: 720 }}>
      <ResourceCalendar
        startsAt={new Date(2026, 8, 4)}
        endsAt={new Date(2026, 8, 5)}
        resources={resources}
        events={events}
        intervalMinutes={15}
        onGridClick={handleEmptySlot}
        onEventClick={(event) => console.log('Selected', event)}
      />
    </div>
  );
}

The component is controlled: your application owns the resource and event arrays. Use callbacks to open your editor, update application state, and pass the new arrays back to the grid.

Resource data

Every resource needs a stable id and a display label.

interface GridResource<TMeta = Record<string, unknown>> {
  id: string;
  label: string;
  subTitle?: string;
  color?: string;
  colorTheme?: string;
  meta?: TMeta;
}

Use GridResource<TMeta> to keep application-specific metadata type-safe.

Event data

An event requires id and title, plus one or more resources and a time range. Use full timestamps:

{
  startsAt: '2026-09-04T09:00:00-04:00',
  endsAt: '2026-09-04T10:30:00-04:00'
}

Both startsAt and endsAt are required and accept Date objects or ISO date-time strings. Times are displayed in the browser's local timezone. Include an offset (e.g. -04:00 or Z) to identify a specific instant, or omit it for local time. endsAt must be later than startsAt. Missing, invalid, or reversed ranges throw RangeError; the calendar does not invent fallback dates or infer overnight ends. For overnight events, explicitly put the following date in endsAt.

Migration: date, endDate, startTime, and endTime were removed from GridEvent. Combine each date and time into its corresponding timestamp.

Assign resources with the required resourceIds array, including for a single resource. An empty array leaves an event unassigned (not rendered). Duplicate IDs are treated as one assignment.

Migration: Replace event resourceId: 'room-a' with resourceIds: ['room-a']. Slot-click callbacks still return a single resourceId, identifying the clicked column.

To span several resources:

const multiRoomEvent: GridEvent = {
  id: 'booking-2',
  title: 'Company workshop',
  resourceIds: ['room-a', 'room-b'],
  startsAt: '2026-09-04T13:00:00',
  endsAt: '2026-09-04T15:00:00',
};

Use GridEvent<TData> and its data property to retain your domain model in callbacks.

The calendar only accepts fields used for display, scheduling, or customization. Store application-specific information in event.data and resource.meta:

const booking: GridEvent<{ patient: string; status: string; notes: string }> = {
  id: 'booking-3',
  title: 'Consultation',
  resourceIds: ['room-a'],
  startsAt: '2026-09-04T09:00:00',
  endsAt: '2026-09-04T10:00:00',
  subTitle: 'Alex Morgan',
  data: { patient: 'Alex Morgan', status: 'confirmed', notes: 'First visit' },
};

Migration: Event fields patient, practitioner, roomName, type, notes, and status were removed. Move them into data; use subTitle for secondary card text. Resource fields type, category, location, active, and order were removed; move these into meta and filter/sort resources before passing them in. The unused event input conflictOverlapSide was also removed; the calendar still calculates it in GridEventLayout. hasConflict remains available to override automatic overlap detection.

Date ranges

Pass JavaScript Date objects to startsAt and endsAt for one exact time window. The start is inclusive and the end is exclusive; a full day runs from midnight to midnight the following day. Both endpoints are required when either is provided. With neither, the calendar displays today.

For working hours, gaps, or explicit continuous boundaries, use dateRanges:

<ResourceCalendar
  dateRanges={[
    {
      id: 'friday',
      startsAt: new Date(2026, 8, 4, 8),
      endsAt: new Date(2026, 8, 4, 18),
      label: 'Friday, September 4',
    },
    {
      id: 'saturday',
      startsAt: new Date(2026, 8, 5, 9),
      endsAt: new Date(2026, 8, 5, 14),
      label: 'Saturday, September 5',
    },
  ]}
  resources={resources}
  events={events}
/>

Ranges are sorted chronologically. Exactly adjacent windows render as one continuous timeline; any gap creates a separate section. Overlapping, invalid, or non-positive windows throw RangeError. An explicit empty dateRanges array displays no windows.

Minutes and seconds are preserved. For example, 08:30–17:30 produces partial-hour rows at both ends. Overnight and multi-day windows split at local midnight for date headers, while positions and heights follow elapsed time (including daylight-saving transitions).

Migration: startHour and endHour were removed from the calendar and date-range props. Specify those times directly in each range’s timestamps. Top-level startsAt/endsAt now describe one exact window rather than an inclusive list of whole dates. Use dateRanges for separate daily working hours.

Important props

| Prop | Type | Default | Purpose | | --- | --- | --- | --- | | resources | GridResource[] | required | Resource columns | | events | GridEvent[] | required | Events to display | | startsAt / endsAt | Date | today | Exact time window, end exclusive | | dateRanges | GridDateRange[] | — | Explicit date/time windows | | intervalMinutes | number | 15 | Clickable sub-slot duration | | subHourGrading | none \| dashed \| solid \| dotted | none | Sub-hour divider style | | timeSlotHeight | number | 48 | Pixels per hour | | timeColumnWidth | number | 80 | Time-gutter width | | resourceColumnWidth | number | 180 | Minimum resource width | | cornerLabel | string | — | Label in the top-left corner cell; hidden when omitted | | theme | string | default CSS palette | CSS theme name (data-grid-theme) | | className | string | empty | Extra root classes | | ariaLabel | string | Resource scheduling grid | Accessible region name |

Interaction callbacks

<ResourceCalendar
  resources={resources}
  events={events}
  onGridClick={(slot) => openCreateDialog(slot)}
  onEventClick={(event) => openEventDialog(event)}
  onResourceHeaderClick={(resource) => openResourceDialog(resource)}
/>

onGridClick receives the selected Date, YYYY-MM-DD string, resource ID, HH:mm time, and minute offset.

Custom event rendering

<ResourceCalendar
  resources={resources}
  events={events}
  renderEvent={(event, layout) => (
    <div>
      <strong>{event.title}</strong>
      {layout.hasConflict && <span>Conflict</span>}
    </div>
  )}
/>

Each resource can define render(resource), and each individual event can define render(event, layout).

Themes

Choose default, warm, clinical, or dark:

<ResourceCalendar theme="dark" resources={resources} events={events} />

Define your own theme in a stylesheet loaded after the package CSS:

import 'react-resource-calendar/styles.css';
import './calendar-theme.css';

<ResourceCalendar className="booking-calendar" resources={resources} events={events} />
.booking-calendar {
  --grid-primary: #7c3aed;
  --grid-surface: #fafafa;
  --grid-border: #ddd6fe;
  --grid-event-blue-bg: #ede9fe;
  --grid-event-blue-border: #7c3aed;
  --grid-event-blue-text: #4c1d95;
}

Override only the variables you need. Set overrides on the calendar itself (as above), rather than on :root, because the calendar defines its own defaults. Variables can reference app tokens, for example --grid-primary: var(--app-accent). Each calendar can have its own theme. theme="brand" simply sets data-grid-theme="brand"; define .react-resource-calendar[data-grid-theme="brand"] in your stylesheet. Without an explicit theme, a .dark ancestor selects the dark preset.

Available palette variables are --grid-surface, --grid-surface-subtle, --grid-surface-container, --grid-surface-card, --grid-surface-hover, --grid-border, --grid-border-strong, --grid-text-primary, --grid-text-secondary, --grid-text-muted, --grid-primary, --grid-focus-ring, --grid-error, --grid-error-bg, and --grid-error-border.

Event categories (colorTheme) include blue, teal, amber, purple, rose, and emerald. Each has --grid-event-CATEGORY-bg, -border, -text, and -badge variables. Unknown categories fall back to blue. Define a custom category using local variables:

.booking-calendar [data-grid-color="surgery"] {
  --grid-event-bg: #f0fdfa;
  --grid-event-border: #0d9488;
  --grid-event-text: #134e4a;
  --grid-event-badge: #ccfbf1;
}

This applies to both event cards and resource tabs. Custom event renderers also receive the category variables through their wrapper. Conflict colors use the error variables. Calculated positions and dimensions remain controlled by the layout props.

Migration: JavaScript theme objects, theme context/hooks, theme helpers, and their types have been removed. Replace object-valued theme props with CSS overrides; string-valued preset props continue to work.

TypeScript exports

The main entry point exports the root component, internal building blocks, layout utilities, and all public types. Common types include:

  • ResourceCalendarProps
  • GridResource
  • GridEvent
  • GridDateRange
  • GridSlotClickInfo
  • GridEventLayout

Browser and layout notes

  • The component is client-side React and requires DOM APIs.
  • It does not fetch, mutate, or persist data.
  • Dates use the browser's local timezone; include an explicit offset in ISO timestamps when needed.
  • cornerLabel changes the displayed corner label only.
  • Give the parent element a usable height because the grid uses height: 100% internally.

License

MIT