Migrating to Hapio Calendar
Move a calendar app to Hapio Calendar by changing an import, keep its options, and then adopt Hapio's own API at your pace.
Hapio Calendar is an independent calendar, not a fork of another. To make moving easy it has a
migration component, EventCalendar, that takes the React options, callbacks and payloads of the
calendar library you migrate from by their names and renders Hapio's HapioCalendar with them. There is one render path:
the migration component is the same calendar, with the same keyboard support, accessibility and
time-zone handling, configured from your existing options.
1. Add the migration component
npx shadcn@latest add @hapio/event-calendarThis adds components/event-calendar.tsx and the hapio-calendar block it renders, and installs
@hapio/calendar-compat. Load the Temporal polyfill as Installation shows.
2. Change the import
Replace your current calendar component with EventCalendar and keep your options. The plugins go:
every view is built in, so plugins is ignored. See it running in the
migration example.
'use client'
import type { DatesSetInfo, EventClickInfo, EventInput } from '@hapio/calendar-compat'
import { EventCalendar } from '@/components/event-calendar'
interface BookingProps {
readonly status: 'confirmed' | 'canceled'
}
export function BookingsCalendar({
events,
onRange,
onOpen,
}: {
events: readonly EventInput<BookingProps>[]
onRange: (startStr: string, endStr: string) => void
onOpen: (id: string) => void
}) {
const eventClick = (info: EventClickInfo<BookingProps>) => {
info.jsEvent.preventDefault() // stay on the page instead of following `url`
onOpen(info.event.id)
}
const datesSet = (info: DatesSetInfo) => onRange(info.startStr, info.endStr)
return (
<EventCalendar<BookingProps>
initialView="timeGridWeek"
availableViews={['timeGridWeek', 'timeGridDay', 'dayGridMonth', 'listWeek']}
timeZone="Europe/Stockholm"
firstDay={1}
slotMinTime="07:00:00"
nowIndicator
navLinks
events={events}
eventClick={eventClick}
datesSet={datesSet}
eventClass={(info) => (info.event.extendedProps.status === 'canceled' ? 'line-through' : '')}
height="max(36rem, calc(100svh - 17rem))"
/>
)
}- Types:
@hapio/calendar-compatexports your current calendar's option and payload types under their names (EventInput,EventClickInfo,DatesSetInfo,EventApi, …), generic in yourextendedPropstype, andEventCalendarPropsdeclares every option the component maps, so those keep compiling (satisfies EventCalendarProps). An option marked reported on Options is not declared: in a typed app it is a type error ("Object literal may only specify known properties"), which lists what step 4 removes (Differences). - Development warnings: a reported option that reaches the component (from JavaScript, or through an object not checked against the type) is ignored and listed once in the browser console, with a link to this guide.
- Uncontrolled, as before:
initialViewandinitialDateseed the calendar once; re-create it with akeyto reset it. A dropped or resized event stays where the user left it until youreventschange it (Differences). - The size:
heighttakes any CSS length or pixels and sizes the whole calendar, toolbar included.
3. Interactions are wrappers
Your current calendar's interaction plugin made calendars selectable, editable and droppable. In Hapio each is an opt-in item whose wrapper gives the calendars inside it the code, so a read-only calendar stays small:
| Your options | Wrap the calendar in |
|---|---|
selectable, select, dateClick in week and day views | <CalendarSelection> (calendar-selection) |
editable, eventDrop, eventResize, eventChange | <CalendarEditing> (calendar-editing, selection included) |
droppable, drop, eventReceive | <CalendarExternalDrops> (calendar-external-draggable) inside either |
Every editable event also gets its actions menu (move, change the time and resize without dragging), which your current calendar does not have. See Event actions.
4. Replace what has no counterpart
The options marked reported on Options have no counterpart, so they go in this step: a typed app's compiler lists them as type errors, and in JavaScript the development warnings do. Remove each one, or replace it as its row says:
- The toolbar DSL (
headerToolbar,footerToolbar,buttons): keep the built-in toolbar, or build your own withuseCalendarController()from@hapio/calendar-compat/react, which replaces it:
'use client'
import type { EventInput } from '@hapio/calendar-compat'
import { useCalendarController } from '@hapio/calendar-compat/react'
import { EventCalendar } from '@/components/event-calendar'
import { Button } from '@/components/ui/button'
export function CalendarWithHeader({ events }: { events: readonly EventInput[] }) {
// Replaces `headerToolbar`: your own header, driving the calendar.
const controller = useCalendarController()
const buttons = controller.getButtonState()
return (
<>
<header className="flex items-center gap-2">
<Button
type="button"
variant="outline"
onClick={controller.prev}
aria-disabled={buttons.prev.isDisabled}
>
{buttons.prev.hint}
</Button>
<h2>{controller.view?.title}</h2>
<Button
type="button"
variant="outline"
onClick={controller.next}
aria-disabled={buttons.next.isDisabled}
>
{buttons.next.hint}
</Button>
<Button type="button" onClick={controller.today} aria-disabled={buttons.today.isDisabled}>
{buttons.today.text}
</Button>
</header>
<EventCalendar controller={controller} initialView="dayGridMonth" events={events} />
</>
)
}- The imperative API (
getApi()mutations,addEvent,setProp,refetchEvents): Hapio is declarative. Change yourevents, and the calendar follows. Draggablefor external elements: useCalendarDraggableoruseExternalDraggable(Dragging in); itsdatais whateventReceivereads as the event.- Recurring events need the recurrence engine: see Recurrence.
Options maps every option and callback, and Differences lists what behaves differently and what is not supported.
5. Then, at your pace
EventCalendar is a thin file over HapioCalendar. When you want Hapio's own API (controlled
view and date, accessor mode, typed data, rules with reasons, recurrence scopes), replace it
with HapioCalendar one calendar at a time; both live side by side. Start with
Your first calendar.
Testing
@hapio/calendar-react/testing and @hapio/calendar-core/testing: conformance, the contract checker, rendering and clocks.
Your options in Hapio
Every option, callback, event field and payload of the calendar you migrate from, what the migration component does with it, and the Hapio option it becomes.