Calendar
Date-only deadlines with month and agenda views, entity filtering and record previews.
Review deadlines across entities, inspect a record without leaving the calendar, and start a filing or task for a specific day.
Month
Click a filing to inspect its details. October 13 has five records: 2 more opens all five; choosing a record replaces that list inside the same popup. Escape closes the preview and returns focus to the originating trigger. Today, previous and next navigate the selected month.
The month has five or six weeks. Adjacent-month dates are muted but their events retain their colors. Weekend cells have a quiet background. Today uses the red date badge. At most three chips appear per day.
Choosing the Component
| You need to… | Use |
|---|---|
| Review records by month or in a chronological list | Calendar |
| Enter a date, month or date range in a form | DatePicker |
| Sort and filter records with many columns | Table |
Calendar owns presentation and interaction. Your application owns records, permissions, status calculations and persistence. Dates represent whole days; time slots, recurrence and drag scheduling are outside this component's contract.
Usage
import { Calendar, type CalendarEvent } from '@commenda/pathfinder/calendar';
const events: CalendarEvent[] = [
{
id: 'vat-q3',
title: 'VAT return Q3',
date: '2026-10-20',
category: { id: 'indirect', label: 'Indirect tax', tone: 'blue' },
entity: { id: 'de', label: 'Meridian GmbH' },
status: { label: 'In progress', tone: 'info' },
},
];
<Calendar
label="Compliance calendar"
events={events}
dateConfig={{ today: '2026-10-13' }}
/>;Import @commenda/pathfinder/styles.css once in the application. No consumer Tailwind compilation is needed. Interactive callbacks belong in a client component in Next.js App Router.
Agenda
Agenda groups the selected month's records by date. Entity flags, category and status remain visible. Status, assignee and action slots retain their alignment when a record has no assignee. Actions are available on hover, keyboard focus and touch. Narrow containers move trailing details to a second line.
Controlled State
The text below the example shows application state. Navigate or change view, then use Reset. This is the pattern for URL state, saved preferences or a parent-owned toolbar.
Use defaultValue for internal state with an optional change notification. Use value with onValueChange when the parent owns state. Calendar emits the requested value; it does not override a controlled prop. Supplying both modes is a TypeScript error.
Responsive View
The default auto view uses Agenda below 900px of container width and Month at or above it. Choosing Month or Agenda makes that choice persistent. Set a controlled viewConfig.value to auto to restore automatic behavior. An explicitly selected Month view scrolls within its container on narrow screens.
Entity Filtering
Empty selected IDs mean all entities. Choose one entity to remove its repeated name from agenda metadata; flags remain. Filter selection survives month and view changes. The optional configuration follows Pathfinder's value/onValueChange or defaultValue pattern. Supply the complete available entity choices and stable IDs.
Categories and Statuses
Category and lifecycle status are independent. The examples use these verified Figma bar modes:
| Category | category.tone |
|---|---|
| Direct tax | amber |
| Indirect tax | blue |
| Transfer pricing | indigo |
| Entity compliance | purple |
| Payroll | teal |
| Customs | rose |
| Other | neutral |
status.label and status.tone describe the agenda badge. Optional status.appearance controls the compact record presentation:
| Appearance | Behavior |
|---|---|
default | Neutral chip with category bar |
overdue | Category bar retained; red status mark with Overdue tooltip |
filed | Muted category bar and text; green Completed mark |
extended | Dashed neutral outline |
The revised October 12 Figma treatment supersedes older tinted-chip descriptions. The package never infers status from today. For example, an extended deadline can retain an “In progress” badge while using appearance: 'extended'. A flag is an optional React element; Pathfinder gives it optical 12px dimensions. Use the circular country flag assets.
Record Previews and Actions
Clicking a record opens its preview. Navigation occurs only through the explicit Open record action. eventConfig.getMenu(event) supplies the existing recursive Dropdown menu contract for record actions. All records retain their stable IDs when filtering or navigating.
renderPreview(event) replaces the default metadata body. Calendar retains the accessible popup title, close control and focus return. Applications own authorization and save flows. Keep critical information readable without relying on color or a tooltip.
Create on a Date
Click a day background or its plus affordance to choose an action. A temporary New chip appears while the menu is open; cancellation removes it. Each callback receives { date, entityIds }. Clicking an existing record never invokes creation.
The toolbar uses the first action as its default and puts alternatives in a SplitButton. Toolbar creation uses the supplied business-local today; day creation uses the clicked date, including adjacent-month cells. The app opens the form and persists a new event. Calendar does not insert optimistic records automatically.
API Loading and Recovery
This example simulates a request, discards obsolete results, and exposes failure/retry. Navigate rapidly or click Simulate API failure. Replace its timer with your query client or fetch and AbortController.
onVisibleRangeChange reports inclusive { start, end, month, view }. Month includes adjacent grid dates; Agenda reports the selected month's first and last dates. Include selected entity IDs in the application query key. Cancel stale requests and only pass results belonging to the current key. Do not turn loading or failed responses into an empty event array without the corresponding stateConfig.
Empty State
stateConfig.emptyContent explains an empty month or current entity filter. Month retains its date grid so creation remains available. Loading and retryable errors take precedence over empty content.
API Reference
Import CalendarProps, CalendarEvent, CalendarEntity, CalendarView, CalendarTone, CalendarVisibleRange, CalendarAddContext and CalendarAddAction from @commenda/pathfinder/calendar or the package root. The public API is one component with direct configuration props; day cells and popup components are internal.
| Prop | Type | Default / ownership |
|---|---|---|
label | string | Required accessible region name |
events | readonly CalendarEvent[] | Required; app owns data and unique IDs |
dateConfig.today | string | Required YYYY-MM-DD business-local date |
dateConfig.locale | string | en-US; dates use Intl formatting |
dateConfig.weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 (Sunday) |
monthConfig | SelectionProps<string> | Today’s YYYY-MM month |
viewConfig | SelectionProps<CalendarView> | auto; also month or agenda |
entityFilterConfig | Selection config + entities, optional label | Omitted: no filter; empty IDs: all |
eventConfig | renderPreview, onOpen, getMenu | Default metadata preview |
addConfig.actions | readonly CalendarAddAction[] | Omitted: read-only calendar |
stateConfig | isLoading, loadingLabel, error, emptyContent | Ready |
onVisibleRangeChange | (range: CalendarVisibleRange) => void | App-owned requests |
className | string | Root styling only |
Event Records
| Field | Type | Guidance |
|---|---|---|
id | string | Stable and unique across the supplied collection |
title | string | Full record name; chips truncate, previews expose it |
date | string | Canonical YYYY-MM-DD deadline |
category | { id, label, tone } | Category identity, readable label and one of the seven bar tones |
entity | { id, label, flag? } | Stable entity identity; optional circular flag element |
status | { label, tone?, appearance? } | Badge content and independent compact-chip treatment |
assignee | PersonIdentity | { kind: 'person', userId?, name?, email?, src? }; omission means unassigned |
Input order determines event order within each day. Agenda sorts day groups chronologically. The application should sort its records before passing them if a business-specific priority is required.
Navigation and Entity Configs
| Config | Fields | Behavior |
|---|---|---|
monthConfig | value, onValueChange or defaultValue, optional onValueChange | Values are YYYY-MM |
viewConfig | Same controlled/uncontrolled shape | month, agenda or auto |
entityFilterConfig | Same shape with readonly string[], plus entities, optional label | Empty IDs means all; omitted config hides the filter |
entities is a readonly array of { id, label, flag? }. The app reconciles selected IDs when its available entities change. Calendar filters the supplied event collection by those IDs; it does not fetch missing records.
Record Interactions
eventConfig field | Signature | When to use it |
|---|---|---|
renderPreview | (event: CalendarEvent) => ReactNode | Replace default metadata with a domain-specific preview |
onOpen | (event: CalendarEvent) => void | Show Open record and route to the full record when it is pressed |
getMenu | (event: CalendarEvent) => DropdownMenu | Supply recursive actions; available in Agenda and record previews |
An event click opens its preview; it does not call onOpen. Action callbacks receive the same record object you supplied. Check permissions before supplying action items, and apply the same checks in your API.
Creation Config
addConfig.actions is a readonly array of { id, label, onAction }. Its context is:
type CalendarAddContext = {
date: string;
entityIds: readonly string[];
};The first action is the toolbar default; remaining actions appear in its split menu. Every supplied action appears in the day menu. Omit addConfig for a review-only calendar. An empty action array also hides creation controls.
Loading, Errors and Empty Results
stateConfig field | Type | Behavior |
|---|---|---|
isLoading | boolean | Replaces the event content with a named loading status; navigation remains usable |
loadingLabel | string | Defaults to “Loading events…” |
error | { message: string; onRetry?: () => void } | Displays the message and an optional Retry action |
emptyContent | ReactNode | Shown when the selected month/filter has no events |
Precedence is loading → error → ready/empty. On a request failure, clear loading and provide the error. Use empty content only for a successful request with no matching records. Do not pass stale results for a new visible range.
Visible Range Callback
type CalendarVisibleRange = {
start: string; // Inclusive YYYY-MM-DD
end: string; // Inclusive YYYY-MM-DD
month: string; // YYYY-MM
view: 'month' | 'agenda'; // Effective view, never 'auto'
};The callback runs after the initial render and when its range or effective view changes. Changing entities alone does not change the date range; include entity state separately in your request key. auto can report Agenda initially and then Month after the container is measured, so request cancellation matters.
Dates are canonical date-only values. No timestamps, locale-formatted strings, UTC-midnight conversions or implicit browser clock. Calendar arithmetic does not shift dates across timezones. The supported range is years 1000–9999; boundary grid slots outside that range are blank. Invalid dates throw a descriptive RangeError; validate API data before rendering.
Keyboard and Accessibility
| Key / input | Result |
|---|---|
| Tab / Shift+Tab | Move through navigation, filters, event triggers and available day actions |
| Enter / Space on an event | Open its preview |
| Enter / Space on N more | Open all events for the day |
| Escape | Close the active popup and restore focus to its trigger |
| Arrow keys in the view selector | Choose Month or Agenda |
| Hover / focus a status mark | Show Completed or Overdue text |
Provide an accessible label, meaningful event titles and text status labels. Color is supplementary. Every event remains reachable through its day overflow. The mobile default is Agenda; an explicitly chosen Month view uses internal horizontal scrolling.
Figma Rules and Decisions
- CALENDAR-001: Shared toolbar and filter state for both views; named navigation controls and month announcements.
- CALENDAR-002: Outside-month text uses pearl-800 instead of the master’s pearl-600 to meet text contrast. Sunday-first default, five/six rows, 120px nominal days, 4px padding, half-pixel rules.
- CALENDAR-003: Three chips plus overflow; selecting from a day list replaces its content in one dialog.
- CALENDAR-004: 20px chips, 3px bar, 12px circular flag, single-line title. The preview exposes full text.
- CALENDAR-005: Revised neutral fills; separate category and lifecycle presentation.
- CALENDAR-006: Status tooltip targets are keyboard-focusable siblings, never nested buttons.
- CALENDAR-007: Agenda retains status104 / avatar24 / actions24 slots; missing assignments do not shift columns.
- CALENDAR-008: Creation forwards the date and entity IDs; a draft is temporary, not saved data.
- CALENDAR-009: Enter/Space open previews. Escape/outside dismiss; focus returns to the originating trigger.
- CALENDAR-010: Month changes are instant for all input methods, including reduced motion. The toolbar never slides.
Month uses table semantics and native buttons, not an incomplete ARIA grid. Tab traverses interactive records and add targets; arrow keys operate the view selector and existing selection/menu primitives. Mobile defaults to Agenda. No dragging, timed-event scheduling or recurrence engine is included.
Sources: Calendar rules, component masters, interaction examples. Revised category modes were verified directly from Figma, including indigo Transfer pricing and teal Payroll.