big-calendar

repository·main·Indexed 21 days ago

https://github.com/lramos33/big-calendar

A responsive calendar application built with Next.js, TypeScript, and Tailwind CSS. It features multiple views (Agenda, Year, Month, Week, Day), drag-and-drop rescheduling via react-dnd, and user-based event filtering. The library provides a CalendarProvider for state management, a useCalendar hook for global state access, and pre-built components like ClientContainer, AddEventDialog, and EditEventDialog.

Tokens
5K
Snippets
20
Records
22
Agent score
76%

What's inside big-calendar

  1. Set up the CalendarProvider

    main

    The CalendarProvider is the central state management component. You must wrap your application or the specific page containing the calendar with it, passing in your users and events data. This provides the necessary context for all calendar components to function.

    import { CalendarProvider } from "@/calendar/contexts/calendar-context";
    
    // Fetch your events and users data
    const events = await getEvents();
    const users = await getUsers();
    
    export default function Layout({ children }) {
      return (
        <CalendarProvider users={users} events={events}>
          {children}
        </CalendarProvider>
      );
    }
  2. Install Big Calendar in your project

    main

    To integrate Big Calendar into an existing project, you must manually copy the core source files and install necessary dependencies.

    1. Copy the following directories from the repository to your src/ folder:

      • src/calendar/: Contains all core calendar functionality, components, contexts, and types.
      • src/components/ui/: Contains the shadcn/ui components used by the calendar.
      • src/hooks/: Contains required hooks such as use-disclosure.
    2. Install any missing dependencies required by these files in your project.

  3. Customize event badge variants

    main

    You can control how events are visually represented (e.g., dot, colored, or mixed styles) by using the ChangeBadgeVariantInput component. This component must be placed within the CalendarProvider tree.

    import { ChangeBadgeVariantInput } from "@/calendar/components/change-badge-variant-input";
    
    // Place this anywhere in your project tree inside the CalendarProvider
    <ChangeBadgeVariantInput />;
  4. Access calendar state with the useCalendar hook

    main

    You can access and control the global calendar state (such as the selected date, selected user, or badge variant) from any component nested within the CalendarProvider using the useCalendar hook.

    import { useCalendar } from "@/calendar/contexts/calendar-context";
    
    function MyComponent() {
      const { 
        selectedDate, 
        setSelectedDate, 
        selectedUserId, 
        setSelectedUserId, 
        events, 
        users, 
        badgeVariant, 
        setBadgeVariant 
      } = useCalendar();
    
      // Your component logic
    }
  5. Render a calendar view using ClientContainer

    main

    To display the calendar, use the ClientContainer component. You specify which view to render using the view prop. Supported view values are:

    • day
    • week
    • month
    • year
    • agenda
    import { ClientContainer } from "@/calendar/components/client-container";
    
    export default function CalendarPage() {
      return <ClientContainer view="month" />;
    }
  6. Define the Event and User data structures

    main

    The calendar expects specific data shapes for events and users. While you can modify these interfaces, the following fields are required for the calendar to function correctly.

    Event Interface (IEvent):

    • id: string
    • title: string
    • description: string
    • startDate: string (ISO string)
    • endDate: string (ISO string)
    • color: one of "blue" | "green" | "red" | "yellow" | "purple" | "orange"
    • user: An object containing id (string) and name (string).

    User Interface (IUser):

    • id: string
    • name: string
    • picturePath: string (optional avatar image path)
    interface IEvent {
      id: string;
      title: string;
      description: string;
      startDate: string; // ISO string
      endDate: string; // ISO string
      color: "blue" | "green" | "red" | "yellow" | "purple" | "orange";
      user: {
        id: string;
        name: string;
      };
    }
    
    interface IUser {
      id: string;
      name: string;
      picturePath?: string; // Optional avatar image
    }
  7. Access and manage calendar state with useCalendar

    main

    The useCalendar hook allows any child component within a CalendarProvider to access and modify the global calendar state.

    Available State and Setters:

    • selectedDate: The currently selected Date.
    • setSelectedDate: Function to update the selected date. Accepts Date | undefined (though it ignores undefined).
    • selectedUserId: The ID of the selected user, or 'all' to view everyone.
    • setSelectedUserId: Function to update the selected user ID.
    • badgeVariant: The visual style of badges (TBadgeVariant).
    • setBadgeVariant: Function to update the badge variant.
    • users: The list of IUser objects.
    • workingHours: The TWorkingHours configuration.
    • setWorkingHours: Dispatcher to update working hours.
    • visibleHours: The TVisibleHours configuration (the time range displayed).
    • setVisibleHours: Dispatcher to update visible hours.
    • events: The current list of IEvent objects.
    • setLocalEvents: Dispatcher to update the local event state (useful for simulating updates without a backend refetch).
    import { useCalendar } from '@/calendar/contexts/calendar-context';
    
    function CalendarControl() {
      const { selectedDate, setSelectedDate, selectedUserId, setSelectedUserId } = useCalendar();
    
      return (
        <div>
          <p>Current Date: {selectedDate.toDateString()}</p>
          <button onClick={() => setSelectedDate(new Date())}>Reset Date</button>
          
          <p>Current User: {selectedUserId}</p>
          <button onClick={() => setSelectedUserId('all')}>Show All Users</button>
        </div>
      );
    }
  8. Initialize the calendar state with CalendarProvider

    main

    To use the calendar components and access global state, you must wrap your application (or the calendar container) in the CalendarProvider. The provider requires users and events as props to initialize the calendar's data context.

    Props:

    • children: React nodes to be rendered within the provider.
    • users: An array of IUser objects representing the users available in the calendar.
    • events: An array of IEvent objects representing the initial set of calendar events.
    import { CalendarProvider } from '@/calendar/contexts/calendar-context';
    
    // Assuming users and events are fetched or defined elsewhere
    function App() {
      return (
        <CalendarProvider users={usersData} events={eventsData}>
          <YourCalendarComponents />
        </CalendarProvider>
      );
    }
  9. Use EditEventDialog to edit events

    main

    The EditEventDialog component provides a pre-built dialog interface for editing an existing event's details, including title, description, responsible user, date, time, and color.

    It uses a DialogTrigger pattern, meaning you wrap the element that should trigger the dialog (e.g., an event card or a button) as a child of EditEventDialog.

    Note on Persistence: In the current implementation, the component uses the useUpdateEvent hook to update the event state locally. For production applications, you should replace or augment this logic to submit the form data to a backend API to ensure changes are persisted.

    import { EditEventDialog } from '@/calendar/components/dialogs/edit-event-dialog';
    import type { IEvent } from '@/calendar/interfaces';
    
    interface MyComponentProps {
      event: IEvent;
    }
    
    export function MyComponent({ event }: MyComponentProps) {
      return (
        <EditEventDialog event={event}>
          <button>Edit Event</button>
        </EditEventDialog>
      );
    }
  10. Configure the theme cookie name and expiration

    main

    The big-calendar library uses a cookie to persist the user's selected theme. If you are implementing custom theme persistence or interacting with the library's state via client-side storage, use the following constants:

    • THEME_COOKIE_NAME: The key used to store the theme in the browser's cookies ("big-calendar-theme").
    • THEME_COOKIE_MAX_AGE: The expiration time for the theme cookie, set to 1 year in seconds (31536000).
    • DEFAULT_VALUES: The fallback configuration if no theme is set, which defaults to { theme: "dark" }.
    export const THEME_COOKIE_NAME = "big-calendar-theme";
    export const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
    
    export const DEFAULT_VALUES = { theme: "dark" };
  11. Event data structure and validation schema

    main

    When creating or updating events in Big Calendar, the event data must conform to the eventSchema. This schema ensures that all required fields are present and that the temporal logic (start time vs. end time) is valid.

    Key constraints:

    • title and description must be non-empty strings.
    • startDate and endDate must be valid Date objects.
    • startTime and endTime are objects containing hour (number) and minute (number).
    • color must be one of the following allowed values: "blue", "green", "red", "yellow", "purple", "orange", or "gray".
    • Temporal Validation: The combined start date/time must occur before the combined end date/time. If this condition fails, a validation error is returned on the startDate path with the message "Start date cannot be after end date".
    import { z } from "zod";
    
    export const eventSchema = z
      .object({
        user: z.string(),
        title: z.string().min(1, "Title is required"),
        description: z.string().min(1, "Description is required"),
        startDate: z.date({ required_error: "Start date is required" }),
        startTime: z.object({ hour: z.number(), minute: z.number() }, { required_error: "Start time is required" }),
        endDate: z.date({ required_error: "End date is required" }),
        endTime: z.object({ hour: z.number(), minute: z.number() }, { required_error: "End time is required" }),
        color: z.enum(["blue", "green", "red", "yellow", "purple", "orange", "gray"], { required_error: "Color is required" }),
      })
      .refine(
        data => {
          const startDateTime = new Date(data.startDate);
          startDateTime.setHours(data.startTime.hour, data.startTime.minute, 0, 0);
    
          const endDateTime = new Date(data.endDate);
          endDateTime.setHours(data.endTime.hour, data.endTime.minute, 0, 0);
    
          return startDateTime < endDateTime;
        },
        {
          message: "Start date cannot be after end date",
          path: ["startDate"],
        }
      );
    
    export type TEventFormData = z.infer<typeof eventSchema>;