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-easy-appointments

v1.0.0

Published

React compound-component calendar for appointment scheduling

Readme

react-easy-appointments

A flexible, high-contrast, fully typed React compound-component calendar for appointment scheduling. It features month and week views, a built-in admin panel, bulk slot generation, and a dual-mode engine (Headless & Styled).

npm version License: MIT


Installation

npm install react-easy-appointments date-fns
# or
pnpm add react-easy-appointments date-fns
# or
yarn add react-easy-appointments date-fns

Make sure react and react-dom (v18 or v19) are installed in your project.


Dual Mode: Styled vs Headless

react-easy-appointments offers two different integration styles:

1. Styled Mode (Default)

Import the ready-to-use stylesheet. The component applies high-contrast, modern styles using a Slate color palette that dynamically adapts to light and dark modes.

import { Calendar } from 'react-easy-appointments'
import 'react-easy-appointments/styles'
import { useState } from 'react'
import type { Slot } from 'react-easy-appointments'

function App() {
  const [slots, setSlots] = useState<Slot[]>([])
  const [selected, setSelected] = useState<Slot | null>(null)
  const [modalOpen, setModalOpen] = useState(false)

  return (
    <div style={{ maxWidth: '800px', margin: '2rem auto' }}>
      <Calendar
        slots={slots}
        onSlotClick={(slot) => {
          setSelected(slot)
          setModalOpen(true)
        }}
        onBook={(slot, data) => {
          // Book slot handler
          setModalOpen(false)
        }}
      >
        <Calendar.Toolbar />
        <Calendar.MonthView />
        <Calendar.WeekView />

        <Calendar.BookingModal
          slot={selected}
          open={modalOpen}
          onClose={() => setModalOpen(false)}
        />
      </Calendar>
    </div>
  )
}

2. Headless Mode

Set headless={true} to strip away all default class names, rendering only raw semantic HTML. Style everything yourself with plain CSS, TailwindCSS, or any other approach.

import { Calendar } from 'react-easy-appointments'
// Do NOT import 'react-easy-appointments/styles'

function HeadlessApp() {
  return (
    <Calendar slots={slots} headless={true}>
      <Calendar.Toolbar />
      <Calendar.MonthView />
      <Calendar.WeekView />
    </Calendar>
  )
}

Admin Control Panel

Calendar.AdminPanel lets you manage appointment availability, bulk-generate slots, view current bookings, cancel appointments, and configure week view hours. It can be placed inside a <Calendar> or used as a standalone component.

<Calendar.AdminPanel
  slots={slots}
  appointments={appointments}
  weekHourStart={7}
  weekHourEnd={20}
  onCreateSlot={(date, start, end) => {
    // Return true on success, false if the slot overlaps an existing one
    return addSlot(date, start, end)
  }}
  onCreateSlots={(newSlots) => {
    setSlots(prev => [...prev, ...newSlots.map(s => ({ ...s, id: uuid(), status: 'available' }))])
  }}
  onRemoveSlot={(slotId) => removeSlot(slotId)}
  onCancelAppointment={(apptId) => cancelAppointment(apptId)}
  onWeekHourStartChange={(h) => setWeekHourStart(h)}
  onWeekHourEndChange={(h) => setWeekHourEnd(h)}
/>

Standalone usage (outside <Calendar>): pass slots, theme, and headless explicitly — they will not be inherited from context.

<Calendar.AdminPanel
  slots={slots}
  theme="dark"
  headless={false}
  onCreateSlot={...}
  onCreateSlots={...}
/>

Quick Generate Modal

Calendar.QuickGenerateModal is a standalone dialog for bulk-creating slots over a date range. It is used internally by Calendar.AdminPanel but can also be rendered independently.

<Calendar.QuickGenerateModal
  open={open}
  onClose={() => setOpen(false)}
  onGenerate={(slots) => {
    setSlots(prev => [...prev, ...slots.map(s => ({ ...s, id: uuid(), status: 'available' }))])
  }}
  defaultDuration={30}
  defaultStartTime="08:00"
  defaultEndTime="18:00"
/>

The modal lets the user pick a date range, repeat days of the week, a time window, and slot duration (preset buttons: 15m, 30m, 45m, 1h, 1.5h, 2h — plus a custom minutes input).


Component API

<Calendar> Props

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | slots | Slot[] | Required | Array of available, booked, and unavailable slots | | onSlotClick | (slot: Slot) => void | — | Callback when a slot element is clicked | | onBook | (slot: Slot, data: BookingFormData) => void | — | Callback when BookingModal form submits | | defaultView | 'month' \| 'week' | 'month' | Initial view layout | | headless | boolean | false | Disable all default stylesheets and BEM classes | | weekStartsOn | 0 \| 1 | 0 (Sunday) | Start day of the week — 0 = Sunday, 1 = Monday | | locale | string | 'en-US' | Language code for formatting titles and day names | | theme | 'light' \| 'dark' \| 'auto' | 'light' | Color scheme — 'auto' follows the OS preference and updates live | | weekHourStart | number | 7 | First hour shown on the week view (0–23) | | weekHourEnd | number | 20 | Last hour shown on the week view (1–24, inclusive) |

<Calendar.BookingModal> Props

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | slot | Slot \| null | Required | The target slot to book | | open | boolean | Required | Shows or hides the modal dialog | | onClose | () => void | Required | Called when the close button or backdrop is clicked |

<Calendar.AdminPanel> Props

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | onCreateSlot | (date: string, start: string, end: string) => boolean | Required | Called when a single slot is submitted. Return false to signal an overlap — the panel will show an error. | | onCreateSlots | (slots: { date: string; startTime: string; endTime: string }[]) => void | Required | Called with the array of slots generated by the Quick Generate dialog | | slots | Slot[] | context | Slot list. Falls back to the parent <Calendar> context when nested. | | appointments | Appointment[] | [] | Active appointments to display and manage | | theme | 'light' \| 'dark' | context | Theme for standalone use (inherited from context when nested) | | headless | boolean | context | Headless mode for standalone use | | weekHourStart | number | context | Week view start hour (for the hour-range control) | | weekHourEnd | number | context | Week view end hour (for the hour-range control) | | onRemoveSlot | (slotId: string) => void | — | Called with each selected slot ID when bulk-deleting. Omit to hide delete controls. | | onCancelAppointment | (apptId: string) => void | — | Called with each selected appointment ID when bulk-cancelling. Omit to hide cancel controls. | | onWeekHourStartChange | (h: number) => void | — | Called when the user changes the start-hour input. Providing this prop reveals the Week View Hours section. | | onWeekHourEndChange | (h: number) => void | — | Called when the user changes the end-hour input. Providing this prop reveals the Week View Hours section. |

<Calendar.QuickGenerateModal> Props

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | open | boolean | Required | Shows or hides the modal | | onClose | () => void | Required | Called when the modal is dismissed | | onGenerate | (slots: { date: string; startTime: string; endTime: string }[]) => void | Required | Called with the generated slot data on submit | | defaultDuration | number | 30 | Pre-selected duration in minutes | | defaultStartTime | string | '08:00' | Default daily start time (24h HH:MM) | | defaultEndTime | string | '18:00' | Default daily end time (24h HH:MM) |


Theme Customization (CSS Variables)

In Styled Mode, override any CSS variable on .rea-calendar in your own stylesheet. All variables are listed in variables.css.

.rea-calendar {
  /* Color accents */
  --rea-btn-primary-bg:        #7c3aed;
  --rea-btn-primary-bg-hover:  #6d28d9;
  --rea-slot-available-bg:     #15803d;
  --rea-slot-booked-bg:        #7c3aed;
  --rea-today-indicator:       #7c3aed;

  /* Borders & backgrounds */
  --rea-border-default:        #cbd5e1;
  --rea-bg-base:               #ffffff;
  --rea-bg-subtle:             #f1f5f9;

  /* Shape */
  --rea-radius-btn:            6px;
  --rea-radius-lg:             10px;

  /* Layout */
  --rea-cell-min-height:       88px;
  --rea-week-row-height:       60px;
}

Complete token categories:

| Category | Variables | | :--- | :--- | | Surfaces | --rea-bg-base, --rea-bg-elevated, --rea-bg-subtle, --rea-bg-wash, --rea-bg-overlay | | Borders | --rea-border-default, --rea-border-focus | | Text | --rea-text-primary, --rea-text-secondary, --rea-text-disabled, --rea-text-on-accent | | Slot states | --rea-slot-available-bg/text, --rea-slot-booked-bg/text, --rea-slot-unavailable-bg/text | | Buttons | --rea-btn-primary-bg/text, --rea-btn-ghost-border | | Accents | --rea-today-indicator | | Motion | --rea-duration-fast, --rea-duration-base, --rea-ease-out | | Shadows | --rea-shadow-sm, --rea-shadow-modal, --rea-shadow-focus | | Shape | --rea-radius-sm/md/lg/btn/pill | | Layout | --rea-cell-min-height, --rea-cell-padding, --rea-toolbar-height, --rea-time-gutter-width, --rea-week-row-height | | Typography | --rea-font-family, --rea-font-size-base/sm/xs |

Deprecated (removed in v0.3): The old --rea-color-* aliases (--rea-color-available, --rea-color-booked, --rea-color-bg, etc.) still work but will be removed. Migrate to the --rea-slot-*, --rea-bg-* naming shown above.


Types

export type SlotStatus = 'available' | 'booked' | 'unavailable'

export interface Slot {
  id: string
  date: string          // ISO date: "YYYY-MM-DD"
  startTime: string     // 24h format: "HH:MM"
  endTime: string       // 24h format: "HH:MM"
  status: SlotStatus
  bookedByLabel?: string
}

export interface BookingFormData {
  subject: string
  notes: string
  durationMinutes: number  // auto-computed from slot start/end times
}

export interface Appointment {
  id: string
  slotId: string
  userLabel: string
  subject: string
  notes?: string
  durationMinutes: number
  status: 'confirmed' | 'cancelled'
}

export type CalendarView = 'month' | 'week'
export type CalendarTheme = 'light' | 'dark' | 'auto'

License

MIT License.