@lemonadejs/schedule
v5.0.5
Published
A reactive schedule and calendar component for managing events and appointments with drag-and-drop, time ranges, and weekly views
Downloads
313
Maintainers
Readme
LemonadeJS Schedule
A reactive schedule and calendar component for managing events and appointments with drag-and-drop support, time range validation, and multiple view modes.
Compatible with Vanilla JavaScript, LemonadeJS, React, Vue, and Angular.
Features
- 📅 Multiple View Modes: Day, week, and weekdays views
- 🔄 Drag & Drop: Move and resize events with mouse
- ⏰ Time Validation: Define valid working hours and read-only time ranges
- 📊 Weekly Schedule: Create recurring weekly schedules without specific dates
- ⌨️ Keyboard Support: Arrow keys, delete, copy/paste (Ctrl+C/V), undo/redo (Ctrl+Z/Y)
- 🎨 Customizable: Event colors, grid intervals, and overlap control
- ♿ Accessibility: Full keyboard navigation support
- 📱 Responsive: Adapts to different screen sizes
Installation
NPM
npm install @lemonadejs/scheduleCDN
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/schedule/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/schedule/dist/style.min.css" />Basic Usage
Simple Schedule
import Schedule from '@lemonadejs/schedule';
const root = document.getElementById('schedule');
const schedule = Schedule(root, {
type: 'week',
value: '2025-01-15',
data: [
{
title: 'Team Meeting',
start: '09:00',
end: '10:00',
date: '2025-01-15',
color: '#3f51b5'
},
{
title: 'Lunch Break',
start: '12:00',
end: '13:00',
date: '2025-01-15',
color: '#4caf50'
}
]
});Weekly Recurring Schedule
Perfect for class schedules, gym timetables, or any recurring weekly events:
const schedule = Schedule(root, {
type: 'weekdays',
weekly: true,
grid: 30, // 30-minute intervals
data: [
{
title: 'Math Class',
start: '09:00',
end: '10:30',
weekday: 1, // Monday (0=Sunday, 1=Monday, ..., 6=Saturday)
color: '#2196f3'
},
{
title: 'Physics Lab',
start: '14:00',
end: '16:00',
weekday: 3, // Wednesday
color: '#ff9800'
}
]
});School Schedule with Working Hours
const schedule = Schedule(root, {
type: 'weekdays',
weekly: true,
grid: 30,
validRange: ['08:00', '18:00'], // Only show 8 AM to 6 PM
readOnlyRange: [
['08:00', '09:00'], // Before school
['12:00', '13:00'], // Lunch break
['17:00', '18:00'] // After school
],
overlap: false, // Prevent overlapping classes
data: []
});Framework Integration
React
The Schedule component includes a React wrapper for seamless integration.
Installation
npm install @lemonadejs/schedule reactBasic React Example
import React, { useRef } from 'react';
import Schedule from '@lemonadejs/schedule/dist/react';
import '@lemonadejs/schedule/dist/style.css';
export default function App() {
const scheduleRef = useRef();
const events = [
{
title: 'Team Meeting',
start: '09:00',
end: '10:00',
date: '2025-01-15',
color: '#3f51b5'
},
{
title: 'Lunch',
start: '12:00',
end: '13:00',
date: '2025-01-15',
color: '#4caf50'
}
];
return (
<div style={{ width: '100%', height: '600px' }}>
<Schedule
ref={scheduleRef}
type="week"
value="2025-01-15"
data={events}
grid={30}
oncreate={(instance, events) => {
console.log('Event created:', events);
}}
/>
</div>
);
}React with State Management
import React, { useRef, useState } from 'react';
import Schedule from '@lemonadejs/schedule/dist/react';
import '@lemonadejs/schedule/dist/style.css';
export default function ScheduleComponent() {
const scheduleRef = useRef();
const [events, setEvents] = useState([
{
title: 'Morning Standup',
start: '09:00',
end: '09:30',
date: '2025-01-15',
color: '#2196f3'
}
]);
const handleCreateEvent = (instance, newEvents) => {
console.log('New event created:', newEvents);
// Update your state or backend
setEvents([...instance.data]);
};
const handleDeleteEvent = (instance, event) => {
console.log('Event deleted:', event);
setEvents([...instance.data]);
};
const addNewEvent = () => {
if (scheduleRef.current) {
scheduleRef.current.addEvents({
title: 'New Event',
start: '14:00',
end: '15:00',
date: '2025-01-15',
color: '#ff9800'
});
}
};
return (
<div>
<button onClick={addNewEvent}>Add Event</button>
<div style={{ width: '100%', height: '600px' }}>
<Schedule
ref={scheduleRef}
type="week"
value="2025-01-15"
data={events}
oncreate={handleCreateEvent}
ondelete={handleDeleteEvent}
/>
</div>
</div>
);
}React Weekly Schedule (Gym/School)
import React, { useRef } from 'react';
import Schedule from '@lemonadejs/schedule/dist/react';
import '@lemonadejs/schedule/dist/style.css';
export default function GymSchedule() {
const scheduleRef = useRef();
const classes = [
{
title: 'Yoga',
start: '07:00',
end: '08:00',
weekday: 1, // Monday
color: '#9c27b0'
},
{
title: 'Spinning',
start: '18:00',
end: '19:00',
weekday: 2, // Tuesday
color: '#f44336'
},
{
title: 'Yoga',
start: '07:00',
end: '08:00',
weekday: 3, // Wednesday
color: '#9c27b0'
},
{
title: 'CrossFit',
start: '18:00',
end: '19:30',
weekday: 4, // Thursday
color: '#ff5722'
}
];
return (
<div style={{ width: '100%', height: '600px' }}>
<Schedule
ref={scheduleRef}
type="weekdays"
weekly={true}
grid={30}
data={classes}
validRange={['06:00', '21:00']}
overlap={false}
/>
</div>
);
}Vue
The Schedule component also includes a Vue 3 wrapper.
Installation
npm install @lemonadejs/schedule vueBasic Vue Example
<template>
<div style="width: 100%; height: 600px">
<Schedule
type="week"
:value="currentDate"
:data="events"
:grid="30"
@oncreate="handleCreate"
/>
</div>
</template>
<script>
import { ref } from 'vue';
import Schedule from '@lemonadejs/schedule/dist/vue';
import '@lemonadejs/schedule/dist/style.css';
export default {
name: 'App',
components: {
Schedule
},
setup() {
const currentDate = ref('2025-01-15');
const events = ref([
{
title: 'Team Meeting',
start: '09:00',
end: '10:00',
date: '2025-01-15',
color: '#3f51b5'
},
{
title: 'Lunch',
start: '12:00',
end: '13:00',
date: '2025-01-15',
color: '#4caf50'
}
]);
const handleCreate = (instance, newEvents) => {
console.log('Event created:', newEvents);
};
return {
currentDate,
events,
handleCreate
};
}
};
</script>Vue with Reactive State
<template>
<div>
<button @click="addEvent">Add Event</button>
<button @click="goToNextWeek">Next Week</button>
<button @click="goToPrevWeek">Previous Week</button>
<div style="width: 100%; height: 600px">
<Schedule
ref="scheduleRef"
type="week"
:value="currentDate"
:data="events"
:validRange="['08:00', '18:00']"
@oncreate="handleCreate"
@ondelete="handleDelete"
/>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import Schedule from '@lemonadejs/schedule/dist/vue';
import '@lemonadejs/schedule/dist/style.css';
export default {
name: 'ScheduleComponent',
components: {
Schedule
},
setup() {
const scheduleRef = ref(null);
const currentDate = ref('2025-01-15');
const events = ref([
{
title: 'Morning Standup',
start: '09:00',
end: '09:30',
date: '2025-01-15',
color: '#2196f3'
}
]);
const handleCreate = (instance, newEvents) => {
console.log('Event created:', newEvents);
events.value = [...instance.data];
};
const handleDelete = (instance, event) => {
console.log('Event deleted:', event);
events.value = [...instance.data];
};
const addEvent = () => {
scheduleRef.value?.current?.addEvents({
title: 'New Event',
start: '14:00',
end: '15:00',
date: currentDate.value,
color: '#ff9800'
});
};
const goToNextWeek = () => {
scheduleRef.value?.current?.next();
};
const goToPrevWeek = () => {
scheduleRef.value?.current?.prev();
};
return {
scheduleRef,
currentDate,
events,
handleCreate,
handleDelete,
addEvent,
goToNextWeek,
goToPrevWeek
};
}
};
</script>Vue Weekly Schedule
<template>
<div style="width: 100%; height: 600px">
<Schedule
type="weekdays"
:weekly="true"
:grid="30"
:data="classes"
:validRange="['06:00', '21:00']"
:overlap="false"
/>
</div>
</template>
<script>
import { ref } from 'vue';
import Schedule from '@lemonadejs/schedule/dist/vue';
import '@lemonadejs/schedule/dist/style.css';
export default {
name: 'GymSchedule',
components: {
Schedule
},
setup() {
const classes = ref([
{
title: 'Yoga',
start: '07:00',
end: '08:00',
weekday: 1, // Monday
color: '#9c27b0'
},
{
title: 'Spinning',
start: '18:00',
end: '19:00',
weekday: 2, // Tuesday
color: '#f44336'
},
{
title: 'Yoga',
start: '07:00',
end: '08:00',
weekday: 3, // Wednesday
color: '#9c27b0'
},
{
title: 'CrossFit',
start: '18:00',
end: '19:30',
weekday: 4, // Thursday
color: '#ff5722'
}
]);
return {
classes
};
}
};
</script>API Reference
Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| type | 'week' \| 'day' \| 'weekdays' | 'week' | View type for the schedule |
| weekly | boolean | false | Enable weekly mode (recurring events without dates) |
| value | string | Current date | Initial date in ISO format (YYYY-MM-DD) |
| data | Event[] | [] | Array of events |
| grid | number | 15 | Time grid interval in minutes |
| overlap | boolean | false | Allow overlapping events |
| validRange | string[] | [] | Valid time range ['HH:MM', 'HH:MM'] (hides other hours) |
| readOnlyRange | string[] \| string[][] | [] | Read-only time ranges (single or multiple) |
| onbeforecreate | function | - | Callback before creating events |
| oncreate | function | - | Callback after creating events |
| onchangeevent | function | - | Callback when event properties change |
| ondelete | function | - | Callback when event is deleted |
| onerror | function | - | Callback for error handling |
Event Object
{
title: string; // Event title
start: string; // Start time in HH:MM format
end: string; // End time in HH:MM format
date?: string; // Date in ISO format (YYYY-MM-DD) - for normal mode
weekday?: number; // Day of week (0-6, Sunday is 0) - for weekly mode
color?: string; // Hex color code
description?: string; // Event description
location?: string; // Event location
guests?: string; // Guest list
readonly?: boolean; // Make event read-only
guid?: string; // Unique identifier (auto-generated)
}Methods
// Add events
schedule.addEvents(event | events[]);
// Update event
schedule.updateEvent(guid, { title: 'New Title', start: '10:00' });
// Delete events
schedule.deleteEvents(guid | guid[] | event | event[]);
// Navigation
schedule.next(); // Next week/day
schedule.prev(); // Previous week/day
// History
schedule.undo(); // Undo last change
schedule.redo(); // Redo last change
// Time ranges
schedule.setRange(['08:00', '18:00']);
schedule.setReadOnly([['00:00', '08:00'], ['18:00', '23:59']]);
// Data management
schedule.getData(); // Get all events
schedule.setData(events); // Set events data
schedule.getEvent(guid); // Get event DOM element
// Render
schedule.render(); // Re-render scheduleExamples
Example 1: Office Hours Schedule
const officeSchedule = Schedule(root, {
type: 'week',
value: '2025-01-20',
grid: 30,
validRange: ['08:00', '18:00'],
readOnlyRange: ['12:00', '13:00'], // Lunch break
oncreate: (instance, events) => {
console.log('New appointment created:', events);
},
onerror: (instance, message) => {
alert(message);
}
});Example 2: Gym Class Schedule
const gymSchedule = Schedule(root, {
type: 'weekdays',
weekly: true,
grid: 60,
data: [
{
title: 'Yoga',
start: '07:00',
end: '08:00',
weekday: 1, // Monday
color: '#9c27b0'
},
{
title: 'Spinning',
start: '18:00',
end: '19:00',
weekday: 2, // Tuesday
color: '#f44336'
},
{
title: 'Yoga',
start: '07:00',
end: '08:00',
weekday: 3, // Wednesday
color: '#9c27b0'
}
]
});Example 3: Interactive Event Creation
const schedule = Schedule(root, {
type: 'week',
value: '2025-01-20',
onbeforecreate: (instance, events) => {
// Validate event before creation
const event = Array.isArray(events) ? events[0] : events;
if (event.start < '09:00') {
alert('Cannot create events before 9 AM');
return false; // Cancel creation
}
return true; // Allow creation
},
oncreate: (instance, events) => {
// Event was created successfully
console.log('Event created:', events);
}
});Example 4: Multiple Read-Only Time Ranges
const schedule = Schedule(root, {
type: 'week',
value: '2025-01-20',
validRange: ['00:00', '23:59'], // Show all hours
readOnlyRange: [
['00:00', '08:00'], // Early morning
['12:00', '13:00'], // Lunch
['18:00', '23:59'] // Evening
]
});Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| Ctrl+Z | Undo last change |
| Ctrl+Y | Redo last change |
| Ctrl+C | Copy selected events |
| Ctrl+V | Paste copied events |
| Delete | Delete selected events |
| Arrow Keys | Navigate between events |
Styling
The schedule uses CSS custom properties for easy customization:
.lm-schedule {
--lm-schedule-border: #e0e0e0;
--lm-schedule-bg: #ffffff;
--lm-schedule-text: #000000;
}
/* Customize event colors */
.lm-schedule-item {
border-radius: 4px;
padding: 4px 8px;
}
/* Disabled time slots */
.lm-schedule-disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
License
This component is part of the LemonadeJS plugins library.
Links
Contributing
We welcome contributions! Please see the main repository for contribution guidelines.
