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

rosterflow-scheduler

v1.0.1

Published

A high-performance React shift-scheduler component supporting 500+ employees, virtual scrolling, and API-driven roster data.

Readme

rosterflow-scheduler

A high-performance React shift-scheduler component.

  • Virtual scrolling — renders 500+ employees smoothly
  • API-driven — triggers onDateRangeChange(startDate, endDate, view) on every navigation or view switch
  • Skeleton loading — beautiful loading states for employees and slots independently
  • Zero style dependencies — no Tailwind, no CSS-in-JS; pure inline styles + CSS variables for theming
  • TypeScript — full types exported

Installation

npm install rosterflow-scheduler
# peer deps (if not already installed)
npm install react react-dom date-fns

Quick Start

import { useState, useEffect, useCallback } from "react";
import { Scheduler } from "rosterflow-scheduler";
import type {
  Employee,
  RosterSlot,
  DateRange,
  ViewMode,
} from "rosterflow-scheduler";

export default function MyPage() {
  const [employees, setEmployees] = useState<Employee[]>([]);
  const [slots, setSlots] = useState<RosterSlot[]>([]);
  const [loadingEmployees, setLoadingEmployees] = useState(true);
  const [loadingSlots, setLoadingSlots] = useState(false);

  // 1. Load employee list once (or on demand)
  useEffect(() => {
    fetch("/api/employees")
      .then((r) => r.json())
      .then((data) => {
        // Map your fields to { employeeId, employeeName, role?, avatarColor? }
        setEmployees(
          data.map((e) => ({
            employeeId: e.id,
            employeeName: e.fullName,
            role: e.department,
          })),
        );
        setLoadingEmployees(false);
      });
  }, []);

  // 2. Load slots whenever the visible date range changes
  const handleDateRangeChange = useCallback(
    (range: DateRange, view: ViewMode) => {
      setLoadingSlots(true);
      fetch(`/api/roster?start=${range.startDate}&end=${range.endDate}`)
        .then((r) => r.json())
        .then((data) => {
          setSlots(
            data.map((s) => ({
              slotId: s.id,
              employeeId: s.employeeId,
              employeeName: s.employeeName,
              rosterId: s.shiftTypeId,
              rosterName: s.shiftTypeName, // e.g. "Morning", "Night"
              rosterColor: s.color, // hex string e.g. "#3b82f6"
              date: s.date, // "yyyy-MM-dd"
              title: s.label,
              startTime: s.startTime, // "07:00"
              endTime: s.endTime, // "15:00"
              notes: s.notes,
            })),
          );
          setLoadingSlots(false);
        });
    },
    [],
  );

  return (
    <div style={{ height: "100vh", padding: 16 }}>
      <Scheduler
        employees={employees}
        slots={slots}
        loading={loadingEmployees}
        slotsLoading={loadingSlots}
        onDateRangeChange={handleDateRangeChange}
        onSlotClick={(slot) => console.log("edit:", slot)}
        onSlotCreate={({ employeeId, date }) =>
          console.log("create:", employeeId, date)
        }
        style={{ height: "100%" }}
      />
    </div>
  );
}

Props

| Prop | Type | Required | Description | | ------------------- | -------------------------------- | -------- | ---------------------------------------------------- | | employees | Employee[] | ✅ | List of employees | | slots | RosterSlot[] | ✅ | Roster slots for the current date range | | loading | boolean | — | Show skeleton rows (employee list loading) | | slotsLoading | boolean | — | Show slot skeletons (API fetch in progress) | | onDateRangeChange | (range, view) => void | — | Fired on navigation, view change, date-picker select | | onSlotClick | (slot) => void | — | User clicks an existing slot | | onSlotCreate | ({ employeeId, date }) => void | — | User clicks empty cell | | onSlotUpdate | (slot) => void | — | Optional — if you handle edit in-component | | onSlotDelete | (slotId) => void | — | Optional — if you handle delete in-component | | initialView | "week" \| "month" | — | Default "week" | | initialDate | Date | — | Default today | | rowHeight | number | — | Default 52 (px) | | renderSlot | (slot) => ReactNode | — | Custom slot chip renderer | | renderEmployee | (employee) => ReactNode | — | Custom employee cell renderer | | className | string | — | Class on outer wrapper | | style | CSSProperties | — | Style on outer wrapper |

Employee shape

interface Employee {
  employeeId: string; // unique key
  employeeName: string;
  role?: string;
  avatarColor?: string; // hex — auto-generated if omitted
  avatarUrl?: string; // future use
}

RosterSlot shape

interface RosterSlot {
  slotId: string;
  employeeId: string;
  employeeName?: string;
  rosterId: string;
  rosterName: string; // displayed as chip label
  rosterColor: string; // hex used for chip colour
  date: string; // "yyyy-MM-dd"
  title: string;
  startTime?: string; // "07:00"
  endTime?: string; // "15:00"
  notes?: string;
}

DateRange shape (passed to onDateRangeChange)

interface DateRange {
  startDate: string; // "yyyy-MM-dd"  — first visible day
  endDate: string; // "yyyy-MM-dd"  — last visible day
}

Theming (CSS variables)

Override any of these on a parent element:

.my-scheduler-wrapper {
  --rsf-primary: #6366f1; /* accent colour */
  --rsf-bg: #ffffff; /* main background */
  --rsf-header-bg: #ffffff; /* header/sticky-col background */
  --rsf-fg: #111827; /* foreground text */
  --rsf-muted: #6b7280; /* secondary text */
  --rsf-border: #e5e7eb; /* all borders */
  --rsf-accent: #f3f4f6; /* hover / toggle bg */
  --rsf-row-alt: #f9fafb; /* alternating row colour */
  --rsf-skeleton: #e5e7eb; /* skeleton pulse colour */
  --rsf-cell-hover: rgba(99, 102, 241, 0.04);
  --rsf-radius: 12px; /* outer border-radius */
}

Performance notes

  • Rows are virtualised — only the visible viewport (+ overscan) is rendered in the DOM regardless of employee count.
  • Slots are stored in a Map<employeeId::date, slot> for O(1) lookup per cell.
  • employees and slots are memoised internally — passing a stable reference avoids unnecessary re-renders.

Local development setup (step by step)

Step 1 — Clone / set up the repo

# If you cloned from git:
cd rosterflow-scheduler

# Or copy your generated files into this structure:
rosterflow-scheduler/
  src/
    components/Scheduler.tsx
    hooks/useScheduler.ts
    hooks/useVirtualRows.ts
    types/index.ts
    utils/dateUtils.ts
    index.ts
  demo/
    src/
      App.tsx
      main.tsx
    index.html
    package.json
    vite.config.ts
    tsconfig.json
  package.json
  vite.config.ts
  tsconfig.json

Step 2 — Install library dependencies

# In the root (library folder)
npm install

Step 3 — Install demo dependencies

cd demo
npm install
cd ..

Step 4 — Run the demo

# From the root:
cd demo && npm run dev
# Open http://localhost:5173

The demo uses a path alias (rosterflow-scheduler → ../src/index.ts) so changes to the library source are reflected instantly without a build step.

Step 5 — Build the library

# From the root:
npm run build
# Output: dist/index.js (ESM), dist/index.cjs (CJS), dist/index.d.ts (types)

Publishing to npm (step by step)

Step 1 — Login to npm

npm login
# Enter your npm username, password, email, and OTP if 2FA is enabled

Step 2 — Choose a package name

Edit package.json"name" field.

  • Public scoped (recommended): "@yourusername/rosterflow-scheduler"
  • Unscoped: "rosterflow-scheduler" (must be unique on npm)

Step 3 — Set your author info

"author": "omal harsha",

Step 4 — Bump the version (first publish: leave as 1.0.0)

For subsequent releases:

npm version patch   # 1.0.0 → 1.0.1  (bug fixes)
npm version minor   # 1.0.0 → 1.1.0  (new features)
npm version major   # 1.0.0 → 2.0.0  (breaking changes)

Step 5 — Dry run (see what will be published)

npm publish --dry-run
# Check the file list — should only show dist/ and package.json/README

Step 6 — Publish

# Unscoped package:
npm publish

# Scoped package (public):
npm publish --access public

Step 7 — Verify

npm info rosterflow-scheduler
# or visit https://www.npmjs.com/package/rosterflow-scheduler

Using after publishing

npm install rosterflow-scheduler
# or
npm install @yourusername/rosterflow-scheduler

License

MIT