Moving through a plan with the gestures of any scrolling surface: drag, wheel, Ctrl with the wheel, and a pinch. Only the view moves; the plan stays as it is.
import { useSchedule } from "@umriss-ui/schedule";
Dragging the background pans through time and through the lanes. The wheel scrolls the lanes and hands the scroll back to the page at their end; Shift with the wheel pans through time; Ctrl or ⌘ with the wheel, or a pinch, zooms around the pointer.
No intent is raised: pan and zoom change the visible span and nothing else (ADR-0001). The span is part of the schedule's view, which onViewChange reports at most once per frame - see View and Linked schedules.
Drag, wheel, Ctrl with the wheel and a pinch need nothing switched on; zoomLimits only bounds the span, here two hours to two days.
import { useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";
/* Without `zoomLimits` the span may run from an hour to twenty-eight days. */
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const min = (n: number) => n * 60_000;
const HOUR = 60 * 60_000;
const TASKS: Task[] = [
{ id: "release", name: "Release 4.12", color: "light-dark(#2563eb, #6b9bff)" },
{ id: "inc-1048", name: "INC-1048 follow-up", color: "light-dark(#c2410c, #f08a52)" },
];
const ENGINEERS = [
{ id: "priya", label: "Priya Raman" },
{ id: "jonas", label: "Jonas Keller" },
{ id: "ada", label: "Ada Mwangi" },
];
const WORK: Subtask[] = [
{ id: "build", task: "release", lane: "priya", from: at(6), to: at(7), leadIn: min(15), leadOut: min(10) },
{ id: "rollout", task: "release", lane: "jonas", from: at(8), to: at(10, 30), leadIn: min(30), leadOut: min(15) },
{ id: "timeline", task: "inc-1048", lane: "ada", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
{ id: "postmortem", task: "inc-1048", lane: "jonas", from: at(12), to: at(13, 30), leadIn: min(15) },
];
export default function PanAndZoom() {
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
return (
<Schedule
ariaLabel="A plan to pan and zoom"
height={196}
zoomLimits={{ min: 2 * HOUR, max: 48 * HOUR }}
>
{ENGINEERS.map((engineer) => (
<Lane key={engineer.id} id={engineer.id} label={engineer.label} />
))}
<Subtasks data={WORK} tasks={TASKS} />
</Schedule>
);
}
| Key | Action |
|---|---|
Home / End |
The first or last subtask on the lane; the view pans to bring it in. |
PageUp / PageDown |
Along the lane by about a tenth of the visible span, the view following. |
The keys of First schedule apply here.
From ScheduleProps — the full table stands on First schedule.
| Name | Type | Default | Description |
|---|---|---|---|
height | number | 400 | Height in CSS pixels, both bands included. The width is the host's. |
zoomLimits | ZoomLimits | one hour to 28 days | The narrowest and widest time span zoom may reach, in milliseconds. |
children | ReactNode | — | Lane, Dependencies and Subtasks, in the order they are to stand and
be drawn. |
| Name | Type | Default | Description |
|---|---|---|---|
ariaLabelrequired | string | — | What the schedule shows, for a screen reader: "Plan of week 38". |
From LaneProps — the full table stands on Lanes.
From SubtasksProps — the full table stands on Subtasks.
From Subtask — the full table stands on Subtasks.
| Name | Type | Default | Description |
|---|---|---|---|
idrequired | string | — | The caller's identity of the subtask. |
taskrequired | string | — | The id of the task it belongs to. |
lanerequired | string | — | The id of the lane it sits on. |
fromrequired | number | — | Start of the main time. |
torequired | number | — | End of the main time. |
leadIn | number | — | Duration of the lead-in before the main time; none is zero. |
leadOut | number | — | Duration of the lead-out after the main time; none is zero. |
From Task — the full table stands on Subtasks.
From ScheduleOptions — the full table stands on View.
| Name | Type | Default | Description |
|---|---|---|---|
initialView | ScheduleView | — | The view to start from, read once when the hook first runs; a different
value on a later render is ignored, as React ignores a changed
defaultValue. To go to a view later, call setView. The schedule
remembers none: where a view is kept is the application's decision. |