Calendar
The calendar package renders one Calendar component with switchable views over a single event
source: a month grid with multi-day event spanning, per-day overflow, and keyboard navigation,
week and day time grids with hour-precise event blocks, and a resource-planning timeline
(“Plantafel”) of resource rows against a day-granular date axis. Every view supports
drag-to-reschedule and shares the same adapter, endpoint, and client-side event cache — switching
views reuses everything already loaded.
Installation
Section titled “Installation”composer require lattice-php/calendarThat is the whole integration: the package ships its React renderer as source, and the
lattice() Vite plugin compiles it into your app’s bundle via virtual:lattice/plugins
(see Component packages). The PHP classes are picked up by
Lattice’s discovery and TypeScript generation automatically.
No-build apps use the precompiled module the package also ships:
run php artisan lattice:assets after installation.
Implement one CalendarAdapter for the host application. Its single required method returns the
events overlapping a requested [$from, $until) window ($until exclusive):
use Carbon\CarbonImmutable;use Lattice\Calendar\AsCalendar;use Lattice\Calendar\CalendarAdapter;use Lattice\Calendar\CalendarDefinition;use Lattice\Calendar\CalendarEvent;
final class CompanyCalendarAdapter implements CalendarAdapter{ public function events(CarbonImmutable $from, CarbonImmutable $until): iterable { return Meeting::query() ->where('starts_at', '<', $until) ->where('ends_at', '>', $from) ->get() ->map(fn (Meeting $meeting): CalendarEvent => CalendarEvent::make( (string) $meeting->id, $meeting->starts_at->format('Y-m-d\TH:i:s'), $meeting->ends_at->format('Y-m-d\TH:i:s'), )->label($meeting->title)->color($meeting->color)); }}
#[AsCalendar('company')]final class CompanyCalendar extends CalendarDefinition{ public function adapter(): CalendarAdapter { return app(CompanyCalendarAdapter::class); }}The adapter is the only application-facing contract. It owns authorization rules beyond the
definition’s authorize(), validation, translations, persistence, and transactions — the calendar
package works with any storage model.
Render it with Calendar::use():
use Lattice\Calendar\CalendarView;use Lattice\Calendar\Components\Calendar;
Calendar::use(CompanyCalendar::class) ->views([CalendarView::Month, CalendarView::Week, CalendarView::Day, CalendarView::Timeline]) ->defaultView(CalendarView::Month) ->date('2026-08-01') ->days(90);->views() picks the enabled views (default: month only); with more than one, the client shows a
view switcher. ->defaultView() chooses the initially active view and falls back to the first
enabled one. ->date() anchors every view — the month view opens on that date’s month, the week
and day views on its week and day, and the timeline window starts on it (default: today).
->days() sets the timeline’s initially rendered window (default 90).
Events
Section titled “Events”CalendarEvent::make($id, $start, $end) uses half-open bounds — $end is exclusive, the same
convention as the events() window. The input format decides the event kind:
- Plain
Y-m-dstrings make an all-day event; a one-day event has$endone day after$start. - A datetime string or
DateTimeInterfacemakes a timed event, normalized toY-m-d\TH:i:s.->allDay()overrides the inference either way.
->label() sets the visible text and ->color() accepts any Lattice color (see
Enums reference); uncolored events render in the theme’s primary tone.
->context([...]) attaches data that is merged into the event action’s payload on click, and
->resource($resourceId) binds the event to a timeline row (see below).
Capabilities
Section titled “Capabilities”The timeline view and rescheduling are opt-in adapter capabilities:
use Illuminate\Http\Request;use Lattice\Calendar\ProvidesCalendarResources;use Lattice\Calendar\ReschedulesCalendarEvents;use Lattice\Calendar\ResourceGroup;
final class ProjectPlanAdapter implements CalendarAdapter, ProvidesCalendarResources, ReschedulesCalendarEvents{ public function groups(): array { return [ ResourceGroup::make('projects', 'Projects')->resources([ ['id' => 'website-relaunch', 'label' => 'Website Relaunch'], ]), ResourceGroup::make('employees', 'Employees')->resources( fn (): array => Employee::query()->select('id', 'name as label')->get()->toArray(), ), ]; }
public function reschedule(Request $request): CalendarEvent { // validate id/resourceId/start/end, persist, return the updated event }
// ... events()}ProvidesCalendarResourcessupplies the timeline’s resource rows and is required as soon asCalendarView::Timelineis enabled — serialization throws aLogicExceptionotherwise.ResourceGroup::make($key, $label)->resources()takes either an inline list of['id' => ..., 'label' => ...]rows or a closure returning the same shape, evaluated once per render. Timeline lanes lay out the events bound to a resource via->resource(); the month view shows every event regardless of binding.ReschedulesCalendarEventsenables drag-to-reschedule in every view. Without it the calendar renders read-only and itsPATCHendpoint responds405.
Actions
Section titled “Actions”Clicks are wired to regular Lattice actions:
Calendar::use(CompanyCalendar::class) ->eventAction(ShowEventAction::class) ->dayAction(PlanDayAction::class);An event click posts { eventId, ...context } and a day click posts { date } to the sealed
action endpoint; the returned effects (toast, modal, redirect, …) dispatch as usual. Events and
day cells are only interactive when the matching action is configured.
Month view
Section titled “Month view”The month grid follows the viewer’s locale for week start and weekday labels, spans multi-day events across day cells with stable per-week lanes, and marks today. Three event lanes are visible per week; further events collapse into a per-day “+N more” button that opens a popover listing everything on that day.
The grid is keyboard-navigable: arrow keys move the focused day (crossing a month boundary navigates), PageUp/PageDown jump a month, and Enter fires the day action. Prev/next/today controls and the month title round out the header; navigating prefetches the adjacent months’ grids.
Rescheduling
Section titled “Rescheduling”With ReschedulesCalendarEvents, dragging an event chip onto another day cell
moves the event by whole days: the duration stays intact, all-day events shift their date bounds,
and timed events keep their wall-clock times while only the dates move. Dragging any weekly
segment of a multi-week event moves the whole event, anchored to the day the drag started on.
Keyboard users can focus a chip and press Control+Shift with an arrow key:
left/right moves by one day, up/down by one week. The optimistic update, rollback, and announced
error handling match the timeline’s rescheduling.
Week and day views
Section titled “Week and day views”The week and day views share one time grid: a scrollable 24-hour axis (opening at 07:00) with hour-precise event blocks, an all-day row on top, and a now indicator on today’s column. The week view follows the viewer’s locale for its first weekday; the day view is the same grid with a single column. All-day events and events spanning several calendar days render as chips in the all-day row; single-day timed events become blocks positioned by their wall-clock times, and overlapping blocks split the column side by side.
Hour-precise rescheduling
Section titled “Hour-precise rescheduling”With ReschedulesCalendarEvents, dragging a block moves the event to the day and
time under the cursor, snapped to 15 minutes with the duration intact; a dashed preview shows the
snapped target while dragging. Dragging the handle at a block’s lower edge changes the event’s end
time instead, down to a 15-minute minimum. All-day chips move by whole days, exactly like in the
month view. Keyboard users can focus a block and press
Control+Shift with an arrow key — left/right moves by one day, up/down by 15 minutes — or focus the resize handle and press an arrow key alone to adjust the end time. The optimistic update, rollback, and announced error handling match the timeline’s rescheduling.
Timeline view
Section titled “Timeline view”The timeline draws each resource’s events as bars against a sticky month/calendar-week/day header,
with weekend columns striped and a marker line for today. Toolbar controls: ‹/› step the
visible window by a week, Today recenters on it, and −/+ zoom the day column width
(10–64px) without changing how many days are loaded. Resource groups collapse independently via
their chevron. Overlapping same-resource events stack into lanes automatically, growing the row —
events() can simply return whatever overlaps.
Timeline lane layout is day-granular: a timed event occupies the calendar days it touches.
Rescheduling and resizing
Section titled “Rescheduling and resizing”Drag an event to a resource row to change its resource and dates in one operation, or drag either edge to resize. Keyboard users can focus an event and press Control+Shift with an arrow key: left/right moves by one day and up/down moves to the adjacent resource. The client updates immediately while the adapter runs, then either applies the returned event or rolls back and raises the adapter’s translated error message as a toast. Each event represents one resource assignment — if one logical event belongs to several resources, return one event per assignment with a stable, unique ID.
Endpoint and lazy loading
Section titled “Endpoint and lazy loading”Navigating past the initially loaded window fetches the missing range from the definition’s
endpoint, merging it into the shared client-side cache rather than re-fetching already-seen days —
the same signed-reference pattern Lattice tables and trees use. The package registers its own
route (there is no core routes seam), following Lattice’s group conventions:
config('lattice.calendars.middleware', ['web', 'auth']) and
config('lattice.calendars.endpoint', 'lattice/calendars/{calendar}').
GET lattice/calendars/{calendar}?from=Y-m-d&to=Y-m-d (to exclusive) re-resolves the definition
from its sealed reference and calls adapter()->events($from, $to) again — authorize() on the
definition gates both the initial render and every window fetch, mirroring
trees and the signing machinery behind
them.
PATCH lattice/calendars/{calendar} accepts id, resourceId, start, and end, then calls
reschedule($request) on adapters implementing ReschedulesCalendarEvents. resourceId is
null for resource-less events, and start/end arrive in the event’s own representation —
Y-m-d bounds for all-day events, wall-clock datetimes for timed ones — so each adapter validates
exactly the shapes it accepts. The success response contains the updated event; failed Laravel
validation responses are forwarded without replacing their translated message.
Translations
Section titled “Translations”The component’s strings ship with inline English defaults. With
laravel-i18next enabled, the plugin’s calendar
namespace is loaded automatically and serves the bundled en/de translations (override them
like any Laravel package translation — see Internationalization).
Bundle size
Section titled “Bundle size”The breakdown below measures the package’s built plugin — React and the framework runtime are external, so this is exactly what the calendar package adds on top of an app that already ships Lattice. It is regenerated on every docs build.
15.0 KB gzipped · 64.0 KB raw JavaScript
| Dependency | Raw | Gzip | Share |
|---|---|---|---|
| @lattice-php/calendar | 56.4 KB | 14.2 KB | 94.9% |
| Bundler runtime | 7.7 KB | 0.8 KB | 5.1% |
Emitted JavaScript files
| File | Raw | Gzip |
|---|---|---|
plugin.js | 64.0 KB | 15.0 KB |