ResourcesMonthView
Month view with resource lanes for scheduling across resources
Source
LLM docs
Docs
Package
Usage
ResourcesMonthView displays a month grid where each day cell contains resource lanes. Events are shown within their resource's lane, making it easy to see resource utilization across the month.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Static mode
Set mode="static" to disable all interactions.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
mode="static"
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Drag and drop
Enable drag and drop with withEventsDragAndDrop prop. The onEventDrop callback receives
the resourceId of the target resource, allowing you to update the event's resource assignment.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState<ScheduleEventData[]>(initialEvents);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withEventsDragAndDrop
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((current) =>
current.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Event form
Click a day cell to create a new event, or click an existing event to edit it. Use withDragSlotSelect
and onSlotDragEnd to allow drag-to-create across day cells. The onSlotDragEnd callback includes
the resourceId of the resource where the drag started.
import dayjs from 'dayjs';
import { useState } from 'react';
import { Select } from '@mantine/core';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { EventData, EventForm } from './EventForm';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState<ScheduleEventData[]>(initialEvents);
const [formOpened, setFormOpened] = useState(false);
const [selectedEventData, setSelectedEventData] = useState<EventData | null>(null);
const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);
const handleDayClick = ({
date: clickedDate,
resourceId,
}: {
date: string;
nativeEvent: React.MouseEvent<HTMLButtonElement>;
resourceId?: string | number;
}) => {
setSelectedResourceId(resourceId ? String(resourceId) : null);
setSelectedEventData({
title: '',
start: dayjs(clickedDate).startOf('day').toDate(),
end: dayjs(clickedDate).endOf('day').toDate(),
color: 'blue',
});
setFormOpened(true);
};
const handleEventClick = (event: ScheduleEventData) => {
setSelectedResourceId(event.resourceId ? String(event.resourceId) : null);
setSelectedEventData({
id: event.id,
title: event.title,
start: new Date(event.start),
end: new Date(event.end),
color: event.color || 'blue',
});
setFormOpened(true);
};
const handleSlotDragEnd = ({
rangeStart,
rangeEnd,
resourceId,
}: {
rangeStart: string;
rangeEnd: string;
resourceId?: string | number;
}) => {
setSelectedResourceId(resourceId ? String(resourceId) : null);
setSelectedEventData({
title: '',
start: new Date(rangeStart),
end: new Date(rangeEnd),
color: 'blue',
});
setFormOpened(true);
};
const handleSubmit = (values: EventData) => {
if (values.id) {
setEvents((prev) =>
prev.map((event) =>
event.id === values.id
? {
...event,
title: values.title,
start: dayjs(values.start).toISOString(),
end: dayjs(values.end).toISOString(),
color: values.color || 'blue',
resourceId: selectedResourceId || event.resourceId,
}
: event
)
);
} else {
setEvents((prev) => [
...prev,
{
id: Math.random().toString(36).substring(2, 11),
title: values.title,
start: dayjs(values.start).toISOString(),
end: dayjs(values.end).toISOString(),
color: values.color || 'blue',
resourceId: selectedResourceId || undefined,
},
]);
}
};
const handleDeleteEvent = () => {
if (selectedEventData?.id) {
setEvents((prev) => prev.filter((e) => e.id !== selectedEventData.id));
}
};
return (
<>
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withDragSlotSelect
onDayClick={handleDayClick}
onSlotDragEnd={handleSlotDragEnd}
onEventClick={handleEventClick}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
<EventForm
opened={formOpened}
onClose={() => setFormOpened(false)}
onExitTransitionEnd={() => setSelectedEventData(null)}
values={selectedEventData}
onSubmit={handleSubmit}
onDelete={selectedEventData?.id ? handleDeleteEvent : undefined}
>
<Select
label="Resource"
placeholder="Select resource"
radius="md"
data={resources.map((r) => ({ value: String(r.id), label: r.label }))}
value={selectedResourceId}
onChange={setSelectedResourceId}
/>
</EventForm>
</>
);
}Custom event rendering
Use renderEvent prop to customize how events are rendered. The example below uses HoverCard
to display event details on hover.
import dayjs from 'dayjs';
import { useState } from 'react';
import { HoverCard, UnstyledButton } from '@mantine/core';
import { ResourcesMonthView, ScheduleEventData } from '@mantine/schedule';
import { EventDetails } from './EventDetails';
import { events as initialEvents, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState<ScheduleEventData[]>(initialEvents);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withEventsDragAndDrop
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((current) =>
current.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
renderEvent={(event, props) => (
<HoverCard width={280} position="right" closeDelay={0} transitionProps={{ duration: 0 }}>
<HoverCard.Target>
<UnstyledButton {...props} />
</HoverCard.Target>
<HoverCard.Dropdown>
<EventDetails event={event} resources={resources} />
</HoverCard.Dropdown>
</HoverCard>
)}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Custom resource label
Use renderResourceLabel prop to customize how resource labels are rendered in the row headers.
Meeting room: Tokyo
Office
Meeting room: Paris
Office
Meeting room: New York
Office
import dayjs from 'dayjs';
import { useState } from 'react';
import { Stack, Text } from '@mantine/core';
import { ResourcesMonthView, ScheduleResourceData } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
renderResourceLabel={(resource: ScheduleResourceData) => (
<Stack gap={0} align="flex-start">
<Text fw={600} size="sm">
{resource.label}
</Text>
<Text size="xs" c="dimmed">
Office
</Text>
</Stack>
)}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Resource groups
Use groups prop to group resources under labeled headers. The group labels are displayed
as a column to the left of resource labels, spanning vertically across their resources.
Use renderGroupLabel to customize group label rendering and groupLabelWidth to control
the group column width.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleResourceGroup } from '@mantine/schedule';
import { events } from './data';
const resources = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
{ id: 'new-york', label: 'Meeting room: New York' },
{ id: 'overflow', label: 'Overflow room' },
];
const groups: ScheduleResourceGroup[] = [
{ label: 'Floor 1', resourceIds: ['tokyo', 'paris'] },
{ label: 'Floor 2', resourceIds: ['new-york'] },
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
groups={groups}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Localization
Set locale prop to change the language. Use labels prop to override built-in labels.
import 'dayjs/locale/es';
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
locale="es"
labels={{
day: 'DÃa',
week: 'Semana',
month: 'Mes',
year: 'Año',
today: 'Hoy',
previous: 'Anterior',
next: 'Siguiente',
more: 'Más',
resources: 'Recursos',
moreLabel: (hiddenEventsCount) => `+${hiddenEventsCount} más`,
}}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Recurring events
ResourcesMonthView automatically expands recurring events for the visible month. See Recurring events guide for full documentation.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events = [
{
id: 'daily-sync-series',
title: 'Daily sync (series)',
start: `${dayjs(today).subtract(2, 'day').format('YYYY-MM-DD')} 09:00:00`,
end: `${dayjs(today).subtract(2, 'day').format('YYYY-MM-DD')} 09:30:00`,
color: 'blue',
resourceId: 'tokyo',
recurrence: {
rrule: 'FREQ=DAILY;COUNT=10',
exdate: [`${today} 09:00:00`],
},
},
{
id: 'daily-sync-override',
title: 'Daily sync (moved today)',
start: `${today} 14:00:00`,
end: `${today} 14:30:00`,
color: 'grape',
resourceId: 'tokyo',
recurringEventId: 'daily-sync-series',
recurrenceId: `${today} 09:00:00`,
},
{
id: 'one-off',
title: 'One-off planning',
start: `${today} 11:00:00`,
end: `${today} 12:00:00`,
color: 'green',
resourceId: 'paris',
},
];
function Demo() {
const [date, setDate] = useState(today);
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startScrollDate={today}
/>
);
}Max events per cell
Use maxEventsPerTimeSlot prop to limit the number of events visible in each cell.
When there are more events than the limit, a "+more" indicator is shown.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events: ScheduleEventData[] = [
{ id: 1, title: 'Team Meeting', start: `${today} 09:00:00`, end: `${today} 10:00:00`, color: 'blue', resourceId: 'tokyo' },
{ id: 2, title: 'Code Review', start: `${today} 11:00:00`, end: `${today} 12:00:00`, color: 'green', resourceId: 'tokyo' },
{ id: 3, title: 'Design Review', start: `${today} 13:00:00`, end: `${today} 14:00:00`, color: 'violet', resourceId: 'tokyo' },
{ id: 4, title: 'Sprint Planning', start: `${today} 15:00:00`, end: `${today} 16:00:00`, color: 'orange', resourceId: 'tokyo' },
{ id: 5, title: 'Retrospective', start: `${today} 16:30:00`, end: `${today} 17:30:00`, color: 'cyan', resourceId: 'tokyo' },
{ id: 6, title: 'Client Call', start: `${today} 09:00:00`, end: `${today} 10:00:00`, color: 'pink', resourceId: 'paris' },
{ id: 7, title: 'Workshop', start: `${today} 11:00:00`, end: `${today} 12:00:00`, color: 'grape', resourceId: 'paris' },
{ id: 8, title: 'Standup', start: `${today} 14:00:00`, end: `${today} 15:00:00`, color: 'red', resourceId: 'paris' },
{ id: 9, title: 'Demo', start: `${today} 16:00:00`, end: `${today} 17:00:00`, color: 'teal', resourceId: 'paris' },
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
maxEventsPerTimeSlot={3}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Day width and row height
Use dayWidth and rowHeight props to customize the dimensions of day columns and resource rows.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
dayWidth={60}
rowHeight={64}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Start scroll date
Use startScrollDate prop to scroll to a specific date on initial render. This is useful
when the month has many days and you want the view to start at a specific date, for example today.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Without weekend days
Set withWeekendDays={false} to hide weekend day columns from the month grid.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withWeekendDays={false}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}More events indicator
When a day cell has more events than maxEventsPerTimeSlot, a "+X more" indicator is shown.
Click the indicator to see all events for that day.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events: ScheduleEventData[] = [
{ id: 1, title: 'Team Standup', start: `${today} 08:00:00`, end: `${today} 08:30:00`, color: 'blue', resourceId: 'tokyo' },
{ id: 2, title: 'Sprint Planning', start: `${today} 09:00:00`, end: `${today} 10:00:00`, color: 'green', resourceId: 'tokyo' },
{ id: 3, title: 'Design Review', start: `${today} 10:30:00`, end: `${today} 11:30:00`, color: 'violet', resourceId: 'tokyo' },
{ id: 4, title: 'Code Review', start: `${today} 13:00:00`, end: `${today} 14:00:00`, color: 'orange', resourceId: 'tokyo' },
{ id: 5, title: 'Retrospective', start: `${today} 14:30:00`, end: `${today} 15:30:00`, color: 'cyan', resourceId: 'tokyo' },
{ id: 6, title: 'Client Call', start: `${today} 16:00:00`, end: `${today} 17:00:00`, color: 'pink', resourceId: 'tokyo' },
{ id: 7, title: 'Workshop', start: `${today} 10:00:00`, end: `${today} 11:00:00`, color: 'grape', resourceId: 'paris' },
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
maxEventsPerTimeSlot={2}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}More events props
Use moreEventsProps to customize the more events dropdown, for example to use a modal
instead of a popover.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
];
const events: ScheduleEventData[] = [
{ id: 1, title: 'Team Standup', start: `${today} 08:00:00`, end: `${today} 08:30:00`, color: 'blue', resourceId: 'tokyo' },
{ id: 2, title: 'Sprint Planning', start: `${today} 09:00:00`, end: `${today} 10:00:00`, color: 'green', resourceId: 'tokyo' },
{ id: 3, title: 'Design Review', start: `${today} 10:30:00`, end: `${today} 11:30:00`, color: 'violet', resourceId: 'tokyo' },
{ id: 4, title: 'Code Review', start: `${today} 13:00:00`, end: `${today} 14:00:00`, color: 'orange', resourceId: 'tokyo' },
{ id: 5, title: 'Retrospective', start: `${today} 14:30:00`, end: `${today} 15:30:00`, color: 'cyan', resourceId: 'tokyo' },
{ id: 6, title: 'Client Call', start: `${today} 16:00:00`, end: `${today} 17:00:00`, color: 'pink', resourceId: 'tokyo' },
{ id: 7, title: 'Workshop', start: `${today} 10:00:00`, end: `${today} 11:00:00`, color: 'grape', resourceId: 'paris' },
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
maxEventsPerTimeSlot={2}
startScrollDate={dayjs().format('YYYY-MM-DD')}
moreEventsProps={{ dropdownType: 'modal', modalTitle: 'All Events' }}
/>
);
}Custom header
Set withHeader={false} to hide the default header and use ScheduleHeader to build
a custom header with navigation controls.
import dayjs from 'dayjs';
import { useState } from 'react';
import { DateStringValue, ResourcesMonthView, ScheduleHeader } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState<DateStringValue>(
dayjs().format('YYYY-MM-DD')
);
return (
<div>
<ScheduleHeader>
<ScheduleHeader.Previous
onClick={() =>
setDate(
dayjs(date)
.subtract(1, 'month')
.startOf('month')
.format('YYYY-MM-DD') as DateStringValue
)
}
/>
<ScheduleHeader.MonthYearSelect
yearValue={dayjs(date).year()}
monthValue={dayjs(date).month()}
onYearChange={(year) =>
setDate(
dayjs(date)
.year(year)
.startOf('month')
.format('YYYY-MM-DD') as DateStringValue
)
}
onMonthChange={(month) =>
setDate(
dayjs(date)
.month(month)
.startOf('month')
.format('YYYY-MM-DD') as DateStringValue
)
}
/>
<ScheduleHeader.Next
onClick={() =>
setDate(
dayjs(date)
.add(1, 'month')
.startOf('month')
.format('YYYY-MM-DD') as DateStringValue
)
}
/>
<ScheduleHeader.Today
onClick={() =>
setDate(
dayjs().format('YYYY-MM-DD') as DateStringValue
)
}
/>
</ScheduleHeader>
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withHeader={false}
/>
</div>
);
}Radius
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
radius="md"
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}Scroll area props
Use scrollAreaProps to customize the scroll area behavior.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
scrollAreaProps={{
scrollbarSize: 10,
offsetScrollbars: true,
type: 'always',
scrollbars: 'x',
}}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}