Calendarkit Pro V2
Customization

Sidebar Menus

Add custom navigation items to the sidebar that replace the main area with your own components.

Sidebar Menus

The sidebarMenus prop adds custom navigation entries to the sidebar. Clicking a menu item replaces the main calendar area with your component — ideal for tasks, reports, settings, or any non-calendar panel.

Interface

interface SidebarMenuItem {
  id:        string;          // unique key
  label:     string;          // display label
  icon?:     React.ReactNode; // optional icon (any React node)
  component: React.ReactNode; // rendered in main area when active
}

Basic example

const sidebarMenus: SidebarMenuItem[] = [
  {
    id: 'tasks',
    label: 'My Tasks',
    icon: <CheckSquare size={16} />,
    component: <TasksPanel />,
  },
  {
    id: 'reports',
    label: 'Reports',
    icon: <BarChart2 size={16} />,
    component: <ReportsPanel />,
  },
];

<ProScheduler sidebarMenus={sidebarMenus} ... />

Full example

'use client';

import { useState } from 'react';
import { ProScheduler } from 'calendarkit-prov2';
import type { CalendarEvent, SidebarMenuItem } from 'calendarkit-prov2';

// ── My Tasks panel ──────────────────────────────────────────────────────────
interface Task { id: string; text: string; done: boolean }

function TasksPanel() {
  const [tasks, setTasks] = useState<Task[]>([
    { id: '1', text: 'Review pull request',    done: false },
    { id: '2', text: 'Update documentation',   done: true  },
    { id: '3', text: 'Prepare sprint demo',    done: false },
    { id: '4', text: 'Fix calendar bug #42',   done: false },
  ]);

  const [newTask, setNewTask] = useState('');

  const add = () => {
    if (!newTask.trim()) return;
    setTasks((prev) => [...prev, { id: Date.now().toString(), text: newTask, done: false }]);
    setNewTask('');
  };

  return (
    <div className="p-6 space-y-4 max-w-lg">
      <h2 className="text-xl font-semibold">My Tasks</h2>

      {/* Add task */}
      <div className="flex gap-2">
        <input
          className="flex-1 px-3 py-2 text-sm border rounded-lg"
          placeholder="New task..."
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && add()}
        />
        <button
          onClick={add}
          className="px-4 py-2 text-sm bg-blue-500 text-white rounded-lg hover:bg-blue-600"
        >
          Add
        </button>
      </div>

      {/* Task list */}
      <ul className="space-y-2">
        {tasks.map((task) => (
          <li
            key={task.id}
            className="flex items-center gap-3 p-3 rounded-lg border"
          >
            <input
              type="checkbox"
              checked={task.done}
              onChange={() =>
                setTasks((prev) =>
                  prev.map((t) => (t.id === task.id ? { ...t, done: !t.done } : t))
                )
              }
              className="w-4 h-4"
            />
            <span className={task.done ? 'line-through text-muted-foreground text-sm' : 'text-sm'}>
              {task.text}
            </span>
          </li>
        ))}
      </ul>

      <p className="text-xs text-muted-foreground">
        {tasks.filter((t) => !t.done).length} tasks remaining
      </p>
    </div>
  );
}

// ── Reports panel ────────────────────────────────────────────────────────────
function ReportsPanel({ events }: { events: CalendarEvent[] }) {
  const total   = events.length;
  const allDay  = events.filter((e) => e.allDay).length;
  const byColor: Record<string, number> = {};
  events.forEach((e) => {
    const c = e.color ?? '#3b82f6';
    byColor[c] = (byColor[c] ?? 0) + 1;
  });

  return (
    <div className="p-6 space-y-6 max-w-lg">
      <h2 className="text-xl font-semibold">Reports</h2>

      <div className="grid grid-cols-2 gap-4">
        <div className="p-4 rounded-lg border">
          <p className="text-3xl font-bold">{total}</p>
          <p className="text-sm text-muted-foreground mt-1">Total Events</p>
        </div>
        <div className="p-4 rounded-lg border">
          <p className="text-3xl font-bold">{allDay}</p>
          <p className="text-sm text-muted-foreground mt-1">All-Day Events</p>
        </div>
      </div>

      <div>
        <p className="font-medium mb-3">Events by color</p>
        <div className="space-y-2">
          {Object.entries(byColor).map(([color, count]) => (
            <div key={color} className="flex items-center gap-3">
              <div className="w-4 h-4 rounded-sm" style={{ backgroundColor: color }} />
              <div className="flex-1 h-2 rounded-full bg-muted overflow-hidden">
                <div
                  className="h-full rounded-full"
                  style={{ width: `${(count / total) * 100}%`, backgroundColor: color }}
                />
              </div>
              <span className="text-sm font-medium w-4">{count}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── SVG Icons ────────────────────────────────────────────────────────────────
const CheckIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
       fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
    <path d="M9 11l3 3L22 4"/>
    <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
  </svg>
);

const ReportIcon = () => (
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
       fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
    <line x1="18" y1="20" x2="18" y2="10"/>
    <line x1="12" y1="20" x2="12" y2="4"/>
    <line x1="6"  y1="20" x2="6"  y2="14"/>
  </svg>
);

// ── Main component ────────────────────────────────────────────────────────────
export default function SidebarMenusExample() {
  const [events, setEvents] = useState<CalendarEvent[]>([]);

  const sidebarMenus: SidebarMenuItem[] = [
    {
      id: 'tasks',
      label: 'My Tasks',
      icon: <CheckIcon />,
      component: <TasksPanel />,
    },
    {
      id: 'reports',
      label: 'Reports',
      icon: <ReportIcon />,
      component: <ReportsPanel events={events} />,
    },
  ];

  return (
    <div className="h-[700px] border rounded-xl overflow-hidden">
      <ProScheduler
        view="week"
        events={events}
        date={new Date()}
        sidebarMenus={sidebarMenus}
        onEventCreate={(e) =>
          setEvents((prev) => [...prev, { ...e, id: crypto.randomUUID() } as CalendarEvent])
        }
        onEventUpdate={(updated) =>
          setEvents((prev) => prev.map((e) => (e.id === updated.id ? updated : e)))
        }
        onEventDelete={(id) =>
          setEvents((prev) => prev.filter((e) => e.id !== id))
        }
      />
    </div>
  );
}

On this page