A high-performance Vue 3 weekly scheduling calendar component designed for worker/resource management with Spanish localization, dark/light theme support, and comprehensive TypeScript integration.
- π Weekly Scheduling Focus: Exclusively designed for weekly calendar view and resource scheduling
- π₯ Worker/Resource Management: Advanced employee/resource scheduling with detailed metadata support
- πͺπΈ Spanish Localization: Built-in Spanish date formatting with memoized performance optimization
- π Dark/Light Theme: Automatic theme detection with smooth transitions and prop-based configuration
- π¨ Modern UI: Clean, professional interface with integrated styling (borders, shadows, rounded corners)
- π Timezone Support: Global timezone configuration with Atemporal library integration
- π Internationalization: Built-in i18n support for multiple locales
- π§ TypeScript: Full type safety with comprehensive type definitions
- π± Perfect Responsive Design: Mobile-first approach with horizontal scrolling, vertical navbar stacking, and optimized touch interactions
- π« Smart Event Stacking: Intelligent vertical stacking prevents visual overlaps
- π·οΈ Multi-Day Events: Seamless handling of events spanning multiple days with date ranges
- β° Time Display: Time ranges for single-day events, title + date range for multi-day events
- β‘ Performance Optimized: Memoization caches, debounced navigation, hardware acceleration
- π― Easy Integration: Works with Vue 3, Nuxt, Quasar, and other Vue-based frameworks
- ποΈ Self-Contained Styling: No wrapper divs needed - component includes integrated visual styling
# npm
npm install atempo-cal
# yarn
yarn add atempo-cal
# pnpm
pnpm add atempo-calAtempoCal requires the following peer dependencies:
npm install vue@^3.5.22 [email protected]<template>
<div>
<AtempoCal
:events="events"
:resources="resources"
:config="config"
:theme="theme"
@event-click="handleEventClick"
@event-create="handleEventCreate"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import AtempoCal from 'atempo-cal'
import type { CalendarEvent, CalendarResource, CalendarConfig } from 'atempo-cal'
import 'atempo-cal/dist/style.css'
// Sample events for worker scheduling
const events = ref<CalendarEvent[]>([
{
id: '1',
title: 'Morning Shift',
startTime: '2024-01-15T06:00:00Z',
endTime: '2024-01-15T14:00:00Z',
resourceId: 'emp-001',
color: '#3B82F6',
metadata: { type: 'shift', shiftType: 'morning' }
},
{
id: '2',
title: 'Annual Leave',
startTime: '2024-01-16T00:00:00Z',
endTime: '2024-01-18T23:59:59Z',
resourceId: 'emp-002',
color: '#10B981',
isAllDay: true,
metadata: { type: 'time-off', category: 'vacation' }
}
])
// Sample workers/resources
const resources = ref<CalendarResource[]>([
{
id: 'emp-001',
name: 'John Smith',
color: '#3B82F6',
metadata: {
department: 'Operations',
role: 'Shift Supervisor',
email: '[email protected]'
}
},
{
id: 'emp-002',
name: 'Sarah Johnson',
color: '#10B981',
metadata: {
department: 'Customer Service',
role: 'Team Lead',
email: '[email protected]'
}
}
])
// Configuration for weekly scheduling calendar
const config = ref<CalendarConfig>({
timezone: 'America/New_York',
locale: 'en-US',
showWeekends: true,
firstDayOfWeek: 1, // Monday
showEventTitles: true // Show event titles for single-day events
})
// Theme configuration
const theme = ref<'light' | 'dark' | 'auto'>('auto')
const handleEventClick = (event: CalendarEvent) => {
console.log('Event clicked:', event)
}
const handleEventCreate = (eventData: Partial<CalendarEvent>) => {
console.log('Create event:', eventData)
}
</script>AtempoCal provides flexible event display options to customize how events appear in the calendar:
Control whether single-day events display their titles or show only time information:
<template>
<div>
<!-- Toggle for event title display -->
<label>
<input v-model="showTitles" type="checkbox" />
Show Event Titles
</label>
<!-- Calendar with configurable event display -->
<AtempoCal
:events="events"
:resources="resources"
:config="{
showEventTitles: showTitles
}"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// Control event title display
const showTitles = ref(true)
</script>Display Modes:
showEventTitles: true(default): Single-day events show both title and timeshowEventTitles: false: Single-day events show only time information- Multi-day events: Always display title and date range regardless of this setting
Use Cases:
- Time-only mode: Perfect for dense schedules where you want to focus on time slots
- Full display mode: Ideal when event details are important for quick identification
- Responsive design: Switch modes based on screen size or user preference
| Prop | Type | Default | Description |
|---|---|---|---|
events |
CalendarEvent[] |
[] |
Array of calendar events |
resources |
CalendarResource[] |
[] |
Array of calendar resources |
config |
CalendarConfig |
{} |
Calendar configuration options |
selectedDate |
string |
undefined |
Currently selected date (ISO string) |
eventActions |
EventAction[] |
[] |
Custom actions for events |
customFields |
CustomField[] |
[] |
Custom field definitions |
loading |
boolean |
false |
Loading state |
readonly |
boolean |
false |
Read-only mode |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Theme configuration |
| Event | Payload | Description |
|---|---|---|
event-click |
CalendarEvent |
Fired when an event is clicked |
event-create |
Partial<CalendarEvent> |
Fired when creating a new event |
event-update |
CalendarEvent |
Fired when updating an event |
event-delete |
string |
Fired when deleting an event (event ID) |
date-change |
string |
Fired when the selected date changes |
view-change |
CalendarView |
Fired when the view changes |
slot-click |
SlotClickInfo |
Fired when clicking on a time slot |
interface CalendarEvent {
id: string
title: string
description?: string
startTime: string // ISO 8601 format
endTime: string // ISO 8601 format
resourceId?: string
color?: string
metadata?: Record<string, any>
isAllDay?: boolean
}
interface CalendarResource {
id: string
name: string
color?: string
avatar?: string
metadata?: Record<string, any>
}
interface CalendarConfig {
timezone?: string
locale?: string
theme?: 'light' | 'dark' | 'auto'
showWeekends?: boolean
firstDayOfWeek?: number // 0-6, 0 = Sunday
showEventTitles?: boolean // Show event titles for single-day events (default: true)
}// main.ts
import { createApp } from 'vue'
import AtempoCal from 'atempo-cal'
import 'atempo-cal/dist/style.css'
const app = createApp(App)
app.use(AtempoCal)
app.mount('#app')// plugins/atempo-cal.client.ts
import AtempoCal from 'atempo-cal'
import 'atempo-cal/dist/style.css'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(AtempoCal)
})AtempoCal is fully compatible with Quasar Framework. The component includes specific CSS fixes to ensure proper grid rendering and alignment:
Step 1: Create Boot File
// src/boot/atempo-cal.ts
import { boot } from 'quasar/wrappers'
import AtempoCal from 'atempo-cal'
import 'atempo-cal/dist/style.css'
export default boot(({ app }) => {
app.use(AtempoCal)
})Step 2: Register Boot File
Quasar Compatibility Features:
- β
Grid Lines: Strengthened border declarations with
!importantto override Quasar's CSS reset - β Proper Alignment: Fixed flexbox and grid layouts to work correctly within Quasar components
- β Framework Isolation: CSS box-sizing and display properties are protected from framework interference
- β Border Visibility: All calendar borders are explicitly defined to ensure visibility
- β Responsive Design: Maintains responsive behavior within Quasar's layout system
Step 3: Usage in Quasar Pages
<template>
<q-page class="q-pa-md">
<div class="row">
<div class="col-12">
<!-- AtempoCal works perfectly within Quasar layouts -->
<AtempoCal
:events="events"
:resources="resources"
:config="config"
/>
</div>
</div>
</q-page>
</template>Alternative: Local Component Import
If you prefer not to register globally, you can import the component locally:
<template>
<q-page class="q-pa-md">
<AtempoCal :events="events" :resources="resources" />
</q-page>
</template>
<script setup lang="ts">
import AtempoCal from 'atempo-cal'
import 'atempo-cal/dist/style.css'
// Your component logic here
</script><script setup lang="ts">
// Import only the component (smaller bundle)
import { AtempoCal } from 'atempo-cal'
import 'atempo-cal/dist/style.css'
</script>AtempoCal supports both light and dark themes with automatic detection. Configure themes using the theme prop:
<template>
<div>
<!-- Theme buttons -->
<div class="theme-controls">
<button @click="theme = 'light'">Light</button>
<button @click="theme = 'dark'">Dark</button>
<button @click="theme = 'auto'">Auto</button>
</div>
<!-- Calendar with theme prop -->
<AtempoCal
:events="events"
:resources="resources"
:config="config"
:theme="theme"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// Theme configuration - controlled by parent component
const theme = ref<'light' | 'dark' | 'auto'>('auto')
</script>'light': Force light theme'dark': Force dark theme'auto': Automatically detect system preference
The component uses CSS custom properties (CSS variables) for easy theme customization:
/* Override theme colors */
:root {
/* Light theme customization */
--atempo-bg-primary: #ffffff;
--atempo-bg-secondary: #f9fafb;
--atempo-text-primary: #111827;
--atempo-accent-primary: #3b82f6;
--atempo-success: #10b981;
--atempo-warning: #f59e0b;
--atempo-error: #ef4444;
}
.dark {
/* Dark theme customization */
--atempo-bg-primary: #111827;
--atempo-bg-secondary: #1f2937;
--atempo-text-primary: #f9fafb;
--atempo-accent-primary: #60a5fa;
}
/* Component-specific overrides */
.atempo-cal {
/* Custom calendar styling */
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}AtempoCal comes with integrated border, shadow, and rounded corner styling built directly into the component. This means you can use the component without any wrapper divs and it will maintain its professional appearance:
<!-- β
Works perfectly - no wrapper needed -->
<template>
<AtempoCal :events="events" :resources="resources" />
</template>
<!-- β No longer necessary -->
<template>
<div class="bg-white rounded-lg shadow border">
<AtempoCal :events="events" :resources="resources" />
</div>
</template>The component automatically includes:
- Border radius:
0.5remrounded corners - Box shadow: Subtle
0 1px 2px 0 rgba(0, 0, 0, 0.05)shadow - Border: Theme-aware border using
--atempo-border-primary - Overflow: Hidden to maintain clean rounded appearance
- Background: Theme-aware background using
--atempo-bg-primary
| Variable | Description | Light Default | Dark Default |
|---|---|---|---|
--atempo-bg-primary |
Primary background | #ffffff |
#111827 |
--atempo-bg-secondary |
Secondary background | #f9fafb |
#1f2937 |
--atempo-bg-tertiary |
Tertiary background | #f3f4f6 |
#374151 |
--atempo-text-primary |
Primary text color | #111827 |
#f9fafb |
--atempo-text-secondary |
Secondary text color | #6b7280 |
#d1d5db |
--atempo-border-primary |
Primary border color | #e5e7eb |
#374151 |
--atempo-accent-primary |
Primary accent color | #3b82f6 |
#60a5fa |
--atempo-success |
Success color | #10b981 |
#34d399 |
--atempo-warning |
Warning color | #f59e0b |
#fbbf24 |
--atempo-error |
Error color | #ef4444 |
#f87171 |
AtempoCal features built-in Spanish localization with optimized performance:
The component includes native Spanish date formatting with memoized caching for optimal performance:
<template>
<!-- Spanish localization is built-in and automatically used -->
<AtempoCal
:events="events"
:resources="resources"
:config="{
locale: 'es-ES',
timezone: 'Europe/Madrid'
}"
/>
</template>Spanish Features:
- Date Format: "22 sept" (day + Spanish month abbreviation)
- Day Names: "LUN", "MAR", "MIE", "JUE", "VIE", "SΓB", "DOM"
- Month Abbreviations: "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic"
- Performance Optimized: Memoized caching prevents redundant date calculations
The Spanish date helpers include several performance optimizations:
// Memoization caches for performance
const spanishMonthCache = new Map<string, string>()
const spanishDayCache = new Map<string, string>()
const spanishDateCache = new Map<string, string>()
// Cached Spanish month formatting
export const getSpanishMonth = (date: Atemporal): string => {
const cacheKey = `${date.year}-${date.month}`
if (spanishMonthCache.has(cacheKey)) {
return spanishMonthCache.get(cacheKey)!
}
// ... formatting logic with cache storage
}AtempoCal leverages the Atemporal library's built-in internationalization for other locales:
<template>
<AtempoCal
:config="{
locale: 'en-US', // English
timezone: 'America/New_York'
}"
/>
</template>Supported locales include all standard IETF language tags. The component automatically formats dates, times, and day names according to the specified locale.
AtempoCal includes comprehensive performance optimizations for smooth, responsive user experience:
Event Filtering Cache: Expensive event filtering operations are memoized to prevent redundant calculations:
// Cached event filtering for workers and dates
const eventFilterCache = new Map<string, CalendarEvent[]>()
const multiDayEventCache = new Map<string, CalendarEvent[]>()
// Cache invalidation on data changes
watch([() => props.events, () => props.resources], () => {
eventFilterCache.clear()
multiDayEventCache.clear()
}, { deep: true })Spanish Date Helpers: All Spanish date formatting functions use memoization:
getSpanishMonth()- Cached month abbreviationsgetSpanishDay()- Cached day namesformatSpanishDate()- Cached complete date strings
Worker Row Heights: Dynamic height calculations are cached to prevent layout thrashing:
const workerRowHeightCache = new Map<string, number>()
const getWorkerRowHeight = (workerId: string): number => {
if (workerRowHeightCache.has(workerId)) {
return workerRowHeightCache.get(workerId)!
}
// Calculate and cache height
}Navigation Functions: Navigation actions are debounced to prevent rapid-fire updates:
const debouncedNavigatePrevious = debounce(() => {
navigatePrevious()
}, 150)
const debouncedNavigateNext = debounce(() => {
navigateNext()
}, 150)v-memo Directives: Expensive list rendering uses v-memo for intelligent re-rendering:
<template>
<!-- Day headers with memoization -->
<div
v-for="date in weekDates"
:key="date.toString()"
v-memo="[date.toString(), isToday(date), isWeekend(date)]"
class="day-header"
>
<!-- content -->
</div>
<!-- Worker rows with memoization -->
<div
v-for="worker in displayWorkers"
:key="worker.id"
v-memo="[worker.id, worker.name, worker.color, getWorkerRowHeight(worker.id)]"
class="resource-row"
>
<!-- content -->
</div>
</template>Transform3D: Critical animations use hardware acceleration:
.atempo-cal {
transform: translate3d(0, 0, 0);
will-change: transform;
}
.resource-row {
transform: translate3d(0, 0, 0);
will-change: height;
}
.stacked-event {
transform: translate3d(0, 0, 0);
will-change: transform, opacity;
}Optimized Scrolling: Smooth scrolling with momentum and hardware acceleration:
.week-content {
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
transform: translate3d(0, 0, 0);
}Cache Invalidation: Automatic cache clearing prevents memory leaks:
// Clear caches when component unmounts
onUnmounted(() => {
eventFilterCache.clear()
multiDayEventCache.clear()
workerRowHeightCache.clear()
spanishMonthCache.clear()
spanishDayCache.clear()
spanishDateCache.clear()
})Shallow Reactivity: Non-critical data uses shallowRef for better performance:
const workerRowHeightCache = shallowRef(new Map<string, number>())
const eventFilterCache = shallowRef(new Map<string, CalendarEvent[]>())For development and debugging, you can monitor cache performance:
// Enable performance logging in development
if (import.meta.env.DEV) {
console.log('Event filter cache size:', eventFilterCache.size)
console.log('Spanish date cache size:', spanishDateCache.size)
console.log('Worker height cache size:', workerRowHeightCache.size)
}AtempoCal features a mobile-first responsive design that provides an optimal experience across all devices:
Horizontal Scrolling: On mobile devices, the calendar enables smooth horizontal scrolling to view all days of the week:
/* Automatic horizontal scrolling on mobile */
.weekly-view {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.week-header,
.week-content {
min-width: 850px; /* Ensures all 7 days are visible */
}Vertical Navbar Stacking: The navigation bar automatically reorganizes on mobile devices:
- Desktop: Horizontal layout (left | center | right)
- Mobile: Vertical stacking (center on top, left in middle, right at bottom)
<template>
<!-- Desktop: horizontal layout -->
<div class="nav-desktop-layout">
<div class="nav-left"><!-- Navigation controls --></div>
<div class="nav-center"><!-- Title display --></div>
<div class="nav-right"><!-- Date picker --></div>
</div>
<!-- Mobile: vertical stacked layout -->
<div class="nav-mobile-layout">
<div class="nav-mobile-center"><!-- Title on top --></div>
<div class="nav-mobile-left"><!-- Controls in middle --></div>
<div class="nav-mobile-right"><!-- Date picker at bottom --></div>
</div>
</template>- Touch-Optimized Scrolling: Smooth momentum scrolling with
-webkit-overflow-scrolling: touch - Grid Alignment: Perfect alignment between header and content sections across all screen sizes
- Flexible Widths: Dynamic column widths that adapt to content while maintaining readability
- Preserved Functionality: All features work seamlessly on mobile, including fixed height and event interactions
// Mobile breakpoint
@media (max-width: 768px) {
// Mobile-specific styles
}
// Desktop breakpoint
@media (min-width: 769px) {
// Desktop-specific styles
}The responsive behavior works automatically, but you can control scroll behavior:
<template>
<AtempoCal
:config="{
maxWorkersBeforeScroll: 5,
fixedHeight: '600px',
enableAutoScroll: true
}"
/>
</template>AtempoCal provides world-class resource management capabilities, perfect for scheduling workers, rooms, or equipment:
<template>
<AtempoCal
:events="events"
:resources="resources"
:config="config"
@event-click="handleEventClick"
@event-create="handleEventCreate"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CalendarEvent, CalendarResource } from 'atempo-cal'
// Define resources (workers, rooms, equipment, etc.)
const resources = ref<CalendarResource[]>([
{
id: 'worker-1',
name: 'John Smith',
color: '#3B82F6',
metadata: {
role: 'Senior Developer',
department: 'Engineering'
}
},
{
id: 'worker-2',
name: 'Sarah Johnson',
color: '#10B981',
metadata: {
role: 'UX Designer',
department: 'Design'
}
}
])
// Events with resource assignments
const events = ref<CalendarEvent[]>([
{
id: '1',
title: 'Project Planning',
startTime: '2024-01-15T09:00:00Z',
endTime: '2024-01-15T11:00:00Z',
resourceId: 'worker-1', // Assigned to John Smith
color: '#3B82F6'
},
{
id: '2',
title: 'Design Review',
startTime: '2024-01-15T10:00:00Z',
endTime: '2024-01-15T12:00:00Z',
resourceId: 'worker-2', // Assigned to Sarah Johnson
color: '#10B981'
}
])
</script>- No Visual Overlaps: Advanced column-based positioning ensures events never visually overlap
- Integrated Resource Display: Resource information is shown within event cards for clear identification
- Unified Daily View: Daily view shows all events in a single column with resource badges
- Intelligent Positioning: Events are automatically positioned to prevent conflicts while maximizing space usage
AtempoCal provides three slots for customizing the navigation bar:
<template>
<AtempoCal
:events="events"
:resources="resources"
>
<!-- Customize left section (navigation controls) -->
<template #navbar-left="{ navigatePrevious, navigateNext, navigateToday, loading }">
<div class="custom-nav-controls">
<button @click="navigatePrevious" :disabled="loading">
β Previous
</button>
<button @click="navigateNext" :disabled="loading">
Next β
</button>
<button @click="navigateToday" :disabled="loading">
Today
</button>
</div>
</template>
<!-- Customize center section (title display) -->
<template #navbar-center="{ displayTitle, currentDate }">
<div class="custom-title">
<h2>{{ displayTitle }}</h2>
<p>Custom Calendar - {{ currentDate.format('YYYY') }}</p>
</div>
</template>
<!-- Customize right section (date picker and controls) -->
<template #navbar-right="{ currentDate, onDateChange, showDatePicker, toggleDatePicker }">
<div class="custom-controls">
<input
type="date"
:value="currentDate.format('YYYY-MM-DD')"
@change="(e) => onDateChange(e.target.value)"
/>
<button v-if="showDatePicker" @click="toggleDatePicker">
π
</button>
</div>
</template>
</AtempoCal>
</template>navbar-left slot:
navigatePrevious()- Function to navigate to previous weeknavigateNext()- Function to navigate to next weeknavigateToday()- Function to navigate to current weekloading- Boolean indicating if navigation is in progresspreviousTitle- Tooltip text for previous buttonnextTitle- Tooltip text for next button
navbar-center slot:
displayTitle- Formatted title string (e.g., "January 15 - 21, 2024")currentDate- Current Atemporal date object
navbar-right slot:
currentDate- Current Atemporal date objectonDateChange(date: string)- Function to change the current dateshowDatePicker- Boolean indicating if date picker should be showntoggleDatePicker()- Function to toggle date picker modal
<template>
<AtempoCal
:events="events"
:event-actions="customActions"
/>
</template>
<script setup lang="ts">
const customActions = [
{
id: 'edit',
label: 'Edit Event',
icon: 'pencil',
handler: (event) => editEvent(event)
},
{
id: 'duplicate',
label: 'Duplicate',
icon: 'copy',
handler: (event) => duplicateEvent(event),
condition: (event) => event.metadata?.canDuplicate
}
]
</script><script setup lang="ts">
import { useCalendar, useEvents, useTheme } from 'atempo-cal'
// Calendar state management
const {
currentDate,
currentView,
navigateToDate,
navigateNext,
navigatePrevious
} = useCalendar()
// Event management
const {
events,
createEvent,
updateEvent,
deleteEvent
} = useEvents()
// Theme management
const {
currentTheme,
toggleTheme
} = useTheme()
</script>- Node.js 18+
- npm, yarn, or pnpm
# Clone the repository
git clone https://github.com/NaturalDevCR/AtempoCal.git
cd AtempoCal
# Install dependencies
npm install
# Start development server (demo)
npm run dev
# Build library
npm run build
# Run tests
npm run checkatempo-cal/
βββ src/ # Library source code
β βββ components/ # Vue components
β βββ composables/ # Vue composables
β βββ types/ # TypeScript definitions
β βββ utils/ # Utility functions
β βββ index.ts # Library entry point
βββ demo/ # Demo application
β βββ src/
β βββ index.html
βββ dist/ # Built library
βββ README.md
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
- Code Quality: Follow TypeScript best practices and maintain test coverage
- Atemporal Usage: Use the Atemporal library exclusively for all date/time operations
- Accessibility: Ensure components are accessible and keyboard navigable
- Performance: Optimize for performance and bundle size
- Documentation: Update documentation for any API changes
This project is licensed under the MIT License - see the LICENSE file for details.
- Atemporal - Modern date-time library with Temporal API
- Vue 3 - Progressive JavaScript framework
- Tailwind CSS - Utility-first CSS framework
- Heroicons - Beautiful hand-crafted SVG icons
- π Documentation
- π Issue Tracker
- π¬ Discussions
Made with β€οΈ by the AtempoCal Team