rosterflow-scheduler
v1.0.1
Published
A high-performance React shift-scheduler component supporting 500+ employees, virtual scrolling, and API-driven roster data.
Maintainers
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-fnsQuick 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. employeesandslotsare 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.jsonStep 2 — Install library dependencies
# In the root (library folder)
npm installStep 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:5173The 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 enabledStep 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/READMEStep 6 — Publish
# Unscoped package:
npm publish
# Scoped package (public):
npm publish --access publicStep 7 — Verify
npm info rosterflow-scheduler
# or visit https://www.npmjs.com/package/rosterflow-schedulerUsing after publishing
npm install rosterflow-scheduler
# or
npm install @yourusername/rosterflow-schedulerLicense
MIT
