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