@nicola9779/nicalendar-custom
v0.1.4
Published
Reusable React calendar component with month, week, day and agenda views.
Readme
@nicola9779/nicalendar-custom
Reusable React calendar library with an opinionated default UI and full customization APIs.
What You Get
- Views:
month,week,day,agenda - Fully prop-driven data model (no internal fetching)
- One main layout card by default:
- filters section
- calendar toolbar (
prev/today/next + month label + view tabs) - legend
- calendar grid/list
- Summary KPI cards (enabled by default)
- Filter system with custom input type + custom predicates
- Self-contained style via
@nicola9779/nicalendar-custom/styles.css - Runtime customization via:
filterssummaryCardslegendItemsthemeclassNamesrenderers
Install
npm i @nicola9779/nicalendar-customPeer dependencies:
react^18 || ^19react-dom^18 || ^19
Quick Start
import { Calendar, type CalendarEvent } from "@nicola9779/nicalendar-custom";
import "@nicola9779/nicalendar-custom/styles.css";
const events: CalendarEvent[] = [
{
id: "1",
title: "Policy renewal",
date: new Date(),
start: "10:00",
end: "11:00",
client: "Northwind Corp",
assignee: "Nico",
status: "pending",
priority: "normal",
type: "manual",
},
];
export default function App() {
return (
<Calendar
events={events}
initialView="month"
currentUser="Nico"
showSummary
showFilters
showLegend
/>
);
}Default Behaviors
showSummary:trueshowFilters:trueshowLegend:true- Filter section is collapsible (
Nascondi/Mostra) - Legend defaults include:
Normal,High,Critical,Call,Task - Event marker uses a colored dot based on
eventColorMode
Custom Summary Cards
<Calendar
events={events}
summaryCards={[
{ id: "due", label: "Due This Week", value: 7, color: "#2563eb" },
{ id: "overdue", label: "Overdue", value: 2, color: "#e11d48" },
{ id: "pending", label: "Pending", value: 11, color: "#f59e0b" },
{ id: "completed", label: "Completed", value: 4, color: "#16a34a" },
]}
/>Custom Filters (names + input type + logic)
import type { CalendarFilterConfig } from "@nicola9779/nicalendar-custom";
const filters: CalendarFilterConfig[] = [
{
id: "clientSearch",
label: "Filtro Clienti",
input: "text",
placeholder: "Search clients...",
row: 1,
predicate: (event, value) => {
const q = String(value ?? "").trim().toLowerCase();
return !q || event.client.toLowerCase().includes(q);
},
},
{
id: "status",
label: "Status",
input: "single-select",
row: 1,
options: [
{ value: "", label: "All" },
{ value: "pending", label: "Pending" },
{ value: "overdue", label: "Overdue" },
{ value: "completed", label: "Completed" },
],
bindTo: "statuses",
defaultValue: "",
},
{
id: "onlyMine",
label: "Solo assegnate a me",
input: "toggle",
bindTo: "onlyMine",
row: 2,
},
];
<Calendar events={events} filters={filters} />Theme Tokens
<Calendar
events={events}
theme={{
background: "#eef2f8",
panel: "#ffffff",
panelStrong: "#f7f9fc",
border: "#d3dceb",
text: "#1e293b",
muted: "#64748b",
accent: "#2563eb",
accentSoft: "#e8efff",
chipBackground: "#f8fafc",
buttonBackground: "#ffffff",
}}
/>Bring Your Own UI (shadcn/radix/custom)
Use renderers to replace default controls/components.
<Calendar
events={events}
renderers={{
Button: ({ className, children, onClick, ariaLabel }) => (
<button type="button" className={className} onClick={onClick} aria-label={ariaLabel}>
{children}
</button>
),
}}
/>Core Props
events: CalendarEvent[](required)onEventClick?: (event: CalendarEvent) => voidinitialView?: "month" | "week" | "day" | "agenda"currentUser?: stringshowSummary?: booleanshowFilters?: booleanshowLegend?: booleansummaryCards?: CalendarSummaryCard[]legendItems?: CalendarLegendItem[]filters?: CalendarFilterConfig[]eventColorMode?: "priority" | "status" | "type" | "custom"classNames?: Partial<CalendarClassNames>renderers?: CalendarRendererstheme?: Partial<CalendarThemeTokens>
Event Type
type CalendarEvent = {
id: string;
title: string;
date: Date;
start: string;
end: string;
client: string;
assignee: string;
status: "pending" | "overdue" | "completed";
priority: "normal" | "high" | "critical";
type: "manual" | "recurring" | "system";
color?: string;
tags?: { id: string; label: string; color?: string; textColor?: string }[];
};Development
npm run typecheck
npm run build
npm run test:runStorybook
Storybook is configured to document and test the calendar in isolation.
Run locally
npm run storybookOpen http://localhost:6006.
Build static docs
npm run build-storybookPublish Storybook online (GitHub Pages)
Questo repository include il workflow:
.github/workflows/storybook-pages.yml
Per abilitarlo:
- Push su
main(o run manuale del workflow da Actions) - In GitHub:
Settings -> Pages -> Source: GitHub Actions - Usa l'URL Pages del repository per condividere Storybook con il team
Included stories
- Default
- Empty state
- Views: Month / Week / Day / Agenda
- Interaction: event click handling, filters enabled
- Edge cases: many events, only critical events, only completed events
Quality Gates
Before release, the package runs:
- TypeScript checks
- Unit/integration tests with Vitest + React Testing Library
- Build generation (
.mjs,.cjs,.d.ts,dist/styles.css)
prepublishOnly runs all checks automatically.
Versioning & Release Management
This package follows strict semantic versioning:
patch: bug fixes onlyminor: non-breaking improvementsmajor: breaking API changes
Changes are tracked with Changesets:
npm run changeset
npm run version-packages
npm run releaseEvery release includes an explicit change type and bump reason.
npm Publish automation (recommended)
Workflow:
.github/workflows/publish-npm.yml
Requisito:
- Secret GitHub
NPM_TOKENcon permessi publish sul package scoped.
Trigger:
- manuale da Actions
- automatico quando pubblichi una GitHub Release
Comando locale consigliato prima di creare la release:
npm version patch
git push origin main --follow-tagsTroubleshooting (important for local dev)
If you update library styles and do not see changes in playground, clear Vite cache and force reload:
cd ../calendar
npm run build
cd ../playground
rm -rf node_modules/.vite
npm run dev -- --force