ResourcesSchedule
Schedule wrapper component that combines resource day, week and month views
Source
LLM docs
Docs
Package
Usage
ResourcesSchedule is a wrapper component that combines ResourcesDayView, ResourcesWeekView, and ResourcesMonthView
into a single component with view switching. It manages the current view and date state, passing
common props to all three views automatically.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}Controlled state
You can control the current date and view level with date/onDateChange and view/onViewChange props.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule, ResourcesScheduleViewLevel } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [view, setView] = useState<ResourcesScheduleViewLevel>('day');
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
view={view}
onViewChange={setView}
resources={resources}
events={events}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}Drag and drop
Enable drag and drop across all views with withEventsDragAndDrop prop.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState<ScheduleEventData[]>(initialEvents);
return (
<ResourcesSchedule
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
)
);
}}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}Week view
Set defaultView="week" to open the schedule in week view by default.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
defaultView="week"
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}View-specific props
Use dayViewProps, weekViewProps, and monthViewProps to pass props specific to each view.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
dayViewProps={{
startTime: '08:00:00',
endTime: '18:00:00',
intervalMinutes: 30,
startScrollTime: '08:00:00',
}}
weekViewProps={{
startTime: '08:00:00',
endTime: '18:00:00',
startScrollDateTime: `${today} 08:00:00`,
}}
monthViewProps={{
maxEventsPerTimeSlot: 3,
startScrollDate: today,
}}
/>
);
}Event form
Click time slots, day cells, or existing events to open an event form. The form works across day, week, and month views with automatic view switching.
import dayjs from 'dayjs';
import { useState } from 'react';
import { Select } from '@mantine/core';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
import { EventData, EventForm } from './EventForm';
import { events as initialEvents, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
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 handleTimeSlotClick = ({
slotStart,
slotEnd,
resourceId,
}: {
slotStart: string;
slotEnd: string;
nativeEvent: React.MouseEvent<HTMLButtonElement>;
resourceId?: string | number;
}) => {
setSelectedResourceId(resourceId ? String(resourceId) : null);
setSelectedEventData({
title: '',
start: new Date(slotStart),
end: new Date(slotEnd),
color: 'blue',
});
setFormOpened(true);
};
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 (
<>
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withDragSlotSelect
onTimeSlotClick={handleTimeSlotClick}
onDayClick={handleDayClick}
onSlotDragEnd={handleSlotDragEnd}
onEventClick={handleEventClick}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
<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>
</>
);
}External drag and drop
You can drag external items onto the schedule using onExternalEventDrop prop. The callback
receives the resourceId of the target resource.
Drag to schedule
Quick Sync
30 min
Workshop
120 min
One-on-One
60 min
import { useRef, useState } from 'react';
import dayjs from 'dayjs';
import { Box, Grid, Text } from '@mantine/core';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
const resources = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
{ id: 'new-york', label: 'Meeting room: New York' },
{ id: 'london', label: 'Meeting room: London' },
];
const sidebarItems = [
{ title: 'Quick Sync', duration: 30, color: 'teal' },
{ title: 'Workshop', duration: 120, color: 'orange' },
{ title: 'One-on-One', duration: 60, color: 'violet' },
];
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState<ScheduleEventData[]>([]);
const nextId = useRef(1);
const handleExternalDrop = ({
dataTransfer,
dropDateTime,
resourceId,
}: {
dataTransfer: DataTransfer;
dropDateTime: string;
resourceId?: string | number;
}) => {
const raw = dataTransfer.getData('text/plain');
if (!raw) {
return;
}
const data = JSON.parse(raw);
const start = dayjs(dropDateTime);
const end = start.add(data.duration, 'minutes');
setEvents((prev) => [
...prev,
{
id: nextId.current++,
title: data.title,
start: start.format('YYYY-MM-DD HH:mm:ss'),
end: end.format('YYYY-MM-DD HH:mm:ss'),
color: data.color,
resourceId,
},
]);
};
const handleEventDrop = ({
eventId,
newStart,
newEnd,
resourceId,
}: {
eventId: string | number;
newStart: string;
newEnd: string;
event: ScheduleEventData;
resourceId?: string | number;
}) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId ? { ...event, start: newStart, end: newEnd, resourceId } : event
)
);
};
return (
<Grid>
<Grid.Col span={{ base: 12, sm: 3 }}>
<Text fw={500} mb="xs">Drag to schedule</Text>
{sidebarItems.map((item) => (
<Box
key={item.title}
draggable
onDragStart={(e) => {
e.dataTransfer.setData('text/plain', JSON.stringify(item));
e.dataTransfer.effectAllowed = 'copy';
}}
style={{
padding: '8px 12px',
marginBottom: 8,
borderRadius: 4,
cursor: 'grab',
backgroundColor: `var(--mantine-color-${item.color}-light)`,
color: `var(--mantine-color-${item.color}-light-color)`,
}}
>
<Text size="sm" fw={500}>{item.title}</Text>
<Text size="xs">{item.duration} min</Text>
</Box>
))}
</Grid.Col>
<Grid.Col span={{ base: 12, sm: 9 }}>
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
withEventsDragAndDrop
onEventDrop={handleEventDrop}
onExternalEventDrop={handleExternalDrop}
/>
</Grid.Col>
</Grid>
);
}Event resize
Enable event resizing with withEventResize prop. Events can be resized by dragging their left or
right edges in all three views. In week view, an event cannot be resized to span multiple days. In
month view, resizing snaps to whole days and keeps the time of day.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule, ScheduleEventData } from '@mantine/schedule';
import { events as initialEvents, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
const [events, setEvents] = useState<ScheduleEventData[]>(initialEvents);
const handleEventUpdate = ({ eventId, newStart, newEnd }: { eventId: string | number; newStart: string; newEnd: string; event: ScheduleEventData }) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId ? { ...event, start: newStart, end: newEnd } : event
)
);
};
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
withEventsDragAndDrop
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
withEventResize
onEventResize={handleEventUpdate}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}Drop validation
Use preventEventOverlap, canDropEvent and other validation props to reject a drop or resize
before it is committed, see drop validation.
In resource views, the event is checked against the events of the target resource.
The target resource is passed to canDropEvent and canDropExternalEvent as resourceId.
import { useState } from 'react';
import dayjs from 'dayjs';
import { Stack, Text } from '@mantine/core';
import { ResourcesDayView, ScheduleEventData, ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'room-a', label: 'Room A' },
{ id: 'room-b', label: 'Room B' },
{ id: 'room-c', label: 'Room C (large groups only)' },
];
const initialEvents: ScheduleEventData[] = [
{
id: 1,
title: 'Interview (2 people)',
start: `${today} 09:00:00`,
end: `${today} 10:00:00`,
color: 'blue',
resourceId: 'room-a',
payload: { attendees: 2 },
},
{
id: 2,
title: 'All-hands (40 people)',
start: `${today} 11:00:00`,
end: `${today} 12:00:00`,
color: 'grape',
resourceId: 'room-c',
payload: { attendees: 40 },
},
];
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [events, setEvents] = useState(initialEvents);
const [message, setMessage] = useState<string | null>(null);
return (
<Stack>
<ResourcesDayView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startTime="08:00:00"
endTime="18:00:00"
withEventsDragAndDrop
preventEventOverlap
canDropEvent={({ event, resourceId }) =>
resourceId !== 'room-c' || (event.payload?.attendees ?? 0) >= 20
}
onEventDrop={({ eventId, newStart, newEnd, resourceId }) => {
setMessage(null);
setEvents((prev) =>
prev.map((event) =>
event.id === eventId
? { ...event, start: newStart, end: newEnd, resourceId }
: event
)
);
}}
onEventPlacementRejected={(data) => {
if (data.action === 'external-drop') {
return;
}
setMessage(
data.reason === 'overlap'
? `${data.event.title} would overlap ${data.conflicts.length} event(s) in that room`
: 'Room C is reserved for meetings with 20 or more attendees'
);
}}
/>
{message && <Text c="red" size="sm">{message}</Text>}
</Stack>
);
}Static mode
Set mode="static" to disable all interactions across all three views.
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
mode="static"
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}