Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
kinetix-calendar
happones/kinetix
claude-code
calendar
scheduler
timezone
drag-and-drop
inertia
actions
authorization

Month/week/day-view event scheduler over an Eloquent model: events laid out on a grid with client-side navigation, timezone-correct rendering, and a built-in event-details modal/sheet. The Vue component is KinetixEventCalendar (distinct from the date-picker's KinetixCalendar). Activates when building a calendar/scheduler of events.

Install
php artisan boost:add-skill happones/kinetix

Save this content to: .claude/skills/kinetix-calendar/SKILL.md

# Kinetix Calendar Development

## When to Apply

Activate this skill when:
- Building a month/week/day calendar / scheduler of records that have dates.
- Rendering `<KinetixEventCalendar>`.
- The calendar needs to be correct across timezones, or needs an event
  details popup (modal or slide-in sheet).

**Component name:** use `KinetixEventCalendar` — `KinetixCalendar` is the
date-picker's single-date selector, a different component.

## Documentation

For full details, reference `docs/calendar.md` (published at https://happones.github.io/kinetix/calendar).

## Backend (server-driven, like Tables/Kanban)

```php
use Happones\Kinetix\Actions\Action;
use Happones\Kinetix\Calendar\Calendar;

$calendar = Calendar::make(Event::query())
    ->dateColumn('starts_at')
    ->endColumn('ends_at')          // optional, inclusive multi-day/timed
    ->title('name')
    ->color(fn (Event $e) => $e->color)
    ->description(fn (Event $e) => $e->notes)   // optional, shown in the popup
    ->url(fn (Event $e) => route('events.show', $e))
    ->query(fn ($q) => $q->whereMonth('starts_at', now()->month))
    ->timezone(fn () => auth()->user()->timezone ?? 'UTC') // optional, defaults to config('app.timezone')
    ->eventActions([                                        // optional — omit for read-only
        Action::make('edit')->icon('pencil')
            ->inertiaVisit(fn (Event $e) => route('events.edit', $e)),
        Action::make('delete')->icon('trash')->color('danger')
            ->requiresConfirmation('Delete this event?')
            ->inertiaVisit(fn (Event $e) => route('events.destroy', $e), ['method' => 'delete']),
    ]);

return Inertia::render('Calendar', ['calendar' => $calendar->toData()]);
```

`eventActions()` resolves each `Action` against its event's underlying
record, exactly like `Table::recordActions()` — the same fluent `Action`
builder used by tables/page headers, so `inertiaVisit()`/`request()`
(background HTTP)/`dispatch()` (custom browser event)/`requiresConfirmation()`
/`authorize()`/`visible()`/`hidden()` all work identically here.

No migration or config flag — it's read-only by default and navigates
client-side over the supplied events (scope a sensible window for big
datasets).

### Drag-and-drop rescheduling (`moveable()`)

```php
Calendar::make(Event::query())
    ->dateColumn('starts_at')->endColumn('ends_at')->title('name')
    ->moveable()                        // opt-in: events become draggable
    ->authorizeMove('reschedule')       // optional; default ability `update` when a policy exists
    ->moveScope(['team_id' => $teamId]); // tenant guard, enforced on the endpoint lookup
```

Dragging an event to another day (month view) or hour slot (week/day) POSTs
`{model, recordId, start}` to `{prefix}/tables/calendar-move` — the signed
descriptor mirrors Kanban's (user- and team-bound, expiring, columns sealed in). The end
column shifts by the same delta so durations survive. While dragging, the
source chip dims and the hovered cell/slot highlights with a dashed ghost chip
previewing the landing spot. Moves are optimistic
(snap back + error toast on failure, reload on success), work on touch via
long-press, and have a keyboard alternative (Alt + arrows: ±1 day, ±1 week in
month view, ±1 hour in time grids). The component emits `event-moved(event,
newStartIso)` after a successful move.

### Resizing (`resizable()`)

```php
Calendar::make(Event::query())
    ->dateColumn('starts_at')->endColumn('ends_at')->title('name')
    ->resizable(); // needs endColumn(); works with or without moveable()
```

Dragging an event's end edge POSTs `{model, recordId, end}` to
`{prefix}/tables/calendar-resize`. It uses the same descriptor, ability and
`moveScope()` as moves, and a descriptor only allows the writes its calendar
opted into. The start never changes. The handle is the bottom of a timed
block in week/day (snaps to 15 minutes, auto-scrolls the hour grid) and the
right end of the chip on the event's last day in month view and the all-day
row (whole days, same time of day; all-day end days are inclusive). An end
before the start is clamped client-side (15 minutes / its own day) and
refused server-side (422). Escape cancels. The keyboard alternative is
Alt + Shift + arrows (±1 day; ±1 week in month, ±15 minutes in the time
grids). The component emits `event-resized(event, newEndIso)`.

`start`/`end` always serialize as **absolute-instant ISO-8601 datetimes**
(never date-only) — this is what makes the frontend timezone-correct: an
event's real moment in time is preserved, so it can be re-rendered under any
timezone (the server's resolved default, or a client override) and still land
on the right local day/hour. `allDay` is auto-detected (start/end exactly at
midnight).

## Frontend

```vue
<KinetixEventCalendar
    :calendar="calendar"
    :week-starts-on="1"
    :views="['month', 'week', 'day']"
    event-display="sheet"
    sheet-side="right"
    @event-click="(e) => …"
    @day-click="(date) => …"
    @slot-click="(dateTime) => …"
/>
```

- **Views**: `views` opts into the month/week/day switcher (default
  `['month']`, unchanged single-view behavior). `view`/`v-model:view` for
  controlled state; `anchorDate` (ISO `Y-MM-DD`) sets the initial window —
  useful for deep-linking a specific date.
- **Timezone**: `timezone` prop overrides `calendar.timezone` (the server's
  default) — e.g. pass the viewer's own browser zone
  (`Intl.DateTimeFormat().resolvedOptions().timeZone`) for a true per-visitor
  calendar with zero backend round-trip.
- **Event details popup**: clicking an event opens a built-in modal (default)
  or sheet (`event-display="sheet"`, `sheet-side` top/right/bottom/left) with
  the color, formatted range, description, a "View details" link when `url`
  is set, and any `eventActions` as small icon+label buttons — identically in
  both the modal and the sheet. `@event-click` always fires too;
  `:show-event-details="false"` suppresses the built-in popup for fully
  custom handling.
- **Scroll-to-now**: switching into `week`/`day` (via the switcher, mounting
  directly in that view, or clicking "Today" while already there) auto-scrolls
  the hourly grid so the current time stays in view — a no-op when "now" falls
  outside `start-hour`/`end-hour`.
- `week-starts-on` 0–6 (default Monday); `locale` drives month/weekday/hour
  labels; `start-hour`/`end-hour` restrict the week/day hourly range (e.g.
  business hours `8`/`18`). i18n `calendar_*` (en/es/fr/pt/zh/ja/ru).

The new **`<KinetixSheet>`** primitive (`open`, `side`, `title`,
`description`, `#header`/`#footer` slots) is a standalone, reusable
shadcn-style slide-in panel — usable anywhere, not just for calendar events.

## Creating & editing events (CRUD wiring)

The calendar reads events; CRUD is regular page wiring. Preferred: an in-page
modal — a `KinetixPageHeader` action with `->dispatch('event-create')`, the
page listens for `kinetix:event-create` and opens a `KinetixModal` hosting
`<KinetixForm :form="eventForm" flat @submit="submit" />` (**always pass
`flat` in modals** — the panel is the surface; Sections must not nest a card
inside it). `@day-click`/`@slot-click` prefill the start for click-to-create.
Controllers persist, flash `KinetixFlash::success(__('kinetix.record_created'))`
(`record_updated`/`record_deleted` for the other verbs) and `return back()`.
Alternative: dedicated pages via `eventActions()` + `inertiaVisit()`, with the
page controllers flashing the same way before they redirect. Full example:
docs/calendar.md §7.

Kinetix Calendar Development

When to Apply

Activate this skill when:

  • Building a month/week/day calendar / scheduler of records that have dates.
  • Rendering <KinetixEventCalendar>.
  • The calendar needs to be correct across timezones, or needs an event details popup (modal or slide-in sheet).

Component name: use KinetixEventCalendar — KinetixCalendar is the date-picker's single-date selector, a different component.

Documentation

For full details, reference docs/calendar.md (published at https://happones.github.io/kinetix/calendar).

Backend (server-driven, like Tables/Kanban)

use Happones\Kinetix\Actions\Action;
use Happones\Kinetix\Calendar\Calendar;

$calendar = Calendar::make(Event::query())
    ->dateColumn('starts_at')
    ->endColumn('ends_at')          // optional, inclusive multi-day/timed
    ->title('name')
    ->color(fn (Event $e) => $e->color)
    ->description(fn (Event $e) => $e->notes)   // optional, shown in the popup
    ->url(fn (Event $e) => route('events.show', $e))
    ->query(fn ($q) => $q->whereMonth('starts_at', now()->month))
    ->timezone(fn () => auth()->user()->timezone ?? 'UTC') // optional, defaults to config('app.timezone')
    ->eventActions([                                        // optional — omit for read-only
        Action::make('edit')->icon('pencil')
            ->inertiaVisit(fn (Event $e) => route('events.edit', $e)),
        Action::make('delete')->icon('trash')->color('danger')
            ->requiresConfirmation('Delete this event?')
            ->inertiaVisit(fn (Event $e) => route('events.destroy', $e), ['method' => 'delete']),
    ]);

return Inertia::render('Calendar', ['calendar' => $calendar->toData()]);

eventActions() resolves each Action against its event's underlying record, exactly like Table::recordActions() — the same fluent Action builder used by tables/page headers, so inertiaVisit()/request() (background HTTP)/dispatch() (custom browser event)/requiresConfirmation() /authorize()/visible()/hidden() all work identically here.

No migration or config flag — it's read-only by default and navigates client-side over the supplied events (scope a sensible window for big datasets).

Drag-and-drop rescheduling (moveable())

Calendar::make(Event::query())
    ->dateColumn('starts_at')->endColumn('ends_at')->title('name')
    ->moveable()                        // opt-in: events become draggable
    ->authorizeMove('reschedule')       // optional; default ability `update` when a policy exists
    ->moveScope(['team_id' => $teamId]); // tenant guard, enforced on the endpoint lookup

Dragging an event to another day (month view) or hour slot (week/day) POSTs {model, recordId, start} to {prefix}/tables/calendar-move — the signed descriptor mirrors Kanban's (user- and team-bound, expiring, columns sealed in). The end column shifts by the same delta so durations survive. While dragging, the source chip dims and the hovered cell/slot highlights with a dashed ghost chip previewing the landing spot. Moves are optimistic (snap back + error toast on failure, reload on success), work on touch via long-press, and have a keyboard alternative (Alt + arrows: ±1 day, ±1 week in month view, ±1 hour in time grids). The component emits event-moved(event, newStartIso) after a successful move.

Resizing (resizable())

Calendar::make(Event::query())
    ->dateColumn('starts_at')->endColumn('ends_at')->title('name')
    ->resizable(); // needs endColumn(); works with or without moveable()

Dragging an event's end edge POSTs {model, recordId, end} to {prefix}/tables/calendar-resize. It uses the same descriptor, ability and moveScope() as moves, and a descriptor only allows the writes its calendar opted into. The start never changes. The handle is the bottom of a timed block in week/day (snaps to 15 minutes, auto-scrolls the hour grid) and the right end of the chip on the event's last day in month view and the all-day row (whole days, same time of day; all-day end days are inclusive). An end before the start is clamped client-side (15 minutes / its own day) and refused server-side (422). Escape cancels. The keyboard alternative is Alt + Shift + arrows (±1 day; ±1 week in month, ±15 minutes in the time grids). The component emits event-resized(event, newEndIso).

start/end always serialize as absolute-instant ISO-8601 datetimes (never date-only) — this is what makes the frontend timezone-correct: an event's real moment in time is preserved, so it can be re-rendered under any timezone (the server's resolved default, or a client override) and still land on the right local day/hour. allDay is auto-detected (start/end exactly at midnight).

Frontend

<KinetixEventCalendar
    :calendar="calendar"
    :week-starts-on="1"
    :views="['month', 'week', 'day']"
    event-display="sheet"
    sheet-side="right"
    @event-click="(e) => …"
    @day-click="(date) => …"
    @slot-click="(dateTime) => …"
/>
  • Views: views opts into the month/week/day switcher (default ['month'], unchanged single-view behavior). view/v-model:view for controlled state; anchorDate (ISO Y-MM-DD) sets the initial window — useful for deep-linking a specific date.
  • Timezone: timezone prop overrides calendar.timezone (the server's default) — e.g. pass the viewer's own browser zone (Intl.DateTimeFormat().resolvedOptions().timeZone) for a true per-visitor calendar with zero backend round-trip.
  • Event details popup: clicking an event opens a built-in modal (default) or sheet (event-display="sheet", sheet-side top/right/bottom/left) with the color, formatted range, description, a "View details" link when url is set, and any eventActions as small icon+label buttons — identically in both the modal and the sheet. @event-click always fires too; :show-event-details="false" suppresses the built-in popup for fully custom handling.
  • Scroll-to-now: switching into week/day (via the switcher, mounting directly in that view, or clicking "Today" while already there) auto-scrolls the hourly grid so the current time stays in view — a no-op when "now" falls outside start-hour/end-hour.
  • week-starts-on 0–6 (default Monday); locale drives month/weekday/hour labels; start-hour/end-hour restrict the week/day hourly range (e.g. business hours 8/18). i18n calendar_* (en/es/fr/pt/zh/ja/ru).

The new <KinetixSheet> primitive (open, side, title, description, #header/#footer slots) is a standalone, reusable shadcn-style slide-in panel — usable anywhere, not just for calendar events.

Creating & editing events (CRUD wiring)

The calendar reads events; CRUD is regular page wiring. Preferred: an in-page modal — a KinetixPageHeader action with ->dispatch('event-create'), the page listens for kinetix:event-create and opens a KinetixModal hosting <KinetixForm :form="eventForm" flat @submit="submit" /> (always pass flat in modals — the panel is the surface; Sections must not nest a card inside it). @day-click/@slot-click prefill the start for click-to-create. Controllers persist, flash KinetixFlash::success(__('kinetix.record_created')) (record_updated/record_deleted for the other verbs) and return back(). Alternative: dedicated pages via eventActions() + inertiaVisit(), with the page controllers flashing the same way before they redirect. Full example: docs/calendar.md §7.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect