
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@bernierllc/calendar
Advanced tools
/* Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC. */
A highly customizable, accessible React calendar component for Next.js projects with support for template-driven and render prop-based customization.
{{fieldName}} syntaxnpm install @bernierllc/calendar
import { Calendar } from '@bernierllc/calendar';
import type { CalendarEvent } from '@bernierllc/calendar';
const events: CalendarEvent[] = [
{
uid: '1',
summary: 'Team Meeting',
description: 'Weekly team sync',
start: '2024-01-15T10:00:00Z',
end: '2024-01-15T11:00:00Z',
categories: ['work'],
location: 'Conference Room A'
}
];
function App() {
return (
<Calendar
events={events}
categoryColors={{ work: 'bg-blue-500' }}
/>
);
}
| Prop | Type | Description |
|---|---|---|
events | CalendarEvent[] | Array of calendar events |
categoryColors | Record<string, string> | Predefined color mapping for categories |
initialView | 'week' | 'month' | Initial calendar view (default: 'month') |
filters | ReactNode | Function | Custom filter UI or render prop |
getEventColor | (event: CalendarEvent) => string | Custom color logic function |
getEventPill | (event: CalendarEvent) => { label: string, color: string } | null | Custom pill logic for event list |
ellipsizeTitles | boolean | Always ellipsize event titles (default: true) |
| Prop | Type | Description |
|---|---|---|
eventDescriptionTemplate | string | Template for event description/body |
eventModalTemplate | string | Template for event modal content |
eventCellTemplate | string | Template for calendar grid cells |
eventListRowTemplate | string | Template for event list rows |
| Prop | Type | Description |
|---|---|---|
eventModalRenderer | (event: any) => ReactNode | Custom modal rendering |
eventCellRenderer | (event: any, context: { date: Date }) => ReactNode | Custom calendar cell rendering |
eventListRowRenderer | (event: any) => ReactNode | Custom event list row rendering |
interface CalendarEvent {
uid: string; // Unique identifier
summary: string; // Event title
description?: string; // Event description
location?: string; // Event location
start: string; // ISO 8601 start date
end: string; // ISO 8601 end date
recurrenceRule?: string; // RRULE string for recurring events
status?: 'CONFIRMED' | 'TENTATIVE' | 'CANCELLED';
organizer?: string; // Organizer email/name
attendees?: string[]; // Attendee emails
categories?: string[]; // Event categories for color coding
[key: string]: any; // Custom fields supported
}
import { Calendar } from '@bernierllc/calendar';
const events = [
{
uid: '1',
summary: 'Team Meeting',
start: '2024-01-15T10:00:00Z',
end: '2024-01-15T11:00:00Z',
categories: ['work']
}
];
<Calendar
events={events}
categoryColors={{ work: 'bg-blue-500', personal: 'bg-green-500' }}
/>
<Calendar
events={events}
eventDescriptionTemplate="Mentor: {{mentor}}, Location: {{location}}, Type: {{experienceType}}"
eventModalTemplate="<h3>{{summary}}</h3><p>Mentor: {{mentor}}</p><p>Experience: {{experienceType}}</p>"
eventCellTemplate="{{summary}} ({{mentor}})"
eventListRowTemplate="{{summary}} - {{mentor}} - {{experienceType}}"
/>
<Calendar
events={events}
eventModalRenderer={(event) => (
<div className="custom-modal">
<h2>{event.summary}</h2>
<p>Mentor: {event.mentor}</p>
<p>Experience: {event.experienceType}</p>
<button onClick={() => handleEdit(event)}>Edit</button>
</div>
)}
eventCellRenderer={(event, { date }) => (
<div className={`event-cell ${event.experienceType}`}>
<span className="title">{event.summary}</span>
<span className="mentor">{event.mentor}</span>
</div>
)}
/>
<Calendar
events={events}
getEventColor={(event) => {
if (event.experienceType === 'mentorship') return 'bg-purple-500';
if (event.experienceType === 'workshop') return 'bg-orange-500';
return 'bg-gray-500';
}}
getEventPill={(event) => ({
label: event.experienceType,
color: event.experienceType === 'mentorship' ? 'bg-purple-100 text-purple-800' : 'bg-orange-100 text-orange-800'
})}
/>
<Calendar
events={events}
filters={({ visibleEvents, filters, setFilters }) => (
<div className="filters">
<select
value={filters.experienceType || ''}
onChange={(e) => setFilters({ ...filters, experienceType: e.target.value })}
>
<option value="">All Types</option>
<option value="mentorship">Mentorship</option>
<option value="workshop">Workshop</option>
</select>
<span>{visibleEvents.length} events</span>
</div>
)}
/>
import { generateICS } from '@bernierllc/calendar/utils/ics';
const handleExport = () => {
const icsString = generateICS(events);
const blob = new Blob([icsString], { type: 'text/calendar' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'calendar.ics';
a.click();
};
Template tags are used as fallbacks when render props aren't provided:
<Calendar
events={events}
eventModalTemplate="<h3>{{summary}}</h3><p>{{description}}</p>"
eventModalRenderer={(event) => {
// Custom logic for specific events
if (event.experienceType === 'mentorship') {
return <MentorshipModal event={event} />;
}
// Fallback to template for other events
return null; // Will use template
}}
/>
interface CustomEvent extends CalendarEvent {
mentor: string;
experienceType: 'mentorship' | 'workshop' | 'networking';
difficulty: 'beginner' | 'intermediate' | 'advanced';
maxParticipants?: number;
}
const events: CustomEvent[] = [
{
uid: '1',
summary: 'React Workshop',
start: '2024-01-15T10:00:00Z',
end: '2024-01-15T12:00:00Z',
mentor: 'John Doe',
experienceType: 'workshop',
difficulty: 'intermediate',
maxParticipants: 20
}
];
<Calendar
events={events}
filters={({ visibleEvents, filters, setFilters }) => (
<div className="space-y-2">
<select
value={filters.experienceType || ''}
onChange={(e) => setFilters({ ...filters, experienceType: e.target.value })}
className="border rounded px-2 py-1"
>
<option value="">All Experience Types</option>
<option value="mentorship">Mentorship</option>
<option value="workshop">Workshop</option>
<option value="networking">Networking</option>
</select>
<select
value={filters.difficulty || ''}
onChange={(e) => setFilters({ ...filters, difficulty: e.target.value })}
className="border rounded px-2 py-1"
>
<option value="">All Difficulties</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
<div className="text-sm text-gray-600">
{visibleEvents.length} events found
</div>
</div>
)}
/>
The calendar is built with accessibility in mind:
The calendar uses Tailwind CSS classes. You can customize the appearance by:
getEventColor and getEventPill for dynamic stylingISC License - see LICENSE file for details.
See CONTRIBUTING.md for development guidelines.
This package is part of a monorepo using npm workspaces. All dependencies are hoisted to the root. Always run npm install from the root directory.
This package uses React 19.1.0. If you see peer dependency warnings with Testing Library, use:
npm install --legacy-peer-deps
This is a temporary workaround until official support is released.
FAQs
A calendar package for the tools monorepo.
We found that @bernierllc/calendar demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.