KQ-1739 · ZONE/OVERLAP
Zone Overlap
A find-a-time surface: a team across time zones as rows on one shared timeline, hours outside each person's day hatched, busy blocks drawn, and an Everyone lane holding the stretches where all are free. The meeting band spans every row with each person's local time on a flag; dragged it follows the pointer 1:1 and lands on a quarter hour on springs.snap with the throw's velocity, and as it enters the everyone-free stretch it blooms — one value springs up on springs.snap (down on springs.glide) and brightens the band and widens its glow from a single CSS property. The band is a slider: arrows move it fifteen minutes, brackets change its length, Enter picks and S suggests.
Design sync
Tue 20 Oct · 14:00–14:45 Lisbon
Left and Right move the meeting fifteen minutes, Shift with them an hour. The bracket keys change its length. Enter picks the time, S suggests the next time everyone is free.
14:00–14:45 lisbon · 4 of 5 free
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/zone-overlap
Props
- bandStyle
- "glass" | "solid" | "outline"
- default "glass"
- How the meeting band is drawn: a translucent wash, a pigment fill, or a dashed outline.
- hourWidth
- number
- default 44
- Pixels per hour of the shared timeline; the drag maps to it.
- tone
- "cool" | "warm" | "mono"
- default "cool"
- The palette: cobalt band and teal overlap, amber band and coral busy blocks, or ink with colour only where everyone is free.
- people
- ZonePerson[]
- default defaultZonePeople
- The team: { id, name, zone, offset (minutes from UTC), hours: [start, end] local hours, busy?: { start, end, title? }[] local minutes }. The first person is "you": the timeline runs on their clock.
- date
- number
- default defaultZoneDate
- Any time on the day shown, ms (its UTC date is the reference day).
- now
- number
- default defaultZoneNow
- The current time, ms, for local clocks and the now-line. Never read from the clock.
- value / defaultValue
- number
- default — / 840
- The band's start, minutes from midnight on the reference clock, controlled or initial.
- onValueChange
- (start: number) => void
- default —
- Fires from the drag, press or key that moved the band.
- duration / defaultDuration
- number
- default — / 45
- The meeting length in minutes, controlled or initial.
- onDurationChange
- (minutes: number) => void
- default —
- Fires from the stepper, bracket key or edge drag that changed the length.
- minDuration
- number
- default 15
- Shortest meeting, minutes.
- maxDuration
- number
- default 240
- Longest meeting, minutes.
- onPick
- (slot: ZoneSlot) => void
- default —
- Pick (or Enter on the band) was pressed: { start, end (ms), duration, free, total, everyoneFree }.
- onSuggest
- (slot: ZoneSlot) => void
- default —
- Suggest (or a Best times entry) moved the band: the next slot where everyone is free, or the best partial one.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the calendars have arrived: loading draws rows as outlines, error a Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the calendars failed to load.
- title
- string
- default "Design sync"
- The meeting's name.
- height
- number
- default 540
- Height of the surface in px; its body scrolls inside.
- disabled
- boolean
- default false
- Nothing can be moved or picked.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| bandStyle | "glass" | "solid" | "outline" | "glass" | How the meeting band is drawn: a translucent wash, a pigment fill, or a dashed outline. |
| hourWidth | number | 44 | Pixels per hour of the shared timeline; the drag maps to it. |
| tone | "cool" | "warm" | "mono" | "cool" | The palette: cobalt band and teal overlap, amber band and coral busy blocks, or ink with colour only where everyone is free. |
| people | ZonePerson[] | defaultZonePeople | The team: { id, name, zone, offset (minutes from UTC), hours: [start, end] local hours, busy?: { start, end, title? }[] local minutes }. The first person is "you": the timeline runs on their clock. |
| date | number | defaultZoneDate | Any time on the day shown, ms (its UTC date is the reference day). |
| now | number | defaultZoneNow | The current time, ms, for local clocks and the now-line. Never read from the clock. |
| value / defaultValue | number | — / 840 | The band's start, minutes from midnight on the reference clock, controlled or initial. |
| onValueChange | (start: number) => void | — | Fires from the drag, press or key that moved the band. |
| duration / defaultDuration | number | — / 45 | The meeting length in minutes, controlled or initial. |
| onDurationChange | (minutes: number) => void | — | Fires from the stepper, bracket key or edge drag that changed the length. |
| minDuration | number | 15 | Shortest meeting, minutes. |
| maxDuration | number | 240 | Longest meeting, minutes. |
| onPick | (slot: ZoneSlot) => void | — | Pick (or Enter on the band) was pressed: { start, end (ms), duration, free, total, everyoneFree }. |
| onSuggest | (slot: ZoneSlot) => void | — | Suggest (or a Best times entry) moved the band: the next slot where everyone is free, or the best partial one. |
| status | "ready" | "loading" | "error" | "ready" | Whether the calendars have arrived: loading draws rows as outlines, error a Try again. |
| onRetry | () => void | — | Try again was pressed after the calendars failed to load. |
| title | string | "Design sync" | The meeting's name. |
| height | number | 540 | Height of the surface in px; its body scrolls inside. |
| disabled | boolean | false | Nothing can be moved or picked. |
| className | string | — | Classes for the outer surface. |