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

@bernierllc/calendar

v1.2.2

Published

A calendar package for the tools monorepo.

Readme

/* 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. */

@bernierllc/calendar

A highly customizable, accessible React calendar component for Next.js projects with support for template-driven and render prop-based customization.

Features

  • 📅 Month and Week Views - Toggle between calendar views
  • 🎨 Flexible Styling - Custom colors, pills, and styling logic
  • 🧩 Template Tags - String-based customization with {{fieldName}} syntax
  • ⚛️ Render Props - Full React component customization
  • 📋 Event List - Sidebar list of events with custom rendering
  • 🔍 Dynamic Filters - Custom filter UI with render props
  • Accessible - WCAG 2.1 AA compliant with keyboard navigation
  • 📤 ICS Export - Generate iCalendar files with recurring events
  • 🎯 TypeScript - Full type safety and IntelliSense support

Installation

npm install @bernierllc/calendar

Quick Start

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' }}
    />
  );
}

API Reference

Calendar Props

| 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) |

Template-Driven Customization

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

Render Prop Customization

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

CalendarEvent Interface

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
}

Usage Examples

Basic Usage

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' }}
/>

Template Tags

<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}}"
/>

Render Props

<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>
  )}
/>

Custom Styling Logic

<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'
  })}
/>

Custom Filters

<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>
  )}
/>

ICS Export

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();
};

Advanced Patterns

Combining Template Tags and Render Props

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
  }}
/>

Custom Fields with Type Safety

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
  }
];

Dynamic Filtering with Custom Fields

<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>
  )}
/>

Accessibility

The calendar is built with accessibility in mind:

  • Keyboard Navigation - Full keyboard support for navigation and interaction
  • ARIA Labels - Proper ARIA attributes for screen readers
  • Focus Management - Logical tab order and focus indicators
  • Semantic HTML - Proper use of table elements and landmarks
  • Color Contrast - Meets WCAG 2.1 AA contrast requirements

Styling

The calendar uses Tailwind CSS classes. You can customize the appearance by:

  1. Overriding Tailwind classes in your CSS
  2. Using render props for complete control
  3. Using template tags for simple text customization
  4. Using getEventColor and getEventPill for dynamic styling

Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

License

ISC License - see LICENSE file for details.

Contributing

See CONTRIBUTING.md for development guidelines.

Monorepo Workspaces & Dependency Management

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.

React 19 and Testing Library Compatibility

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.