Change how long work takes, or how long its preparation and its wrap-up take, by dragging an edge (also called resizing). Each part changes on its own and is reported for your application to apply.
import { useSchedule, applyIntent } from "@umriss-ui/schedule";
stretch enables the two edges of the main time. leadIn and leadOut add a grip at the outer edge of each; the grips show on the selected subtask only, so click a bar first.
Stretching the main time leaves the lead-in and the lead-out as long as they were.
With stretch in intents, either edge of a bar drags to a new start or end; the other edge stays put.
import { useState } from "react";
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
import type { Subtask, Task } from "@umriss-ui/schedule";
const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
const PROJECTS: readonly Task[] = [{ id: "booking", name: "Booking app", color: "light-dark(#0d9488, #3cc7b8)" }];
const START: readonly Subtask[] = [
{ id: "w-111", task: "booking", lane: "hana", from: day(16), to: day(18, 17), name: "Push notifications" },
{ id: "w-116", task: "booking", lane: "david", from: day(16), to: day(19, 13), name: "Device test matrix" },
];
export default function StretchTheMainTime() {
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
const [work, setWork] = useState(START);
return (
<Schedule
ariaLabel="Two estimates to adjust"
height={140}
intents={["stretch"]}
onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
>
<Lane id="hana" label="Hana Sato" />
<Lane id="david" label="David Kowalski" />
<Subtasks data={work} tasks={PROJECTS} />
</Schedule>
);
}
Add leadIn and leadOut: a selected tour shows a grip for its loading and its unloading, each changed on its own.
import { useState } from "react";
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
import type { 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 TOURS: Task[] = [{ id: "north", name: "North depot tours", color: "light-dark(#0d9488, #3cc7b8)" }];
/* Lead-in is loading at the depot, lead-out unloading the returns. */
const START: Subtask[] = [
{ id: "T-01", task: "north", lane: "van-214", from: at(8), to: at(10), leadIn: min(30), leadOut: min(15) },
{ id: "T-02", task: "north", lane: "van-377", from: at(11), to: at(12, 30), leadIn: min(15) },
];
export default function ChangeLeadInAndLeadOut() {
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(13, 30)] } });
const [tours, setTours] = useState(START);
return (
<Schedule
ariaLabel="Two tours from North depot"
height={150}
intents={["stretch", "leadIn", "leadOut"]}
onIntent={(intent) => setTours((current) => current.map((tour) => applyIntent(tour, intent)))}
>
<Lane id="van-214" label="Van FP 214 K" />
<Lane id="van-377" label="E-van FP 377 K" />
<Subtasks data={tours} tasks={TOURS} />
</Schedule>
);
}
| Key | Action |
|---|---|
Alt+Shift+← Alt+Shift+→ |
Proposes ending the active subtask one raster step earlier or later. |
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. |
intents | readonly IntentKind[] | [] | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule (ADR-0023). |
children | ReactNode | — | Lane, Dependencies and Subtasks, in the order they are to stand and
be drawn. |
| Name | Type | Default | Description |
|---|---|---|---|
onIntent | (intent: Intent) => void | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
| 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. |
name | string | — | What the subtask is called, where the schedule names it. |
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. |
What umriss deliberately does not build, and why: ADR-0032.