ape-calendar
v0.9.0
Published
FullCalendar + react-day-picker based React calendar suite (macro + mini calendar, popovers, hooks). Self-contained with own Radix primitives.
Maintainers
Readme
ape-calendar
FullCalendar + react-day-picker based React calendar suite. Self-contained: ships its own Radix Popover primitive, no external UI library required.
Status: scaffold. Components are migrated package-by-package from
cms-portal. See the monorepo README for progress.
Install
npm install ape-calendar
# peer deps
npm install react react-domImport the stylesheet once at the app root:
import 'ape-calendar/style.css'Usage (planned)
import { MacroCalendar, MiniMonthCalendar, useCalendarState } from 'ape-calendar'
const { state, actions } = useCalendarState()
<MacroCalendar
events={events}
activeView={state.activeView}
selectedDate={state.selectedDate}
locale="es-CO"
onEditEvent={openForm}
onDeleteEvent={confirmDelete}
onEventTimeChange={({ event, startAt, endAt, revert }) =>
updateApi(event.id, { startAt, endAt }).catch(revert)
}
/>Toolbar & view controller
The calendar stays fully controlled (the consumer owns activeView / selectedDate). useCalendarController encapsulates the standard navigation behavior so every portal behaves the same — spread its handlers onto CalendarToolbar and MacroCalendar:
- Hoy → today in day view (re-pressing it falls back to week).
- Picking a day from month or list → that day's week.
- prev/next step by the current view;
periodLabelis localized for you.
import {
CalendarToolbar,
MacroCalendar,
StatusFilterSelect,
EventTypeChip,
Select,
useCalendarController,
} from 'ape-calendar'
const cal = useCalendarController({ initialView: 'week', firstDay: 1, locale: 'es-CO' })
<CalendarToolbar
activeView={cal.activeView}
onChangeView={cal.onChangeView}
onNavigate={cal.onNavigate}
todayActive={cal.todayActive}
periodLabel={cal.periodLabel}
views={['month', 'week', 'list']} // month ▦ · calendar(day/week) ▤ · list ☰
filtersSlot={
<>
<StatusFilterSelect placeholder="Estado del evento" options={statusOptions} selected={selectedStatuses} onToggle={toggleStatus} />
{selectedTypes.map(t => <EventTypeChip key={t} label={labels[t]} onRemove={() => clearType(t)} />)}
<Select placeholder="Regional" options={regionalOptions} value={regional} onChange={setRegional} />
</>
}
/>
<MacroCalendar
events={events}
activeView={cal.activeView}
selectedDate={cal.selectedDate}
onDateSelected={cal.onSelectDate}
onSwitchToDay={cal.onSelectDate}
onVisibleRangeChange={cal.onVisibleRangeChange}
/* …event callbacks (onEditEvent, onDeleteEvent, onEventTimeChange) */
/>The day view has no dedicated switcher button — it is reached via Hoy. The filters (status, type chips, regional) are composed by the consumer into filtersSlot; the library provides the building blocks (Select, StatusFilterSelect, EventTypeChip) but never owns the filtering.
Theming (CSS variables)
All colors and a few layout tokens are driven by --cal-* CSS variables declared on
:root. Override them on any parent scope (e.g. .my-app, :root, or the element you
wrap the calendar in) to retheme — no Tailwind config or build step required.
Status — base colors
| Variable | Default |
| ------------------------- | --------- |
| --cal-status-default | #cbd5e1 |
| --cal-status-draft | #D4D4D4 |
| --cal-status-published | #39A900 |
| --cal-status-scheduled | #005DCA |
| --cal-status-archived | #D4D4D4 |
| --cal-status-cancelled | #8C1712 |
| --cal-status-finalizado | #9747FC |
| --cal-status-selected | #005DCA |
Status — chip / block surfaces + text
Each status exposes bg, bg-hover, and text (e.g. --cal-status-draft-bg).
| Status | -bg | -bg-hover | -text |
| ------------ | --------- | ----------- | --------- |
| draft | #fafafa | #f4f4f5 | #52525b |
| published | #ecfdf3 | #d1fadf | #1d6b00 |
| scheduled | #e8f1fd | #cfe0fb | #003a82 |
| archived | #fafafa | #f4f4f5 | #52525b |
| cancelled | #fdecec | #fbd2d2 | #5c0f0c |
| finalizado | #f4ebff | #e6d3ff | #4b1a8a |
Surfaces, text & UI
| Variable | Default |
| ---------------------- | -------------------- |
| --cal-surface | #ffffff |
| --cal-surface-subtle | #f8fafc |
| --cal-mini-surface | #f1f5f9 |
| --cal-macro-surface | var(--cal-surface) |
| --cal-border | #e2e8f0 |
| --cal-text | #0f172a |
| --cal-text-muted | #64748b |
| --cal-text-faint | #94a3b8 |
| --cal-primary | #1d4ed8 |
| --cal-primary-hover | #1e40af |
| --cal-danger | #dc2626 |
| --cal-danger-bg | #fef2f2 |
| --cal-focus-ring | #93c5fd |
FullCalendar bridge tokens
Scoped under .cal-macro-root .fc so they never leak to other FullCalendar instances.
| Variable | Default |
| -------------------------- | --------- |
| --cal-fc-border | #e6effa |
| --cal-fc-slot-height | 52px |
| --cal-fc-text | #334155 |
| --cal-fc-font-size | 0.75rem |
| --cal-fc-small-font-size | 0.7rem |
Optional capabilities
The library is flexible like FullCalendar — every visual capability is opt-in via a prop or slot. Omit it and nothing extra renders or costs.
Availability band (availability)
MacroCalendar accepts an optional availability: AvailabilitySchedule ({ daysOfWeek: number[]; startTime: 'HH:mm'; endTime: 'HH:mm' }[]). When provided, it paints a subtle colored "horario de atención" band in the time-grid views (week/day), via recurring background events. When omitted, no band renders. Theme it through --cal-availability-bg.
const availability = [
{ daysOfWeek: [1, 2, 3, 4, 5], startTime: '08:00', endTime: '17:00' },
]
<MacroCalendar availability={availability} ... />The configuration UI for this schedule lives in ape-calendar-form as AvailabilityScheduleDialog — also opt-in.
Agenda day header slot (renderAgendaDayHeader)
The list view (activeView === 'list', rendered by EventAgendaList) exposes a per-day header slot. Use it to surface availability pills ("Horario de atención" / "No tenemos atención") or any other per-day annotation. The library does not derive this from availability — your portal decides what to show.
<MacroCalendar
availability={availability}
renderAgendaDayHeader={(date, { hasEvents }) => {
const slot = availability.find(s => s.daysOfWeek.includes(date.getDay()))
return slot
? <span>Horario de atención · {slot.startTime}–{slot.endTime}</span>
: <span>No tenemos atención</span>
}}
...
/>EventAgendaList is also exported standalone for portals that want to compose the agenda in a custom layout.
Drag granularity (slotDuration / snapDuration)
Both props are optional, default '00:30:00' and '00:15:00'. Override per portal needs (e.g. snapDuration="00:05:00" for tighter resize, or '01:00:00' for hour-only steps).
Messages
User-facing strings are passed via the messages prop. Some fields are plain strings,
others are callbacks — the table below documents the exact shape per component.
MacroCalendar — MacroCalendarMessages
| Field | Type | Default |
| ----------------- | -------------------------------------------------------------------------------- | --------------- |
| moreLink | string \| ((count: number) => string) — the string form interpolates {count} | +{count} more |
| allDayText | string | 'All day' |
| todayHeaderAria | string | 'Today' |
// both are valid:
<MacroCalendar messages={{ moreLink: '+{count} más' }} />
<MacroCalendar messages={{ moreLink: count => `+${count} más` }} />MoreEventsPopover — MoreEventsPopoverMessages
All fields are callbacks.
| Field | Type |
| ------------ | --------------------------- |
| header | (count: number) => string |
| editAria | (title: string) => string |
| deleteAria | (title: string) => string |
Companion package
For event creation/edit forms, install ape-calendar-form.
