How the planner is looking at the plan - the span in view and the folded lane groups - as one value to keep and hand back.
import { useSchedule } from "@umriss-ui/schedule";
The schedule keeps its view itself and puts it nowhere, not in the address and not in any storage. view on what useSchedule hands back holds it, and onViewChange reports every change the planner makes to it - a pan or zoom at most once per frame; keep it where the application keeps things and go back to it with setView.
initialView is the start, read once: a different value on a later render is ignored. setView(view) goes to a whole view at once, and what it leaves out goes back to its default; the same view again changes nothing, and onViewChange does not hear it. Without a span the schedule shows the extent of its subtasks, within zoomLimits. A group no LaneGroup declares falls out.
setDomain, toggleGroup, foldAll and unfoldAll change the view from outside - a button of the application's own that shows the whole plan or folds every team. The selection is not part of the view (selectedTask).
onViewChange reports the span and the folded groups as one view; keep it - here in the browser's storage, so it outlives a reload - and go back to it with setView. Pan, zoom, fold, keep, reload, restore.
import { useState } from "react";
import { Button, Stack, Text } from "@umriss-ui/core";
import { useSchedule } from "@umriss-ui/schedule";
import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const min = (n: number) => n * 60_000;
const START: ScheduleView = { domain: [at(6), at(16)] };
const KEPT = "umriss-schedule-demo-view";
/* Storage can be missing or refuse - a private window, a blocked site. The
view is then simply not kept. */
function readKept(): ScheduleView | null {
try {
const stored = localStorage.getItem(KEPT);
return stored === null ? null : (JSON.parse(stored) as ScheduleView);
} catch {
return null;
}
}
function keep(view: ScheduleView): void {
try {
localStorage.setItem(KEPT, JSON.stringify(view));
} catch {
/* Not kept; the schedule works on regardless. */
}
}
const TEAMS: Task[] = [
{ id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
{ id: "billing", name: "Billing run", color: "light-dark(#0d9488, #3cc7b8)" },
];
const WORK: Subtask[] = [
{ id: "p1", task: "portal", lane: "noah", from: at(7), to: at(9, 30), leadOut: min(15) },
{ id: "p2", task: "portal", lane: "arjun", from: at(10), to: at(13), leadIn: min(30) },
{ id: "b1", task: "billing", lane: "mei", from: at(8), to: at(11) },
{ id: "b2", task: "billing", lane: "lukas", from: at(11, 30), to: at(14, 30), leadIn: min(15) },
];
export default function KeepAndRestore() {
/* The schedule holds its view; `view` is what it shows now, and `setView`
restores a kept one. */
const [kept, setKept] = useState<ScheduleView | null>(readKept);
const { Schedule, Lane, LaneGroup, Subtasks, view, setView, setDomain } = useSchedule({ initialView: START });
return (
<Stack gap={3}>
<Schedule ariaLabel="Two teams on Tuesday, 17 March" height={290}>
<LaneGroup id="product" label="Product">
<Lane id="noah" label="Noah Fischer" />
<Lane id="arjun" label="Arjun Mehta" />
</LaneGroup>
<LaneGroup id="finance" label="Finance">
<Lane id="mei" label="Mei Tanaka" />
<Lane id="lukas" label="Lukas Brandt" />
</LaneGroup>
<Subtasks data={WORK} tasks={TEAMS} />
</Schedule>
<Stack gap={1}>
<Text as="span" size="sm" tone="secondary">
What the application would keep
</Text>
{/* JSON has no space to break at: without this it ran past a phone's card. */}
<Text as="code" size="sm" mono data-role="view" style={{ overflowWrap: "anywhere" }}>
{JSON.stringify(view)}
</Text>
</Stack>
<Stack direction="row" gap={2} wrap>
<Button
size="sm"
onClick={() => {
keep(view);
setKept(view);
}}
data-keep
>
Keep this view
</Button>
<Button size="sm" variant="ghost" disabled={kept === null} onClick={() => kept && setView(kept)} data-restore>
Restore the kept view
</Button>
<Button size="sm" variant="ghost" onClick={() => setDomain(null)} data-whole-plan>
Show the whole plan
</Button>
</Stack>
</Stack>
);
}
The keys of First schedule apply here.
ScheduleOptions| 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.Shown in: Keep and restore a view, The smallest schedule (Installation), A day of tours (First schedule) and 78 more |
| Name | Type | Default | Description |
|---|---|---|---|
onViewChange | (view: ScheduleView) => void | — | Every change the planner makes, once, always the whole view - the one to
keep, or to hand another schedule's setView. A pan or zoom is reported
at most once per frame. Not called for the view the schedule starts
with, nor for one setView applies.Shown in: Keep two schedules in step (Linked schedules), Read a rota beside the incidents (Linked schedules) |
ScheduleView| Name | Type | Default | Description |
|---|---|---|---|
domain | readonly [number, number] | — | The span in view, as two wall-clock instants. Absent, the schedule shows
the extent of its subtasks, within zoomLimits.Shown in: Keep and restore a view, The smallest schedule (Installation), A day of tours (First schedule) and 77 more |
folded | readonly string[] | — | The Lane groups that are folded, by id. An id no group carries falls
out. A folded outer group hides the inner ones without their ids leaving
the list, so unfolding it gives back the view that was there. Shown in: Fold groups from outside (Lane groups), Read a folded group (Lane groups), Start with groups folded (Lane groups) |