KQ-1305 · WEEK/PLANNER

Week Planner

A week drawn on its hours. Pressing empty time and dragging rubber-bands a block out of the pressed slot, its far edge 1:1 under the pointer with a snapped line and a live time label, ticking at every new slot; on release the edge springs onto the snapped time on snap with the pointer's velocity and the event thocks down with its title ready to type. Events drag across time and days and stretch from either edge the same way, landing on snap with the release velocity while a dashed placeholder shows the landing and overlapping events re-divide their columns on glide; a now line moves through today on drift. The grid is one tab stop with a slot cursor (arrows, Shift for a range, Enter to create), and each event moves with the arrows, stretches with Shift, renames with Enter and goes with Delete.

KQ-1305 · WEEK/PLANNER

Thu 1 Oct

Up and Down move the event by 15 minutes, Shift with Up or Down changes its end, Left and Right move it a day, Enter renames, Delete removes, Escape returns to the grid.

Arrow keys move the slot cursor, Shift with Up or Down selects a range, Enter creates an event there, t jumps to now.

13:30 · 22 events this week · drag across empty time to add

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/week-planner

Props

snap
number
default 15
Minutes every drag, resize and cursor step snaps to; also the gutter's tick marks.
density
"compact" | "cozy" | "roomy"
default "cozy"
Height of an hour (40, 52 or 68px), with type and padding to match. A change glides every event to its new place.
now
Date | number
default defaultWeekPlannerNow
The current moment, which draws the now line and shades time already gone: a Date or epoch ms, or a number from 0 to 24 read as that hour of today (13.5 is 13:30).
events / defaultEvents
WeekEvent[]
default defaultWeekEvents
Events with epoch-ms start and end, a title, an optional calendar id and location. Controlled or uncontrolled.
onEventsChange
(events: WeekEvent[]) => void
default —
Fires from the drag, key or field that changed the events, with all of them.
calendars
WeekCalendar[]
default defaultWeekCalendars
The calendars events belong to, each with a label and a tint (a CSS colour token).
day / defaultDay
Date | number
default today
The focused day: its week is shown, and on a phone the day itself. Controlled or uncontrolled.
onDayChange
(day: number) => void
default —
Fires with the new day's midnight (epoch ms) from the arrows, the day strip, the headers or the keys.
selected / defaultSelected
string | null
default null
The selected event, shown with a stronger tint and, from 68rem, in the details panel.
onSelectedChange
(id: string | null) => void
default —
Fires when an event is picked, created or let go.
today
Date | number
default the date of now
The date marked as today; an hour-form now falls on it.
hours
[number, number]
default [7, 21]
The hours the grid draws. Events outside are clipped to them.
workHours
[number, number]
default [9, 18]
Working hours, drawn on the lighter ground; the keyboard cursor starts at their first hour when now is elsewhere.
weekStartsOn
0 | 1
default 1
1 starts weeks on Monday, 0 on Sunday.
zoneOffset
number
default 0
Minutes east of UTC that times are drawn in, never the machine's own zone, so the server and the browser draw the same week.
defaultDuration
number
default 30
Minutes an event made with Enter or New lasts when no range is selected.
newTitle
string
default "New event"
A new event's title, selected in its field so typing replaces it.
defaultCalendar
string
default —
The calendar new events join. Defaults to the first calendar.
onCreate
(event: WeekEvent) => void
default —
An event was drawn, made with Enter, or added with New.
onUpdate
(event: WeekEvent, previous: WeekEvent) => void
default —
An event moved, was resized, renamed or changed calendar.
onDelete
(event: WeekEvent) => void
default —
An event was removed with Delete, its close button or the details panel.
label
string
default "Week"
The planner's accessible name.
status
"ready" | "loading" | "error"
default "ready"
Loading covers the hours with a note; error offers Retry.
onRetry
() => void
default —
Retry was pressed on the error state.
sound
boolean
default false
Play a tick at every new slot a drag or key reaches and a thock when an event lands. Off unless asked for.
disabled
boolean
default false
Makes the whole planner inert.
className
string
default —
Classes for the root; it is 560px tall by default and lays out by its own width.