KQ-1349 · CALENDAR/APP
Calendar App
A complete calendar screen built around week-planner and meeting-card: a month grid and the week that morph into each other — the week you are in is a row of the month, so Week grows that row to fill the grid on glide while the other weeks fold away, then the planner cross-fades in over it, and Month runs it back — with a view switch whose pill travels on snap, months that slide in from 16px, a sidebar of mini month, calendars and the day ahead, and an event drawer that docks on glide, slides over or rises as a sheet you can pull down 1:1. Everything is read against `now`. The month is a real grid: arrows, Home, End and Page keys move the day, Enter steps into its events and Escape steps back out.
October 2026
Arrow keys move the day, Home and End to the week's ends, Page Up and Page Down a month. Enter steps into the day's events, Escape steps back out.
drawer open · design review · escape closes
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/calendar-app
Props
- view / defaultView
- "month" | "week"
- default "month"
- The month, or the focused day's week; changing it runs the morph.
- onViewChange
- (view: CalendarView) => void
- default —
- Fires from the switch, a day's number or Show in week.
- drawer
- "side" | "over" | "sheet"
- default "side"
- How the event drawer arrives: docked at the right (the calendar narrows), over the calendar above a scrim, or as a sheet from the bottom that pulls down. A phone always uses the sheet.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Month chips (two plain, three with times on wide grids, or three filled) and week-planner's hour height.
- events / defaultEvents
- CalendarEvent[]
- default defaultCalendarEvents
- Week-planner events with optional attendees, agenda and notes; an event with attendees opens as a meeting card.
- onEventsChange
- (events: CalendarEvent[]) => void
- default —
- Fires from the drag, edit, creation or deletion that changed the events, with all of them.
- calendars
- WeekCalendar[]
- default defaultCalendarCalendars
- The calendars events belong to, with their tints (CSS colours, never hex).
- hidden / defaultHidden
- string[]
- default []
- Calendar ids whose events are hidden everywhere.
- onHiddenChange
- (ids: string[]) => void
- default —
- Fires from the calendar checkboxes.
- day / defaultDay
- Date | number
- default the day of `now`
- The focused day: the month and the week shown, and the day the agenda reads.
- onDayChange
- (day: number) => void
- default —
- Fires with the new day's midnight from the grid, the mini month, the arrows, Today or the planner.
- selected / defaultSelected
- string | null
- default null
- The event in the drawer, or null for a closed drawer.
- onSelectedChange
- (id: string | null) => void
- default —
- Fires from a chip, an agenda row, a planner event, Close, Escape, the scrim or the pull.
- now
- Date | number
- default defaultCalendarNow
- The current moment: today, the now line, the agenda's Now and countdowns, and the meeting card's ring. Never read from the clock during render.
- zoneOffset
- number
- default 0
- Minutes east of UTC that dates and times are drawn in, so server and browser agree.
- weekStartsOn
- 0 | 1
- default 1
- 1 starts weeks on Monday, 0 on Sunday.
- onCreate
- (event: CalendarEvent) => void
- default —
- An event was drawn or added in the week.
- onDelete
- (event: CalendarEvent) => void
- default —
- An event was deleted from the drawer or the week.
- onJoinedChange
- (id: string, joined: boolean) => void
- default —
- The viewer joined or left a meeting from its card.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholder chips; error offers Try again.
- onRetry
- () => void
- default —
- Try again was pressed in the error state.
- label
- string
- default "Calendar"
- The screen's accessible name.
- sound
- boolean
- default false
- Play a swish for the morph, the drawer and the sidebar and a tick for each step of the day cursor; the composed pieces play their own. Off unless asked for.
- disabled
- boolean
- default false
- Read only: nothing can be moved, made or deleted.
- className
- string
- default —
- Classes for the root. It is 560px tall by default; pass a height class to change it.
| Prop | Type | Default | Description |
|---|---|---|---|
| view / defaultView | "month" | "week" | "month" | The month, or the focused day's week; changing it runs the morph. |
| onViewChange | (view: CalendarView) => void | — | Fires from the switch, a day's number or Show in week. |
| drawer | "side" | "over" | "sheet" | "side" | How the event drawer arrives: docked at the right (the calendar narrows), over the calendar above a scrim, or as a sheet from the bottom that pulls down. A phone always uses the sheet. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Month chips (two plain, three with times on wide grids, or three filled) and week-planner's hour height. |
| events / defaultEvents | CalendarEvent[] | defaultCalendarEvents | Week-planner events with optional attendees, agenda and notes; an event with attendees opens as a meeting card. |
| onEventsChange | (events: CalendarEvent[]) => void | — | Fires from the drag, edit, creation or deletion that changed the events, with all of them. |
| calendars | WeekCalendar[] | defaultCalendarCalendars | The calendars events belong to, with their tints (CSS colours, never hex). |
| hidden / defaultHidden | string[] | [] | Calendar ids whose events are hidden everywhere. |
| onHiddenChange | (ids: string[]) => void | — | Fires from the calendar checkboxes. |
| day / defaultDay | Date | number | the day of `now` | The focused day: the month and the week shown, and the day the agenda reads. |
| onDayChange | (day: number) => void | — | Fires with the new day's midnight from the grid, the mini month, the arrows, Today or the planner. |
| selected / defaultSelected | string | null | null | The event in the drawer, or null for a closed drawer. |
| onSelectedChange | (id: string | null) => void | — | Fires from a chip, an agenda row, a planner event, Close, Escape, the scrim or the pull. |
| now | Date | number | defaultCalendarNow | The current moment: today, the now line, the agenda's Now and countdowns, and the meeting card's ring. Never read from the clock during render. |
| zoneOffset | number | 0 | Minutes east of UTC that dates and times are drawn in, so server and browser agree. |
| weekStartsOn | 0 | 1 | 1 | 1 starts weeks on Monday, 0 on Sunday. |
| onCreate | (event: CalendarEvent) => void | — | An event was drawn or added in the week. |
| onDelete | (event: CalendarEvent) => void | — | An event was deleted from the drawer or the week. |
| onJoinedChange | (id: string, joined: boolean) => void | — | The viewer joined or left a meeting from its card. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholder chips; error offers Try again. |
| onRetry | () => void | — | Try again was pressed in the error state. |
| label | string | "Calendar" | The screen's accessible name. |
| sound | boolean | false | Play a swish for the morph, the drawer and the sidebar and a tick for each step of the day cursor; the composed pieces play their own. Off unless asked for. |
| disabled | boolean | false | Read only: nothing can be moved, made or deleted. |
| className | string | — | Classes for the root. It is 560px tall by default; pass a height class to change it. |