React components and hooks
createCalendar, Root and its props, the components, hooks, meta, and diagnostics.
Everything on this page is exported from @moltenship/calendar.
createCalendar
const Calendar = createCalendar<TEvent, TResource, TSelection, TFocus>()(options);Creates a calendar: a set of components and hooks typed to your records. Call it once at module
scope. It's called twice: the first call takes your types, and the second infers the view names
from views and the ID types from the adapter.
TEvent: your event record. Required.TResource: your resource record. Defaults tounknown; useneverif you don't have resources.TSelection,TFocus: the types of theselectionandfocusstate. Default toCalendarSelectionandCalendarFocus, the days, slots, and events the built-in views read and write. A calendar with only custom views can use its own types.
Prop
Type
The returned object holds the components and hooks below, plus views, the list you passed in.
Use it to derive the view-name type: (typeof Calendar.views)[number]["name"].
defineCalendarView
const view = defineCalendarView({ name, defaultOptions, strategy, Renderer });Defines a view. defaultOptions is required only when the strategy's options can't be undefined.
Custom views covers the strategy, the projection input, and the
Renderer props. The related types are CalendarViewStrategy, CalendarViewProjectionInput, and
CalendarViewRendererProps.
resolveViewInstantRange(range, timeZone) turns visible dates into the instant range that Root
passes to project as instantRange, and resolveViewLoadRange(strategy, anchor, environment, options, range) gives the loadRange it passes. Use them to run a strategy outside React, for
example in a test.
Root
<Calendar.Root> holds the state and provides it to everything inside. It renders no element of its
own.
Prop
Type
You can write most props inline. locale, weekRules, now, and controlled values are compared by
value; events and resources are compared item by item; viewOptions, onDiagnosticsChange, and
the change handlers are read from the latest render. Keep a clock object stable, for example at
module scope.
Root throws on its first render if it has no date (calendar-root-requires-anchor-date), no
time zone (calendar-root-requires-time-zone), or no view (calendar-root-requires-view).
Components
Use these inside Root. Each renders one plain element with a data-calendar-part attribute and
accepts that element's usual props, including className, style, ref, and event handlers.
| Component | Renders | Notes |
|---|---|---|
Frame | <section> | Outer wrapper. Gets the {id}-frame ID and is labelled by the title. |
Toolbar | <header> | Wrapper for controls. |
Title | <span> | The active view's title, or its children. Gets the {id}-title ID. |
PreviousTrigger | <button> | Moves back one step of the active view. Disabled when no view is active. |
NextTrigger | <button> | Moves forward one step of the active view. |
TodayTrigger | <button> | Moves to today in the calendar's time zone, from now, clock, or the system clock. |
ViewSwitcher | <select> | One option per view. Requires getViewLabel(name). |
Viewport | <div> | Wrapper for the view. Gets the {id}-viewport ID. |
ActiveView | view output | Renders the active view, or fallback if it isn't registered. |
Event | your output | Looks up one event by eventId and calls children with it. |
Announcer | <div> | A hidden live region that speaks moves, resizes, and creates. Requires messages. |
- The buttons default to
type="button"and setaria-controlsto the viewport. Give each one text or anaria-label. - Your
onClickoronChangeruns first. Callevent.preventDefault()in it to skip the built-in action. Frame,Title, andViewportdon't acceptid. SetidonRootinstead, so the IDs andaria-controlsalways match.
Event passes your record and its normalized form:
<Calendar.Event eventId="standup" fallback={<p>Not found</p>}>
{({ event, normalized }) => (
<p>
{event.title} starts at {normalized.start.toString()}
</p>
)}
</Calendar.Event>Hooks
The hooks on your calendar work anywhere inside its Root. Using them, or any component above,
outside Root throws calendar-hook-outside-root.
| Hook | Returns |
|---|---|
useCalendar() | { state, actions, meta } |
useCalendarState() | The six state values |
useCalendarActions() | The actions below. The object never changes. |
useCalendarMeta() | The meta object |
useCalendarSelector(selector, opts?) | selector(state). Re-renders only when it changes. |
useVisibleRange() | The active view's visible dates, or null |
useViewProjection() | The active view and its projection, or null |
useCalendarSelector compares results with Object.is. Pass { isEqual } to compare another way.
The selector can be an inline function.
Announcer's messages, and the keyboard hooks for your own views (useCalendarKeyboard,
useRovingFocus, useCalendarInteraction, useCalendarAnnouncer), are covered in
Accessibility.
useNow({ intervalMs }) is exported on its own, not from your calendar. It returns the current
instant from the nearest Root's now or clock, or the system clock, and samples it every
intervalMs (one minute by default). It returns null during server rendering and hydration unless
Root has now.
useAnchorDateRequests(listener) calls listener(date, range) with every date the calendar is
asked to show: from Previous, Next, Today, keys paging past the view, or setAnchorDate. That
includes a date that is already the anchor. range holds the dates the active view shows for
date, so a week view can tell Today inside its week from a date that moves it. A view whose
scroll position can run ahead of the anchor uses it to show its range again when Today asks for
it. The scrolling views do this.
Actions
| Action | Effect |
|---|---|
setView(name) | Changes the view |
setAnchorDate(date) | Changes the date |
setTimeZone(zone) | Changes the time zone |
setSelection(value) | Changes the selection (null clears it) |
setFocus(value) | Changes the focus (null clears it) |
setExpandedResourceIds(ids) | Changes the expanded resources |
navigatePrevious(), navigateNext() | Moves by one step of the active view |
navigateToday() | Moves to today in the calendar's time zone |
proposeEventChange(proposal) | Proposes a change to an event and resolves with the outcome. See Interactions. |
Setting a value equal to the current one does nothing. Calling two actions in a row builds on the
first, so navigateNext(); navigateNext() moves two steps, even for controlled values.
Meta
Prop
Type
Diagnostics
Problems that don't stop rendering are collected in meta.diagnostics and passed to
onDiagnosticsChange. Time doesn't log them.
code | severity | Cause |
|---|---|---|
unregistered-active-view | error | The view state names a view that isn't registered. |
invalid-event | error | A record was skipped. issue has the details (issue codes). |
Last updated on