@umriss-ui/schedule 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +17 -8
- package/dist/index.d.ts +64 -37
- package/dist/schedule.js +1541 -1476
- package/docs/llms-full.md +684 -334
- package/package.json +6 -6
package/docs/llms-full.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @umriss-ui/schedule 0.
|
|
1
|
+
# @umriss-ui/schedule 0.5.0
|
|
2
2
|
|
|
3
3
|
> React Gantt-style schedule for data-dense applications - work on lanes over time, with dependencies, blocked time, findings and controlled editing.
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ Built from: First schedule, Blocked time, Now line, Move and lane, Findings as d
|
|
|
22
22
|
```tsx
|
|
23
23
|
import { useState } from "react";
|
|
24
24
|
import { Alert, Badge, Stack, Text } from "@umriss-ui/core";
|
|
25
|
-
import {
|
|
25
|
+
import { applyIntent, findings, useSchedule } from "@umriss-ui/schedule";
|
|
26
26
|
import type { BlockedTime, Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
27
27
|
|
|
28
28
|
/* Data from the operations world, written out here so the example runs on its own. */
|
|
@@ -108,6 +108,7 @@ const when = (instant: number) =>
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
export default function HandOver() {
|
|
111
|
+
const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(16, 6), day(23, 12)] } });
|
|
111
112
|
const [rota, setRota] = useState<readonly Subtask[]>(START);
|
|
112
113
|
|
|
113
114
|
const onIntent = (intent: Intent) => {
|
|
@@ -133,7 +134,6 @@ export default function HandOver() {
|
|
|
133
134
|
<div data-callout="2">
|
|
134
135
|
<Schedule
|
|
135
136
|
ariaLabel="On-call rota, 16 to 23 March"
|
|
136
|
-
initialDomain={[day(16, 6), day(23, 12)]}
|
|
137
137
|
height={420}
|
|
138
138
|
now={NOW}
|
|
139
139
|
intents={["lane"]}
|
|
@@ -195,7 +195,7 @@ Built from: First schedule, Lane groups, Dependencies, Ripple, Badge, Button.
|
|
|
195
195
|
```tsx
|
|
196
196
|
import { useState } from "react";
|
|
197
197
|
import { Badge, Button, Stack, Text } from "@umriss-ui/core";
|
|
198
|
-
import {
|
|
198
|
+
import { applyIntent, ripple, shiftTask, useSchedule } from "@umriss-ui/schedule";
|
|
199
199
|
import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
200
200
|
|
|
201
201
|
/* Data from the logistics world, written out here so the example runs on its own. */
|
|
@@ -391,6 +391,7 @@ const plateOf = (vehicle: string) => VEHICLES.find((one) => one.id === vehicle)?
|
|
|
391
391
|
const driverOf = (driver: string) => DRIVERS.find((one) => one.id === driver)?.name ?? driver;
|
|
392
392
|
|
|
393
393
|
export default function TourPlan() {
|
|
394
|
+
const { Schedule, Lane, LaneGroup, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [tomorrow(5, 30), tomorrow(15)] } });
|
|
394
395
|
const [plan, setPlan] = useState<readonly Subtask[]>(START);
|
|
395
396
|
|
|
396
397
|
const apply = (intent: Intent) =>
|
|
@@ -418,7 +419,6 @@ export default function TourPlan() {
|
|
|
418
419
|
<div data-callout="1">
|
|
419
420
|
<Schedule
|
|
420
421
|
ariaLabel="Tours of Wednesday, 18 March"
|
|
421
|
-
initialDomain={[tomorrow(5, 30), tomorrow(15)]}
|
|
422
422
|
height={470}
|
|
423
423
|
intents={["move"]}
|
|
424
424
|
onIntent={apply}
|
|
@@ -479,7 +479,7 @@ Built from: First schedule, Lane groups, Blocked time, Time axis and calendar, W
|
|
|
479
479
|
```tsx
|
|
480
480
|
import { useState } from "react";
|
|
481
481
|
import { Alert, Badge, Stack, Text } from "@umriss-ui/core";
|
|
482
|
-
import {
|
|
482
|
+
import { applyIntent, findings, useSchedule } from "@umriss-ui/schedule";
|
|
483
483
|
import type { BlockedTime, Intent, Subtask } from "@umriss-ui/schedule";
|
|
484
484
|
|
|
485
485
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
@@ -631,6 +631,7 @@ const teamOf = (person: string) => PEOPLE.find((one) => one.id === person)?.team
|
|
|
631
631
|
const nameOf = (person: string) => PEOPLE.find((one) => one.id === person)?.name ?? person;
|
|
632
632
|
|
|
633
633
|
export default function SprintAroundLeave() {
|
|
634
|
+
const { Schedule, Lane, LaneGroup, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [SPRINT.from, SPRINT.to] } });
|
|
634
635
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
635
636
|
|
|
636
637
|
const onIntent = (intent: Intent) => {
|
|
@@ -652,7 +653,6 @@ export default function SprintAroundLeave() {
|
|
|
652
653
|
<div data-callout="2">
|
|
653
654
|
<Schedule
|
|
654
655
|
ariaLabel="Sprint 14, work per person"
|
|
655
|
-
initialDomain={[SPRINT.from, SPRINT.to]}
|
|
656
656
|
calendar={WORKING_DAYS}
|
|
657
657
|
height={570}
|
|
658
658
|
now={NOW}
|
|
@@ -716,7 +716,7 @@ Built from: First schedule, Dependencies, Move and lane, Stretch, lead-in, lead-
|
|
|
716
716
|
```tsx
|
|
717
717
|
import { useState } from "react";
|
|
718
718
|
import { Checkbox, ContextMenu, MenuItem, MenuSeparator, Stack, Text } from "@umriss-ui/core";
|
|
719
|
-
import {
|
|
719
|
+
import { applyIntent, findings, ripple, shiftTask, useSchedule } from "@umriss-ui/schedule";
|
|
720
720
|
import type { Dependency, Intent, ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
|
|
721
721
|
|
|
722
722
|
/* Data from the plant world, written out here so the example runs on its own. */
|
|
@@ -821,6 +821,7 @@ function applied(plan: Plan, intent: Intent, cascade: boolean): Plan {
|
|
|
821
821
|
}
|
|
822
822
|
|
|
823
823
|
export default function ReplanTheDay() {
|
|
824
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY_OF_PLAN } });
|
|
824
825
|
const [plan, setPlan] = useState<Plan>(START);
|
|
825
826
|
const [cascade, setCascade] = useState(true);
|
|
826
827
|
const [menu, setMenu] = useState<Menu | null>(null);
|
|
@@ -844,7 +845,6 @@ export default function ReplanTheDay() {
|
|
|
844
845
|
<div data-callout="2">
|
|
845
846
|
<Schedule
|
|
846
847
|
ariaLabel="Plan of Tuesday, 17 March, in the planner's hands"
|
|
847
|
-
initialDomain={DAY_OF_PLAN}
|
|
848
848
|
height={380}
|
|
849
849
|
intents={["move", "lane", "stretch", "leadIn", "leadOut"]}
|
|
850
850
|
onIntent={apply}
|
|
@@ -906,7 +906,7 @@ What an application installs and hands over before the first plan.
|
|
|
906
906
|
Install the schedule with its two peers and hand it plain data: lanes, tasks, subtasks and dependencies. It draws them and reports what a planner wants to change.
|
|
907
907
|
|
|
908
908
|
```ts
|
|
909
|
-
import {
|
|
909
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
910
910
|
```
|
|
911
911
|
|
|
912
912
|
```sh
|
|
@@ -928,7 +928,7 @@ The schedule reports, the application decides. It draws the data it is given and
|
|
|
928
928
|
Two people, one project, one dependency: lanes in JSX, the rest as plain arrays handed to `Subtasks` and `Dependencies`.
|
|
929
929
|
|
|
930
930
|
```tsx
|
|
931
|
-
import {
|
|
931
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
932
932
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
933
933
|
|
|
934
934
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -944,8 +944,9 @@ const subtasks: Subtask[] = [
|
|
|
944
944
|
const dependencies: Dependency[] = [{ id: "handover", from: "design", to: "build", lag: min(15) }];
|
|
945
945
|
|
|
946
946
|
export default function SmallestSchedule() {
|
|
947
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(8), at(16)] } });
|
|
947
948
|
return (
|
|
948
|
-
<Schedule ariaLabel="Tuesday, design and build"
|
|
949
|
+
<Schedule ariaLabel="Tuesday, design and build" height={150}>
|
|
949
950
|
<Lane id="noah" label="Noah Fischer" />
|
|
950
951
|
<Lane id="arjun" label="Arjun Mehta" />
|
|
951
952
|
<Dependencies data={dependencies} />
|
|
@@ -965,7 +966,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
965
966
|
|
|
966
967
|
| Prop | Type | Default | Description |
|
|
967
968
|
|---|---|---|---|
|
|
968
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
969
969
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
970
970
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
971
971
|
|
|
@@ -1024,12 +1024,24 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
1024
1024
|
| [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
1025
1025
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
1026
1026
|
|
|
1027
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
1028
|
+
|
|
1029
|
+
| Prop | Type | Default | Description |
|
|
1030
|
+
|---|---|---|---|
|
|
1031
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
1032
|
+
|
|
1033
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
1034
|
+
|
|
1035
|
+
| Prop | Type | Default | Description |
|
|
1036
|
+
|---|---|---|---|
|
|
1037
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
1038
|
+
|
|
1027
1039
|
### First schedule
|
|
1028
1040
|
|
|
1029
1041
|
Work on lanes over time, with a day band above and time ticks that follow the zoom (also called a Gantt chart, resource plan or timeline). Reach for it when a planner needs to see who or what is busy when, and what does not fit.
|
|
1030
1042
|
|
|
1031
1043
|
```ts
|
|
1032
|
-
import {
|
|
1044
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1033
1045
|
```
|
|
1034
1046
|
|
|
1035
1047
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/schedule/
|
|
@@ -1047,7 +1059,7 @@ The plot is one tab stop. The keys walk the subtasks as the pointer would hover
|
|
|
1047
1059
|
Vehicles as lanes, tours as tasks: each leg is a subtask with loading as its lead-in, each handover a dependency with a transfer time.
|
|
1048
1060
|
|
|
1049
1061
|
```tsx
|
|
1050
|
-
import {
|
|
1062
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1051
1063
|
import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
|
|
1052
1064
|
|
|
1053
1065
|
/* Two findings stand in this plan on purpose: two tours claim van FP 214 K at
|
|
@@ -1091,8 +1103,9 @@ const HANDOVERS: readonly Dependency[] = [
|
|
|
1091
1103
|
];
|
|
1092
1104
|
|
|
1093
1105
|
export default function FirstSchedule() {
|
|
1106
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY } });
|
|
1094
1107
|
return (
|
|
1095
|
-
<Schedule ariaLabel="Tours of Tuesday, 17 March"
|
|
1108
|
+
<Schedule ariaLabel="Tours of Tuesday, 17 March" height={240}>
|
|
1096
1109
|
{VEHICLES.map((vehicle) => (
|
|
1097
1110
|
<Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
1098
1111
|
))}
|
|
@@ -1108,7 +1121,7 @@ export default function FirstSchedule() {
|
|
|
1108
1121
|
With nothing planned yet, `Subtasks` gets an empty array: the lanes and the time axis still stand, ready for the first drop.
|
|
1109
1122
|
|
|
1110
1123
|
```tsx
|
|
1111
|
-
import {
|
|
1124
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1112
1125
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1113
1126
|
|
|
1114
1127
|
const at = (day: number, hours: number) => new Date(2026, 2, day, hours).getTime();
|
|
@@ -1123,8 +1136,9 @@ const PEOPLE = [
|
|
|
1123
1136
|
];
|
|
1124
1137
|
|
|
1125
1138
|
export default function NoWorkYet() {
|
|
1139
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(23, 8), at(27, 18)] } });
|
|
1126
1140
|
return (
|
|
1127
|
-
<Schedule ariaLabel="Sprint 15, nothing planned yet"
|
|
1141
|
+
<Schedule ariaLabel="Sprint 15, nothing planned yet" height={170}>
|
|
1128
1142
|
{PEOPLE.map((person) => (
|
|
1129
1143
|
<Lane key={person.id} id={person.id} label={person.label} />
|
|
1130
1144
|
))}
|
|
@@ -1139,7 +1153,7 @@ export default function NoWorkYet() {
|
|
|
1139
1153
|
The full case: seven stations, six orders and their transports, the present marked and every order named in its bars.
|
|
1140
1154
|
|
|
1141
1155
|
```tsx
|
|
1142
|
-
import {
|
|
1156
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1143
1157
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
1144
1158
|
|
|
1145
1159
|
/* Data from the plant world, written out here so the example runs on its own. */
|
|
@@ -1216,10 +1230,10 @@ const NAMES = new Map(ORDERS.map((order) => [order.id, order.name ?? order.id]))
|
|
|
1216
1230
|
const HALF_PAST_TEN = new Date(2026, 2, 17, 10, 30).getTime();
|
|
1217
1231
|
|
|
1218
1232
|
export default function MachinePlan() {
|
|
1233
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY_OF_PLAN } });
|
|
1219
1234
|
return (
|
|
1220
1235
|
<Schedule
|
|
1221
1236
|
ariaLabel="Machine shop, Tuesday 17 March"
|
|
1222
|
-
initialDomain={DAY_OF_PLAN}
|
|
1223
1237
|
height={380}
|
|
1224
1238
|
now={HALF_PAST_TEN}
|
|
1225
1239
|
label={(step) => NAMES.get(step.task) ?? step.task}
|
|
@@ -1264,8 +1278,7 @@ Under forced colours the plot paints itself in the system colours: the work in t
|
|
|
1264
1278
|
|
|
1265
1279
|
| Prop | Type | Default | Description |
|
|
1266
1280
|
|---|---|---|---|
|
|
1267
|
-
| `
|
|
1268
|
-
| `height` | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 74 more |
|
|
1281
|
+
| `height` | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 75 more |
|
|
1269
1282
|
| `laneHeight` | `number` | `44` | Height of one lane in pixels.<br>Shown in: [Scroll more lanes than fit (Lanes)](#/lane/many-lanes) |
|
|
1270
1283
|
| `headerWidth` | `number` | `160` | Width of the lane headers in pixels - at most 40 % of the schedule, so that on a phone the plot keeps the larger part.<br>Shown in: [What a lane header says (Lanes)](#/lane/lane-headers), [Scroll more lanes than fit (Lanes)](#/lane/many-lanes), [Group lanes under a head (Lane groups)](#/lane-groups/a-group) and 1 more |
|
|
1271
1284
|
| `calendar` | [`CalendarInput`](#type-CalendarInput) | `[]` | A working calendar: the intervals in which time counts. Nights and weekends outside them are cut out of the axis. Without intervals every hour counts, as on the wall clock.<br>Shown in: [Cut the nights out of the axis (Time axis and calendar)](#/time-axis/working-calendar), [Plan in office hours only (Time axis and calendar)](#/time-axis/office-hours), [Plan the sprint around leave (Scenarios)](#/scenarios/plan-the-sprint-around-leave) |
|
|
@@ -1275,9 +1288,6 @@ Under forced colours the plot paints itself in the system colours: the work in t
|
|
|
1275
1288
|
| `canMoveTo` | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`, lane: string) => boolean` | every lane is open | Whether a subtask may go to a lane. It narrows `"lane"`, it does not enable it. Asked once per lane when a drag takes hold, and asked again at the drop. The lanes it turns down are marked from the first frame of the drag - a planner sees the refusal before meeting it - and over one of them the ghost stays on the last lane that was allowed, the cursor says no and a line ties the ghost to the pointer. A refused lane costs the lane and nothing else: a drop after it still reports the move in time. It is asked for work dragged in from outside as well, with the key and task the application declared in `placing`.<br>Shown in: [Keep work on the lanes that fit (Where a subtask may go)](#/where-it-may-go/lanes-that-fit), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in), [Keep work out of someone's leave (Where a subtask may go)](#/where-it-may-go/around-leave) and 1 more |
|
|
1276
1289
|
| `selectedTask` | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself.<br>Shown in: [Control the selection (Selection)](#/selection/control-the-selection) |
|
|
1277
1290
|
| `onSelectedTaskChange` | `(task: string \| null, subtask: string \| null) => void` | — | Called when a click selects a task or clears the selection, with the subtask that was clicked - null where the click was on a dependency or on nothing. It is called again when another subtask of the same task is clicked.<br>Shown in: [Control the selection (Selection)](#/selection/control-the-selection) |
|
|
1278
|
-
| `defaultCollapsedGroups` | `readonly string[]` | — | Which groups are folded when the schedule mounts, where the schedule keeps the state itself. Ignored while `collapsedGroups` is given.<br>Shown in: [Start with groups folded (Lane groups)](#/lane-groups/start-with-groups-folded) |
|
|
1279
|
-
| `collapsedGroups` | `readonly string[]` | — | The **Lane group**s that are folded, controlled. Leave it out and the schedule keeps them itself, starting from `defaultCollapsedGroups`. Folding changes the view and not the plan, which is why it is no intent: a caller that applies every intent it receives must never find a fold among them ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)). A folded outer group hides the inner ones without their entries leaving the list, so unfolding it gives back the view that was there.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/controlled), [Read a folded group (Lane groups)](#/lane-groups/the-miniature) |
|
|
1280
|
-
| `onCollapsedGroupsChange` | `(groups: readonly string[]) => void` | — | Called with the whole list when the planner folds or unfolds a group.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/controlled), [Read a folded group (Lane groups)](#/lane-groups/the-miniature) |
|
|
1281
1291
|
| `placing` | [`PlacingItem`](#type-PlacingItem)` \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#/placing/drag-from-the-backlog), [Apply the same rule to work dragged in (Where a subtask may go)](#/where-it-may-go/dragged-in) |
|
|
1282
1292
|
| `route` | [`DependencyRoute`](#type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | `"curve"` | How the dependencies are drawn: a `"curve"` that leaves and arrives forwards, a `"straight"` line, or `"orthogonal"` segments. A dependency may say otherwise for itself.<br>Shown in: [Straight (Routes)](#/routes/straight), [Orthogonal (Routes)](#/routes/orthogonal), [Per dependency (Routes)](#/routes/per-dependency) |
|
|
1283
1293
|
| `attach` | [`DependencyAttachment`](#type-DependencyAttachment)<br>`"centre" \| "nearest"` | `"centre"` | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone.<br>Shown in: [Choose where a line touches its bars (Dependencies)](#/dependencies/attach), [Mark the ends with a dot (Dependencies)](#/dependencies/ends), [Draw one handover its own way (Dependencies)](#/dependencies/one-handover-drawn-its-own-way) and 3 more |
|
|
@@ -1285,7 +1295,7 @@ Under forced colours the plot paints itself in the system colours: the work in t
|
|
|
1285
1295
|
| `label` | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge.<br>Shown in: [Plan a day in the machine shop](#/schedule/machine-plan), [Plan a sprint for a team (Subtasks)](#/subtasks/a-sprint), [Write into the bars (Bar labels)](#/bar-labels/bar-labels) and 7 more |
|
|
1286
1296
|
| `tooltip` | `false \| ((target: `[`ScheduleTooltipTarget`](#/tooltip/type-ScheduleTooltipTarget)`) => ReactNode)` | — | The tooltip on a hovered subtask or dependency: its order, its times, its parts and its findings. `false` switches it off; a function receives what the pointer rests on and returns content of the application's own.<br>Shown in: [Write your own tooltip content (Tooltip)](#/tooltip/own-tooltip), [Show a detail panel instead (Tooltip)](#/tooltip/detail-panel-instead), [Say what a bar covers and a line joins (Tooltip)](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
|
|
1287
1297
|
| `now` | `boolean \| number` | `false` | A line marking the present across the lanes: `true` follows the clock by the minute, an instant fixes it there.<br>Shown in: [Plan a day in the machine shop](#/schedule/machine-plan), [Mark the present (Now line)](#/now-line/now-line), [Follow the clock (Now line)](#/now-line/follow-the-clock) and 2 more |
|
|
1288
|
-
| `children` | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and
|
|
1298
|
+
| `children` | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn.<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 75 more |
|
|
1289
1299
|
|
|
1290
1300
|
###### Events
|
|
1291
1301
|
|
|
@@ -1293,13 +1303,12 @@ Under forced colours the plot paints itself in the system colours: the work in t
|
|
|
1293
1303
|
|---|---|---|---|
|
|
1294
1304
|
| `onIntent` | `(intent: `[`Intent`](#type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it.<br>Shown in: [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Cut the nights out of the axis (Time axis and calendar)](#/time-axis/working-calendar), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around) and 18 more |
|
|
1295
1305
|
| `onInteraction` | `(interaction: `[`ScheduleInteraction`](#/interactions/type-ScheduleInteraction)`) => void` | — | Click, context menu and hover, with what the pointer is on and where. While it is set, a right-click opens no browser menu.<br>Shown in: [Report what the pointer is on (Interactions)](#/interactions/report-the-pointer), [Open a context menu (Interactions)](#/interactions/open-a-context-menu), [Show a detail panel instead (Tooltip)](#/tooltip/detail-panel-instead) and 2 more |
|
|
1296
|
-
| `onDomainChange` | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back.<br>Shown in: [Keep two schedules in step (Linked schedules)](#/linked-schedules/in-step), [Read a rota beside the incidents (Linked schedules)](#/linked-schedules/rota-beside-incidents), [Place a mark of your own (The handle)](#/handle/place-a-mark) |
|
|
1297
1306
|
|
|
1298
1307
|
###### Accessibility
|
|
1299
1308
|
|
|
1300
1309
|
| Prop | Type | Default | Description |
|
|
1301
1310
|
|---|---|---|---|
|
|
1302
|
-
| `ariaLabel` *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38".<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and
|
|
1311
|
+
| `ariaLabel` *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38".<br>Shown in: [A day of tours](#/schedule/first-schedule), [Start from an empty plan](#/schedule/no-work-yet), [Plan a day in the machine shop](#/schedule/machine-plan) and 75 more |
|
|
1303
1312
|
|
|
1304
1313
|
###### Styling
|
|
1305
1314
|
|
|
@@ -1521,7 +1530,7 @@ What the schedule draws: work on lanes, and everything a bar says besides its co
|
|
|
1521
1530
|
One row per person, vehicle or room, named by its header and kept in the order it is declared, however many there are (also called resources).
|
|
1522
1531
|
|
|
1523
1532
|
```ts
|
|
1524
|
-
import {
|
|
1533
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1525
1534
|
```
|
|
1526
1535
|
|
|
1527
1536
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/lane/
|
|
@@ -1538,7 +1547,7 @@ A `label` is content, not a string: here the vehicle with its plate, and a badge
|
|
|
1538
1547
|
|
|
1539
1548
|
```tsx
|
|
1540
1549
|
import { Badge, Stack, Text } from "@umriss-ui/core";
|
|
1541
|
-
import {
|
|
1550
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1542
1551
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1543
1552
|
|
|
1544
1553
|
const at = (hours: number) => new Date(2026, 2, 17, hours).getTime();
|
|
@@ -1551,8 +1560,9 @@ const LEGS: Subtask[] = [
|
|
|
1551
1560
|
];
|
|
1552
1561
|
|
|
1553
1562
|
export default function LaneHeaders() {
|
|
1563
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(12)] } });
|
|
1554
1564
|
return (
|
|
1555
|
-
<Schedule ariaLabel="Two vans of the North depot"
|
|
1565
|
+
<Schedule ariaLabel="Two vans of the North depot" height={150} headerWidth={210}>
|
|
1556
1566
|
<Lane
|
|
1557
1567
|
id="v1"
|
|
1558
1568
|
label={
|
|
@@ -1587,7 +1597,7 @@ export default function LaneHeaders() {
|
|
|
1587
1597
|
Twenty vans in 300 pixels: the lanes scroll with their headers, while the time bands and the header column hold still.
|
|
1588
1598
|
|
|
1589
1599
|
```tsx
|
|
1590
|
-
import {
|
|
1600
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1591
1601
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1592
1602
|
|
|
1593
1603
|
/* `height` is the whole schedule, both bands included; `laneHeight` and
|
|
@@ -1613,10 +1623,10 @@ const ROUNDS: Subtask[] = VANS.map((van, i) => ({
|
|
|
1613
1623
|
}));
|
|
1614
1624
|
|
|
1615
1625
|
export default function ManyLanes() {
|
|
1626
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(14)] } });
|
|
1616
1627
|
return (
|
|
1617
1628
|
<Schedule
|
|
1618
1629
|
ariaLabel="Twenty vans, Tuesday morning"
|
|
1619
|
-
initialDomain={[at(6), at(14)]}
|
|
1620
1630
|
height={300}
|
|
1621
1631
|
laneHeight={32}
|
|
1622
1632
|
headerWidth={110}
|
|
@@ -1644,15 +1654,15 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
|
1644
1654
|
|
|
1645
1655
|
| Prop | Type | Default | Description |
|
|
1646
1656
|
|---|---|---|---|
|
|
1647
|
-
| `id` *required* | `string` | — | The identity subtasks name in their `lane`.<br>Shown in: [What a lane header says](#/lane/lane-headers), [Scroll more lanes than fit](#/lane/many-lanes), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
1648
|
-
| `label` | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader.<br>Shown in: [What a lane header says](#/lane/lane-headers), [Scroll more lanes than fit](#/lane/many-lanes), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
1657
|
+
| `id` *required* | `string` | — | The identity subtasks name in their `lane`.<br>Shown in: [What a lane header says](#/lane/lane-headers), [Scroll more lanes than fit](#/lane/many-lanes), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 75 more |
|
|
1658
|
+
| `label` | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader.<br>Shown in: [What a lane header says](#/lane/lane-headers), [Scroll more lanes than fit](#/lane/many-lanes), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 75 more |
|
|
1649
1659
|
|
|
1650
1660
|
### Lane groups
|
|
1651
1661
|
|
|
1652
1662
|
Teams, depots or regions over the lanes, to any depth, folded into one row when a planner wants only the part they work on.
|
|
1653
1663
|
|
|
1654
1664
|
```ts
|
|
1655
|
-
import {
|
|
1665
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1656
1666
|
```
|
|
1657
1667
|
|
|
1658
1668
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/lane-groups/
|
|
@@ -1661,7 +1671,7 @@ A group is structure, never a lane: nothing sits on it, no finding is reported f
|
|
|
1661
1671
|
|
|
1662
1672
|
Folded, a group shows a miniature: every lane in it as a thin strip with its work in the tasks' colours. Dependencies still arrive at the right strip, findings are marked on the row, and a strip can be hovered and selected. Labels, appearances and grips need room and wait for the group to open.
|
|
1663
1673
|
|
|
1664
|
-
Folding is
|
|
1674
|
+
Folding is part of the schedule's view and never an intent: `folded` in `initialView` starts with groups folded, `toggleGroup`, `foldAll` and `unfoldAll` fold from outside, and `onViewChange` reports every fold - see [View](#/view).
|
|
1665
1675
|
|
|
1666
1676
|
#### Examples
|
|
1667
1677
|
|
|
@@ -1670,7 +1680,7 @@ Folding is a view state and never an intent. `collapsedGroups` and `onCollapsedG
|
|
|
1670
1680
|
Wrap lanes in a `LaneGroup`: a slim head names the team and counts its lanes, and its chevron folds it.
|
|
1671
1681
|
|
|
1672
1682
|
```tsx
|
|
1673
|
-
import {
|
|
1683
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1674
1684
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1675
1685
|
|
|
1676
1686
|
/* Declared by composition: a lane never names its group, so moving a person
|
|
@@ -1692,8 +1702,9 @@ const WORK: Subtask[] = [
|
|
|
1692
1702
|
];
|
|
1693
1703
|
|
|
1694
1704
|
export default function AGroup() {
|
|
1705
|
+
const { Schedule, Lane, LaneGroup, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(15)] } });
|
|
1695
1706
|
return (
|
|
1696
|
-
<Schedule ariaLabel="Two developers in a team, a designer and a tester outside it"
|
|
1707
|
+
<Schedule ariaLabel="Two developers in a team, a designer and a tester outside it" height={240} headerWidth={200}>
|
|
1697
1708
|
<LaneGroup id="developers" label="Developers">
|
|
1698
1709
|
<Lane id="arjun" label="Arjun Mehta" />
|
|
1699
1710
|
<Lane id="chloe" label="Chloe Durand" />
|
|
@@ -1711,7 +1722,7 @@ export default function AGroup() {
|
|
|
1711
1722
|
A depot holds a fleet of vans and a truck; an inner group keeps its fold while the outer one closes.
|
|
1712
1723
|
|
|
1713
1724
|
```tsx
|
|
1714
|
-
import {
|
|
1725
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1715
1726
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1716
1727
|
|
|
1717
1728
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -1734,8 +1745,9 @@ const LEGS: Subtask[] = [
|
|
|
1734
1745
|
];
|
|
1735
1746
|
|
|
1736
1747
|
export default function Nesting() {
|
|
1748
|
+
const { Schedule, Lane, LaneGroup, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(15)] } });
|
|
1737
1749
|
return (
|
|
1738
|
-
<Schedule ariaLabel="North depot with its vans and a truck, and a van from Riverside"
|
|
1750
|
+
<Schedule ariaLabel="North depot with its vans and a truck, and a van from Riverside" height={330} headerWidth={200}>
|
|
1739
1751
|
<LaneGroup id="north" label="North depot">
|
|
1740
1752
|
<LaneGroup id="vans" label="Vans">
|
|
1741
1753
|
<Lane id="fp-214" label={`Van ${plate("FP 214 K")}`} />
|
|
@@ -1753,17 +1765,15 @@ export default function Nesting() {
|
|
|
1753
1765
|
|
|
1754
1766
|
##### Fold groups from outside
|
|
1755
1767
|
|
|
1756
|
-
|
|
1768
|
+
`foldAll`, `unfoldAll` and `toggleGroup` fold from outside, and `view.folded` says what is folded - start from `initialView`, keep what `onViewChange` reports.
|
|
1757
1769
|
|
|
1758
1770
|
```tsx
|
|
1759
|
-
import { useState } from "react";
|
|
1760
1771
|
import { Button, Stack, Text } from "@umriss-ui/core";
|
|
1761
|
-
import {
|
|
1772
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1762
1773
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1763
1774
|
|
|
1764
|
-
/* The
|
|
1765
|
-
|
|
1766
|
-
the plan. */
|
|
1775
|
+
/* The fold is part of the schedule's view, not of the plan: no intent, and
|
|
1776
|
+
nothing the application has to hold for the chevrons to work. */
|
|
1767
1777
|
|
|
1768
1778
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
1769
1779
|
|
|
@@ -1776,28 +1786,21 @@ const WORK: Subtask[] = [
|
|
|
1776
1786
|
{ id: "s4", task: "release", lane: "leila", from: at(13), to: at(14, 30) },
|
|
1777
1787
|
];
|
|
1778
1788
|
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
const [folded, setFolded] = useState<readonly string[]>(["discovery"]);
|
|
1789
|
+
export default function FoldFromOutside() {
|
|
1790
|
+
const { Schedule, Lane, LaneGroup, Subtasks, view, foldAll, unfoldAll } = useSchedule({ initialView: { domain: [at(6), at(15)], folded: ["discovery"] } });
|
|
1791
|
+
const folded = view.folded ?? [];
|
|
1783
1792
|
|
|
1784
1793
|
return (
|
|
1785
1794
|
<Stack gap={3}>
|
|
1786
1795
|
<Stack direction="row" gap={2}>
|
|
1787
|
-
<Button size="sm" variant="secondary" onClick={
|
|
1796
|
+
<Button size="sm" variant="secondary" onClick={foldAll} data-fold-all>
|
|
1788
1797
|
Fold everything
|
|
1789
1798
|
</Button>
|
|
1790
|
-
<Button size="sm" variant="secondary" onClick={
|
|
1799
|
+
<Button size="sm" variant="secondary" onClick={unfoldAll} data-unfold-all>
|
|
1791
1800
|
Unfold everything
|
|
1792
1801
|
</Button>
|
|
1793
1802
|
</Stack>
|
|
1794
|
-
<Schedule
|
|
1795
|
-
ariaLabel="Two teams rolling out a release, folded from outside"
|
|
1796
|
-
initialDomain={[at(6), at(15)]}
|
|
1797
|
-
height={280}
|
|
1798
|
-
collapsedGroups={folded}
|
|
1799
|
-
onCollapsedGroupsChange={setFolded}
|
|
1800
|
-
>
|
|
1803
|
+
<Schedule ariaLabel="Two teams rolling out a release, folded from outside" height={280}>
|
|
1801
1804
|
<LaneGroup id="payments" label="Payments">
|
|
1802
1805
|
<Lane id="priya" label="Priya Raman" />
|
|
1803
1806
|
<Lane id="jonas" label="Jonas Keller" />
|
|
@@ -1822,12 +1825,12 @@ Folded, the depot shows each van as a thin strip: handovers still arrive, the do
|
|
|
1822
1825
|
|
|
1823
1826
|
```tsx
|
|
1824
1827
|
import { useState } from "react";
|
|
1825
|
-
import {
|
|
1828
|
+
import { applyIntent, findings, useSchedule } from "@umriss-ui/schedule";
|
|
1826
1829
|
import { Stack, Text } from "@umriss-ui/core";
|
|
1827
1830
|
import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
|
|
1828
1831
|
|
|
1829
1832
|
/* A drag held over the folded depot opens it for the gesture and shuts it
|
|
1830
|
-
again when the drag ends; the
|
|
1833
|
+
again when the drag ends; the schedule's view never learns of it.
|
|
1831
1834
|
`findings()` is computed from the data, so the count below is the same
|
|
1832
1835
|
folded or not. */
|
|
1833
1836
|
|
|
@@ -1862,7 +1865,7 @@ const HANDOVERS: Dependency[] = [
|
|
|
1862
1865
|
const FOUND = findings(LEGS, HANDOVERS);
|
|
1863
1866
|
|
|
1864
1867
|
export default function TheMiniature() {
|
|
1865
|
-
const
|
|
1868
|
+
const { Schedule, Lane, LaneGroup, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6), at(16)], folded: ["north"] } });
|
|
1866
1869
|
const [legs, setLegs] = useState<readonly Subtask[]>(LEGS);
|
|
1867
1870
|
const [last, setLast] = useState("Hold a bar over the folded depot");
|
|
1868
1871
|
|
|
@@ -1870,10 +1873,7 @@ export default function TheMiniature() {
|
|
|
1870
1873
|
<Stack gap={3}>
|
|
1871
1874
|
<Schedule
|
|
1872
1875
|
ariaLabel="A depot that folds into one row"
|
|
1873
|
-
initialDomain={[at(6), at(16)]}
|
|
1874
1876
|
height={240}
|
|
1875
|
-
collapsedGroups={folded}
|
|
1876
|
-
onCollapsedGroupsChange={setFolded}
|
|
1877
1877
|
intents={["move", "lane"]}
|
|
1878
1878
|
onIntent={(intent) => {
|
|
1879
1879
|
if (intent.kind === "place") return;
|
|
@@ -1903,14 +1903,15 @@ export default function TheMiniature() {
|
|
|
1903
1903
|
|
|
1904
1904
|
##### Start with groups folded
|
|
1905
1905
|
|
|
1906
|
-
`
|
|
1906
|
+
`folded` in `initialView` folds groups when the schedule mounts and leaves the rest to the planner: Discovery starts folded, and its chevron opens it.
|
|
1907
1907
|
|
|
1908
1908
|
```tsx
|
|
1909
|
-
import {
|
|
1909
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1910
1910
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1911
1911
|
|
|
1912
|
-
/*
|
|
1913
|
-
steer it,
|
|
1912
|
+
/* The schedule keeps the fold from here on. To store it, keep what
|
|
1913
|
+
`onViewChange` reports; to steer it, use the setters `useSchedule` hands
|
|
1914
|
+
back. */
|
|
1914
1915
|
|
|
1915
1916
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
1916
1917
|
|
|
@@ -1924,13 +1925,9 @@ const WORK: Subtask[] = [
|
|
|
1924
1925
|
];
|
|
1925
1926
|
|
|
1926
1927
|
export default function StartWithGroupsFolded() {
|
|
1928
|
+
const { Schedule, Lane, LaneGroup, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(15)], folded: ["discovery"] } });
|
|
1927
1929
|
return (
|
|
1928
|
-
<Schedule
|
|
1929
|
-
ariaLabel="Two teams rolling out a release, Discovery folded at the start"
|
|
1930
|
-
initialDomain={[at(6), at(15)]}
|
|
1931
|
-
height={280}
|
|
1932
|
-
defaultCollapsedGroups={["discovery"]}
|
|
1933
|
-
>
|
|
1930
|
+
<Schedule ariaLabel="Two teams rolling out a release, Discovery folded at the start" height={280}>
|
|
1934
1931
|
<LaneGroup id="payments" label="Payments">
|
|
1935
1932
|
<Lane id="priya" label="Priya Raman" />
|
|
1936
1933
|
<Lane id="jonas" label="Jonas Keller" />
|
|
@@ -1960,9 +1957,9 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
|
1960
1957
|
|
|
1961
1958
|
| Prop | Type | Default | Description |
|
|
1962
1959
|
|---|---|---|---|
|
|
1963
|
-
| `id` *required* | `string` | — | The group's identity - what `
|
|
1964
|
-
| `label` | `ReactNode` | `id` | What the group's header says - the team, the depot, the region. Real text, read by a screen reader.<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/
|
|
1965
|
-
| `children` | `ReactNode` | — | `Lane`s and further `LaneGroup`s, to any depth.<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/
|
|
1960
|
+
| `id` *required* | `string` | — | The group's identity - what the view's `folded` names. It is never a lane: no subtask sits on it, no finding is reported for it, and no intent names it ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)).<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/fold-from-outside) and 5 more |
|
|
1961
|
+
| `label` | `ReactNode` | `id` | What the group's header says - the team, the depot, the region. Real text, read by a screen reader.<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/fold-from-outside) and 5 more |
|
|
1962
|
+
| `children` | `ReactNode` | — | `Lane`s and further `LaneGroup`s, to any depth.<br>Shown in: [Group lanes under a head](#/lane-groups/a-group), [Nest groups to any depth](#/lane-groups/nesting), [Fold groups from outside](#/lane-groups/fold-from-outside) and 5 more |
|
|
1966
1963
|
|
|
1967
1964
|
#### Known limits
|
|
1968
1965
|
|
|
@@ -1975,7 +1972,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
1975
1972
|
The bars: a main time with an optional lead-in before it and lead-out after it, in the colour of the task they belong to (also called bookings or assignments).
|
|
1976
1973
|
|
|
1977
1974
|
```ts
|
|
1978
|
-
import {
|
|
1975
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1979
1976
|
```
|
|
1980
1977
|
|
|
1981
1978
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/subtasks/
|
|
@@ -1989,7 +1986,7 @@ Lead-in and lead-out are durations beside the main time, not times of their own:
|
|
|
1989
1986
|
Loading before a round and unloading after it: `leadIn` and `leadOut` are durations beside the main time, drawn faint in the same colour.
|
|
1990
1987
|
|
|
1991
1988
|
```tsx
|
|
1992
|
-
import {
|
|
1989
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1993
1990
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1994
1991
|
|
|
1995
1992
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2004,8 +2001,9 @@ const ROUNDS: Subtask[] = [
|
|
|
2004
2001
|
];
|
|
2005
2002
|
|
|
2006
2003
|
export default function LeadInAndLeadOut() {
|
|
2004
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(14, 30)] } });
|
|
2007
2005
|
return (
|
|
2008
|
-
<Schedule ariaLabel="Three rounds of one van"
|
|
2006
|
+
<Schedule ariaLabel="Three rounds of one van" height={110}>
|
|
2009
2007
|
<Lane id="van" label="Van FP 214 K" />
|
|
2010
2008
|
<Subtasks data={ROUNDS} tasks={TOURS} />
|
|
2011
2009
|
</Schedule>
|
|
@@ -2018,7 +2016,7 @@ export default function LeadInAndLeadOut() {
|
|
|
2018
2016
|
Every subtask of a task shares its `color`, and the meaning is yours: here the two projects due this week are in the danger colour.
|
|
2019
2017
|
|
|
2020
2018
|
```tsx
|
|
2021
|
-
import {
|
|
2019
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2022
2020
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2023
2021
|
|
|
2024
2022
|
/* A subtask whose task is missing from `tasks` is drawn muted: it is on the
|
|
@@ -2041,8 +2039,9 @@ const WORK: Subtask[] = [
|
|
|
2041
2039
|
];
|
|
2042
2040
|
|
|
2043
2041
|
export default function TaskColours() {
|
|
2042
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(16, 6), at(20, 20)] } });
|
|
2044
2043
|
return (
|
|
2045
|
-
<Schedule ariaLabel="Projects due this week among the rest"
|
|
2044
|
+
<Schedule ariaLabel="Projects due this week among the rest" height={150}>
|
|
2046
2045
|
<Lane id="chloe" label="Chloe Durand" />
|
|
2047
2046
|
<Lane id="eva" label="Eva Novak" />
|
|
2048
2047
|
<Subtasks data={WORK} tasks={PROJECTS} />
|
|
@@ -2056,7 +2055,7 @@ export default function TaskColours() {
|
|
|
2056
2055
|
Work items straight from the team's tracker: the person is the lane, the project the task, and `name` is what the tooltip calls each one.
|
|
2057
2056
|
|
|
2058
2057
|
```tsx
|
|
2059
|
-
import {
|
|
2058
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2060
2059
|
import type { Subtask } from "@umriss-ui/schedule";
|
|
2061
2060
|
|
|
2062
2061
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
@@ -2146,8 +2145,9 @@ const SUBTASKS: Subtask[] = WORK.map((item): Subtask => ({
|
|
|
2146
2145
|
const SPRINT_14: readonly [number, number] = [new Date(2026, 2, 9, 6).getTime(), new Date(2026, 2, 21).getTime()];
|
|
2147
2146
|
|
|
2148
2147
|
export default function ASprint() {
|
|
2148
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: SPRINT_14 } });
|
|
2149
2149
|
return (
|
|
2150
|
-
<Schedule ariaLabel="Sprint 14, 9 to 20 March"
|
|
2150
|
+
<Schedule ariaLabel="Sprint 14, 9 to 20 March" height={500} label={(s) => s.name ?? s.id}>
|
|
2151
2151
|
{PEOPLE.map((person) => (
|
|
2152
2152
|
<Lane key={person.id} id={person.id} label={person.name} />
|
|
2153
2153
|
))}
|
|
@@ -2167,20 +2167,20 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
|
2167
2167
|
|
|
2168
2168
|
| Prop | Type | Default | Description |
|
|
2169
2169
|
|---|---|---|---|
|
|
2170
|
-
| `data` *required* | `readonly `[`Subtask`](#type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and
|
|
2171
|
-
| `tasks` *required* | `readonly `[`Task`](#type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and
|
|
2170
|
+
| `data` *required* | `readonly `[`Subtask`](#type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and 75 more |
|
|
2171
|
+
| `tasks` *required* | `readonly `[`Task`](#type-Task)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and 75 more |
|
|
2172
2172
|
|
|
2173
2173
|
##### `Subtask`
|
|
2174
2174
|
|
|
2175
2175
|
| Prop | Type | Default | Description |
|
|
2176
2176
|
|---|---|---|---|
|
|
2177
|
-
| `id` *required* | `string` | — | The caller's identity of the subtask.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and
|
|
2178
|
-
| `task` *required* | `string` | — | The id of the task it belongs to.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2179
|
-
| `lane` *required* | `string` | — | The id of the lane it sits on.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2180
|
-
| `from` *required* | `number` | — | Start of the main time.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2181
|
-
| `to` *required* | `number` | — | End of the main time.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2182
|
-
| `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and
|
|
2183
|
-
| `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and
|
|
2177
|
+
| `id` *required* | `string` | — | The caller's identity of the subtask.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [Plan a sprint for a team](#/subtasks/a-sprint) and 72 more |
|
|
2178
|
+
| `task` *required* | `string` | — | The id of the task it belongs to.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 68 more |
|
|
2179
|
+
| `lane` *required* | `string` | — | The id of the lane it sits on.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 68 more |
|
|
2180
|
+
| `from` *required* | `number` | — | Start of the main time.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 68 more |
|
|
2181
|
+
| `to` *required* | `number` | — | End of the main time.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 68 more |
|
|
2182
|
+
| `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 25 more |
|
|
2183
|
+
| `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 22 more |
|
|
2184
2184
|
| `name` | `string` | — | What the subtask is called, where the schedule names it.<br>Shown in: [Plan a sprint for a team](#/subtasks/a-sprint), [Write into the bars (Bar labels)](#/bar-labels/bar-labels), [Join the stops of a tour (Dependencies)](#/dependencies/stops-of-a-tour) and 25 more |
|
|
2185
2185
|
| `appearance` | `readonly `[`SubtaskAppearance`](#type-SubtaskAppearance)`[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins.<br>Shown in: [Plan a sprint for a team](#/subtasks/a-sprint), [Provisional (Appearances)](#/appearances/provisional), [Fixed (Appearances)](#/appearances/fixed) and 3 more |
|
|
2186
2186
|
| `progress` | `number` | — | The share of the work that is done, from 0 to 1, drawn as a filled part of the bar. Without it nothing is claimed - an empty bar and a bar at zero per cent are two different statements.<br>Shown in: [Progress (Appearances)](#/appearances/progress), [Several at once (Appearances)](#/appearances/combinations) |
|
|
@@ -2189,9 +2189,9 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
|
2189
2189
|
|
|
2190
2190
|
| Prop | Type | Default | Description |
|
|
2191
2191
|
|---|---|---|---|
|
|
2192
|
-
| `id` *required* | `string` | — | The caller's identity of the task.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2193
|
-
| `color` *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2194
|
-
| `name` | `string` | — | What the task is called, where the schedule names it.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and
|
|
2192
|
+
| `id` *required* | `string` | — | The caller's identity of the task.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
|
|
2193
|
+
| `color` *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
|
|
2194
|
+
| `name` | `string` | — | What the task is called, where the schedule names it.<br>Shown in: [Main time, lead-in and lead-out](#/subtasks/lead-in-and-lead-out), [Colour by what the task means](#/subtasks/task-colours), [The smallest schedule (Installation)](#/installation/smallest-schedule) and 67 more |
|
|
2195
2195
|
|
|
2196
2196
|
##### Types on this page
|
|
2197
2197
|
|
|
@@ -2208,7 +2208,7 @@ type SubtaskAppearance = "provisional" | "fixed" | "muted" | "open";
|
|
|
2208
2208
|
A line of text inside each bar: cut off where the bar is too narrow, left out where it would say nothing, and held at the view's edge while the bar runs on.
|
|
2209
2209
|
|
|
2210
2210
|
```ts
|
|
2211
|
-
import {
|
|
2211
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2212
2212
|
```
|
|
2213
2213
|
|
|
2214
2214
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/bar-labels/
|
|
@@ -2222,7 +2222,7 @@ The text lies on the main time, not on the lead-in. Its colour follows the bar:
|
|
|
2222
2222
|
`label` returns the text for each bar: here the work item's name, cut where the bar is narrow and left out of the short reviews.
|
|
2223
2223
|
|
|
2224
2224
|
```tsx
|
|
2225
|
-
import {
|
|
2225
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2226
2226
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2227
2227
|
|
|
2228
2228
|
/* Pan into the afternoon: a bar that begins before the view keeps its text at
|
|
@@ -2253,10 +2253,10 @@ const WORK: readonly Subtask[] = [
|
|
|
2253
2253
|
];
|
|
2254
2254
|
|
|
2255
2255
|
export default function BarLabels() {
|
|
2256
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: DAY } });
|
|
2256
2257
|
return (
|
|
2257
2258
|
<Schedule
|
|
2258
2259
|
ariaLabel="Tuesday, 17 March, with the work items written in"
|
|
2259
|
-
initialDomain={DAY}
|
|
2260
2260
|
height={196}
|
|
2261
2261
|
label={(subtask) => subtask.name ?? subtask.id}
|
|
2262
2262
|
>
|
|
@@ -2279,7 +2279,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
2279
2279
|
|
|
2280
2280
|
| Prop | Type | Default | Description |
|
|
2281
2281
|
|---|---|---|---|
|
|
2282
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
2283
2282
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
2284
2283
|
| [`label`](#/schedule/ScheduleProps-label) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge. |
|
|
2285
2284
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -2325,12 +2324,24 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
2325
2324
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
2326
2325
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
2327
2326
|
|
|
2327
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
2328
|
+
|
|
2329
|
+
| Prop | Type | Default | Description |
|
|
2330
|
+
|---|---|---|---|
|
|
2331
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
2332
|
+
|
|
2333
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
2334
|
+
|
|
2335
|
+
| Prop | Type | Default | Description |
|
|
2336
|
+
|---|---|---|---|
|
|
2337
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
2338
|
+
|
|
2328
2339
|
### Appearances
|
|
2329
2340
|
|
|
2330
2341
|
What a bar says besides its colour: provisional, fixed, muted, running on past the view, and how far the work has got. Each takes one channel of the drawing, so several on one bar stay readable.
|
|
2331
2342
|
|
|
2332
2343
|
```ts
|
|
2333
|
-
import {
|
|
2344
|
+
import { useSchedule, resolveAppearance } from "@umriss-ui/schedule";
|
|
2334
2345
|
```
|
|
2335
2346
|
|
|
2336
2347
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/appearances/
|
|
@@ -2346,7 +2357,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/appearances/
|
|
|
2346
2357
|
Work pencilled in but not agreed: `"provisional"` leaves the bar hollow with a dashed outline, never pale like a lead-in.
|
|
2347
2358
|
|
|
2348
2359
|
```tsx
|
|
2349
|
-
import {
|
|
2360
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2350
2361
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2351
2362
|
|
|
2352
2363
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2360,8 +2371,9 @@ const WORK: Subtask[] = [
|
|
|
2360
2371
|
];
|
|
2361
2372
|
|
|
2362
2373
|
export default function Provisional() {
|
|
2374
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
2363
2375
|
return (
|
|
2364
|
-
<Schedule ariaLabel="Agreed work above, provisional work below"
|
|
2376
|
+
<Schedule ariaLabel="Agreed work above, provisional work below" height={144}>
|
|
2365
2377
|
<Lane id="released" label="Agreed, with lead-in" />
|
|
2366
2378
|
<Lane id="draft" label="Provisional" />
|
|
2367
2379
|
<Subtasks data={WORK} tasks={PROJECTS} />
|
|
@@ -2375,7 +2387,7 @@ export default function Provisional() {
|
|
|
2375
2387
|
A delivery slot the customer booked may not move: `"fixed"` sets a cap inside each end and keeps the face clear for the label.
|
|
2376
2388
|
|
|
2377
2389
|
```tsx
|
|
2378
|
-
import {
|
|
2390
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2379
2391
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2380
2392
|
|
|
2381
2393
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2388,10 +2400,10 @@ const LEGS: Subtask[] = [
|
|
|
2388
2400
|
];
|
|
2389
2401
|
|
|
2390
2402
|
export default function Fixed() {
|
|
2403
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
2391
2404
|
return (
|
|
2392
2405
|
<Schedule
|
|
2393
2406
|
ariaLabel="A round that may move above, a booked slot below"
|
|
2394
|
-
initialDomain={[at(6, 30), at(11)]}
|
|
2395
2407
|
height={144}
|
|
2396
2408
|
label={(leg) => (leg.id === "nailed" ? "T-05 · booked slot" : "T-05")}
|
|
2397
2409
|
>
|
|
@@ -2408,7 +2420,7 @@ export default function Fixed() {
|
|
|
2408
2420
|
Another team's work, shown for context: `"muted"` mixes the colour into the surface at full height, so it cannot pass for a lead-in.
|
|
2409
2421
|
|
|
2410
2422
|
```tsx
|
|
2411
|
-
import {
|
|
2423
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2412
2424
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2413
2425
|
|
|
2414
2426
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2422,10 +2434,10 @@ const WORK: Subtask[] = [
|
|
|
2422
2434
|
];
|
|
2423
2435
|
|
|
2424
2436
|
export default function Muted() {
|
|
2437
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
2425
2438
|
return (
|
|
2426
2439
|
<Schedule
|
|
2427
2440
|
ariaLabel="Our team's work above, another team's below"
|
|
2428
|
-
initialDomain={[at(6, 30), at(11)]}
|
|
2429
2441
|
height={144}
|
|
2430
2442
|
label={() => "Checkout migration"}
|
|
2431
2443
|
>
|
|
@@ -2442,7 +2454,7 @@ export default function Muted() {
|
|
|
2442
2454
|
`progress` from 0 to 1 draws a rail inside the main time; left out, the bar claims nothing - which is not zero per cent.
|
|
2443
2455
|
|
|
2444
2456
|
```tsx
|
|
2445
|
-
import {
|
|
2457
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2446
2458
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2447
2459
|
|
|
2448
2460
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2456,8 +2468,9 @@ const WORK: Subtask[] = [
|
|
|
2456
2468
|
];
|
|
2457
2469
|
|
|
2458
2470
|
export default function Progress() {
|
|
2471
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
2459
2472
|
return (
|
|
2460
|
-
<Schedule ariaLabel="Work claiming nothing above, work at 65 per cent below"
|
|
2473
|
+
<Schedule ariaLabel="Work claiming nothing above, work at 65 per cent below" height={144}>
|
|
2461
2474
|
<Lane id="unclaimed" label="Says nothing" />
|
|
2462
2475
|
<Lane id="started" label="65 per cent done" />
|
|
2463
2476
|
<Subtasks data={WORK} tasks={PROJECTS} />
|
|
@@ -2471,7 +2484,7 @@ export default function Progress() {
|
|
|
2471
2484
|
An incident still being worked runs past the view: `"open"` fades the bar at whichever edge it passes, and only there.
|
|
2472
2485
|
|
|
2473
2486
|
```tsx
|
|
2474
|
-
import {
|
|
2487
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2475
2488
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2476
2489
|
|
|
2477
2490
|
/* The middle bar is wholly in view and carries no fade; zoom out until it
|
|
@@ -2488,8 +2501,9 @@ const WORK: Subtask[] = [
|
|
|
2488
2501
|
];
|
|
2489
2502
|
|
|
2490
2503
|
export default function Open() {
|
|
2504
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
2491
2505
|
return (
|
|
2492
|
-
<Schedule ariaLabel="Work running past the view at either edge"
|
|
2506
|
+
<Schedule ariaLabel="Work running past the view at either edge" height={188}>
|
|
2493
2507
|
<Lane id="runs-on" label="Runs on" />
|
|
2494
2508
|
<Lane id="whole" label="Wholly in view" />
|
|
2495
2509
|
<Lane id="began" label="Began before" />
|
|
@@ -2504,7 +2518,7 @@ export default function Open() {
|
|
|
2504
2518
|
Muted, fixed and open on one bar stay three statements, each in its own channel; of `"provisional"` and `"fixed"` the later one wins.
|
|
2505
2519
|
|
|
2506
2520
|
```tsx
|
|
2507
|
-
import {
|
|
2521
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2508
2522
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2509
2523
|
|
|
2510
2524
|
/* A hollow bar lies on the surface, so its label takes the page's text
|
|
@@ -2552,10 +2566,10 @@ const LANES = [
|
|
|
2552
2566
|
];
|
|
2553
2567
|
|
|
2554
2568
|
export default function Combinations() {
|
|
2569
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
2555
2570
|
return (
|
|
2556
2571
|
<Schedule
|
|
2557
2572
|
ariaLabel="Bars carrying several appearances at once"
|
|
2558
|
-
initialDomain={[at(6, 30), at(11)]}
|
|
2559
2573
|
height={230}
|
|
2560
2574
|
label={(leg) => NAMES[leg.id] ?? leg.id}
|
|
2561
2575
|
>
|
|
@@ -2578,7 +2592,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
2578
2592
|
|
|
2579
2593
|
| Prop | Type | Default | Description |
|
|
2580
2594
|
|---|---|---|---|
|
|
2581
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
2582
2595
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
2583
2596
|
| [`label`](#/schedule/ScheduleProps-label) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge. |
|
|
2584
2597
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -2625,12 +2638,24 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
2625
2638
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
2626
2639
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
2627
2640
|
|
|
2641
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
2642
|
+
|
|
2643
|
+
| Prop | Type | Default | Description |
|
|
2644
|
+
|---|---|---|---|
|
|
2645
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
2646
|
+
|
|
2647
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
2648
|
+
|
|
2649
|
+
| Prop | Type | Default | Description |
|
|
2650
|
+
|---|---|---|---|
|
|
2651
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
2652
|
+
|
|
2628
2653
|
### Overlap
|
|
2629
2654
|
|
|
2630
2655
|
Two subtasks claiming one lane at the same time (also called a double booking or conflict): both drawn, offset and marked, never packed into sub-lanes.
|
|
2631
2656
|
|
|
2632
2657
|
```ts
|
|
2633
|
-
import {
|
|
2658
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2634
2659
|
```
|
|
2635
2660
|
|
|
2636
2661
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/overlap/
|
|
@@ -2644,7 +2669,7 @@ The later bar is offset a few pixels and edged, and the shared time is marked ac
|
|
|
2644
2669
|
Both bars stay where their times put them: the later one is offset and edged, and the shared time is marked across the lane.
|
|
2645
2670
|
|
|
2646
2671
|
```tsx
|
|
2647
|
-
import {
|
|
2672
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2648
2673
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2649
2674
|
|
|
2650
2675
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2661,8 +2686,9 @@ const WORK: Subtask[] = [
|
|
|
2661
2686
|
];
|
|
2662
2687
|
|
|
2663
2688
|
export default function DoubleBooking() {
|
|
2689
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(8), at(16)] } });
|
|
2664
2690
|
return (
|
|
2665
|
-
<Schedule ariaLabel="Kofi booked on two projects at once"
|
|
2691
|
+
<Schedule ariaLabel="Kofi booked on two projects at once" height={150}>
|
|
2666
2692
|
<Lane id="kofi" label="Kofi Mensah" />
|
|
2667
2693
|
<Lane id="hana" label="Hana Sato" />
|
|
2668
2694
|
<Subtasks data={WORK} tasks={PROJECTS} />
|
|
@@ -2676,7 +2702,7 @@ export default function DoubleBooking() {
|
|
|
2676
2702
|
Unloading and the next loading occupy the vehicle too: where a `leadOut` runs into the next `leadIn`, that is an overlap.
|
|
2677
2703
|
|
|
2678
2704
|
```tsx
|
|
2679
|
-
import {
|
|
2705
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2680
2706
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2681
2707
|
|
|
2682
2708
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2693,8 +2719,9 @@ const LEGS: Subtask[] = [
|
|
|
2693
2719
|
];
|
|
2694
2720
|
|
|
2695
2721
|
export default function LeadOutMeetsLeadIn() {
|
|
2722
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
|
|
2696
2723
|
return (
|
|
2697
|
-
<Schedule ariaLabel="Two rounds of one van, touching at unloading and loading"
|
|
2724
|
+
<Schedule ariaLabel="Two rounds of one van, touching at unloading and loading" height={110}>
|
|
2698
2725
|
<Lane id="fp-402" label="Van FP 402 R" />
|
|
2699
2726
|
<Subtasks data={LEGS} tasks={TOURS} />
|
|
2700
2727
|
</Schedule>
|
|
@@ -2707,7 +2734,7 @@ export default function LeadOutMeetsLeadIn() {
|
|
|
2707
2734
|
Each further bar steps down a little, three levels deep at most, and every one stays inside its own lane.
|
|
2708
2735
|
|
|
2709
2736
|
```tsx
|
|
2710
|
-
import {
|
|
2737
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2711
2738
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
2712
2739
|
|
|
2713
2740
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2726,8 +2753,9 @@ const WORK: Subtask[] = [
|
|
|
2726
2753
|
];
|
|
2727
2754
|
|
|
2728
2755
|
export default function ThreeAtOnce() {
|
|
2756
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(7), at(14)] } });
|
|
2729
2757
|
return (
|
|
2730
|
-
<Schedule ariaLabel="Priya on three incidents at once"
|
|
2758
|
+
<Schedule ariaLabel="Priya on three incidents at once" height={150}>
|
|
2731
2759
|
<Lane id="priya" label="Priya Raman" />
|
|
2732
2760
|
<Lane id="jonas" label="Jonas Keller" />
|
|
2733
2761
|
<Subtasks data={WORK} tasks={INCIDENTS} />
|
|
@@ -2750,7 +2778,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
2750
2778
|
|
|
2751
2779
|
| Prop | Type | Default | Description |
|
|
2752
2780
|
|---|---|---|---|
|
|
2753
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
2754
2781
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
2755
2782
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
2756
2783
|
|
|
@@ -2794,6 +2821,18 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
2794
2821
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
2795
2822
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
2796
2823
|
|
|
2824
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
2825
|
+
|
|
2826
|
+
| Prop | Type | Default | Description |
|
|
2827
|
+
|---|---|---|---|
|
|
2828
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
2829
|
+
|
|
2830
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
2831
|
+
|
|
2832
|
+
| Prop | Type | Default | Description |
|
|
2833
|
+
|---|---|---|---|
|
|
2834
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
2835
|
+
|
|
2797
2836
|
#### Known limits
|
|
2798
2837
|
|
|
2799
2838
|
- No stacked layout on request yet: an overlap is always drawn offset, as the finding it is.
|
|
@@ -2805,7 +2844,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
2805
2844
|
Lines from one subtask's end to the next one's start within a task, with the lag that must pass between them (also called links or predecessors). A lag that does not fit is marked, not repaired.
|
|
2806
2845
|
|
|
2807
2846
|
```ts
|
|
2808
|
-
import {
|
|
2847
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2809
2848
|
```
|
|
2810
2849
|
|
|
2811
2850
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/dependencies/
|
|
@@ -2821,7 +2860,7 @@ Nothing moves to make a dependency fit. [Ripple](#/ripple) computes the moves th
|
|
|
2821
2860
|
Each stop is a subtask, and the drive to the next is a dependency whose `lag` is the driving time.
|
|
2822
2861
|
|
|
2823
2862
|
```tsx
|
|
2824
|
-
import {
|
|
2863
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2825
2864
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
2826
2865
|
|
|
2827
2866
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -2847,8 +2886,9 @@ const DRIVES: Dependency[] = [
|
|
|
2847
2886
|
];
|
|
2848
2887
|
|
|
2849
2888
|
export default function StopsOfATour() {
|
|
2889
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7), at(10)] } });
|
|
2850
2890
|
return (
|
|
2851
|
-
<Schedule ariaLabel="Two morning tours with their stops"
|
|
2891
|
+
<Schedule ariaLabel="Two morning tours with their stops" height={150}>
|
|
2852
2892
|
<Lane id="fp-214" label="Van FP 214 K" />
|
|
2853
2893
|
<Lane id="fp-377" label="E-van FP 377 K" />
|
|
2854
2894
|
<Dependencies data={DRIVES} />
|
|
@@ -2863,7 +2903,7 @@ export default function StopsOfATour() {
|
|
|
2863
2903
|
By default the lag runs from the end of unloading to the start of loading; `leaves` and `arrives` set to `"main"` skip both.
|
|
2864
2904
|
|
|
2865
2905
|
```tsx
|
|
2866
|
-
import {
|
|
2906
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2867
2907
|
import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
|
|
2868
2908
|
|
|
2869
2909
|
/* The anchor decides whether the dependency is violated, so it should say
|
|
@@ -2890,8 +2930,9 @@ const HANDOVERS: Dependency[] = [
|
|
|
2890
2930
|
];
|
|
2891
2931
|
|
|
2892
2932
|
export default function Anchors() {
|
|
2933
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(16, 30)] } });
|
|
2893
2934
|
return (
|
|
2894
|
-
<Schedule ariaLabel="Two handovers with different anchors"
|
|
2935
|
+
<Schedule ariaLabel="Two handovers with different anchors" height={150}>
|
|
2895
2936
|
<Lane id="truck" label="Truck FP 520 E" />
|
|
2896
2937
|
<Lane id="van" label="Van FP 290 E" />
|
|
2897
2938
|
<Dependencies data={HANDOVERS} />
|
|
@@ -2907,7 +2948,7 @@ export default function Anchors() {
|
|
|
2907
2948
|
|
|
2908
2949
|
```tsx
|
|
2909
2950
|
import { Stack, Text } from "@umriss-ui/core";
|
|
2910
|
-
import {
|
|
2951
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2911
2952
|
import type { Subtask, Task, Dependency, DependencyAttachment } from "@umriss-ui/schedule";
|
|
2912
2953
|
|
|
2913
2954
|
/* Picture only: `attach` changes no finding. Within one lane there is no
|
|
@@ -2933,12 +2974,13 @@ const HANDOVERS: Dependency[] = [
|
|
|
2933
2974
|
];
|
|
2934
2975
|
|
|
2935
2976
|
function Variant({ attach }: { attach: DependencyAttachment }) {
|
|
2977
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
|
|
2936
2978
|
return (
|
|
2937
2979
|
<Stack gap={1}>
|
|
2938
2980
|
<Text size="xs" tone="muted" mono>
|
|
2939
2981
|
attach="{attach}"
|
|
2940
2982
|
</Text>
|
|
2941
|
-
<Schedule ariaLabel={`A release through three engineers, lines from the ${attach}`}
|
|
2983
|
+
<Schedule ariaLabel={`A release through three engineers, lines from the ${attach}`} height={188} attach={attach}>
|
|
2942
2984
|
<Lane id="priya" label="Priya Raman" />
|
|
2943
2985
|
<Lane id="ada" label="Ada Mwangi" />
|
|
2944
2986
|
<Lane id="sam" label="Sam Okafor" />
|
|
@@ -2965,7 +3007,7 @@ export default function Attach() {
|
|
|
2965
3007
|
|
|
2966
3008
|
```tsx
|
|
2967
3009
|
import { Stack, Text } from "@umriss-ui/core";
|
|
2968
|
-
import {
|
|
3010
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
2969
3011
|
import type { Subtask, Task, Dependency, DependencyEnds } from "@umriss-ui/schedule";
|
|
2970
3012
|
|
|
2971
3013
|
/* Picture only, like `route` and `attach`; a single dependency may say
|
|
@@ -2988,12 +3030,13 @@ const HANDOVERS: Dependency[] = [
|
|
|
2988
3030
|
];
|
|
2989
3031
|
|
|
2990
3032
|
function Variant({ ends }: { ends: DependencyEnds }) {
|
|
3033
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
|
|
2991
3034
|
return (
|
|
2992
3035
|
<Stack gap={1}>
|
|
2993
3036
|
<Text size="xs" tone="muted" mono>
|
|
2994
3037
|
ends="{ends}"
|
|
2995
3038
|
</Text>
|
|
2996
|
-
<Schedule ariaLabel={`Design, build and test, ends ${ends}`}
|
|
3039
|
+
<Schedule ariaLabel={`Design, build and test, ends ${ends}`} height={188} attach="nearest" ends={ends}>
|
|
2997
3040
|
<Lane id="noah" label="Noah Fischer" />
|
|
2998
3041
|
<Lane id="arjun" label="Arjun Mehta" />
|
|
2999
3042
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -3019,7 +3062,7 @@ export default function Ends() {
|
|
|
3019
3062
|
The transfer at the depot takes forty minutes and the plan leaves twenty-five: the line turns dashed in the finding colour, and nothing moves.
|
|
3020
3063
|
|
|
3021
3064
|
```tsx
|
|
3022
|
-
import {
|
|
3065
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3023
3066
|
import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
|
|
3024
3067
|
|
|
3025
3068
|
/* Whether the van waits or the truck leaves earlier is the planner's
|
|
@@ -3038,8 +3081,9 @@ const LEGS: Subtask[] = [
|
|
|
3038
3081
|
const HANDOVERS: Dependency[] = [{ id: "transfer", from: "line-haul", to: "round", lag: min(40) }];
|
|
3039
3082
|
|
|
3040
3083
|
export default function ViolatedDependency() {
|
|
3084
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11)] } });
|
|
3041
3085
|
return (
|
|
3042
|
-
<Schedule ariaLabel="A handover from the truck to the van that is too short"
|
|
3086
|
+
<Schedule ariaLabel="A handover from the truck to the van that is too short" height={150}>
|
|
3043
3087
|
<Lane id="truck" label="Truck FP 118 R" />
|
|
3044
3088
|
<Lane id="van" label="Van FP 214 K" />
|
|
3045
3089
|
<Dependencies data={HANDOVERS} />
|
|
@@ -3054,7 +3098,7 @@ export default function ViolatedDependency() {
|
|
|
3054
3098
|
A dependency's own `attach` and `ends` override the schedule's for that one line: here the sign-off joins the middles and marks its ends, the rest stay quiet.
|
|
3055
3099
|
|
|
3056
3100
|
```tsx
|
|
3057
|
-
import {
|
|
3101
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3058
3102
|
import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
|
|
3059
3103
|
|
|
3060
3104
|
/* Picture only, like the schedule's own `attach` and `ends`: the finding
|
|
@@ -3080,8 +3124,9 @@ const HANDOVERS: Dependency[] = [
|
|
|
3080
3124
|
];
|
|
3081
3125
|
|
|
3082
3126
|
export default function OneHandoverDrawnItsOwnWay() {
|
|
3127
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(12, 30)] } });
|
|
3083
3128
|
return (
|
|
3084
|
-
<Schedule ariaLabel="Design, build, test and a sign-off drawn its own way"
|
|
3129
|
+
<Schedule ariaLabel="Design, build, test and a sign-off drawn its own way" height={188} attach="nearest" ends="none">
|
|
3085
3130
|
<Lane id="noah" label="Noah Fischer" />
|
|
3086
3131
|
<Lane id="arjun" label="Arjun Mehta" />
|
|
3087
3132
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -3173,7 +3218,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
3173
3218
|
The shape of a dependency's line: a curve that leaves and arrives forwards, a straight line, or orthogonal segments. Set it for the schedule, or for one dependency.
|
|
3174
3219
|
|
|
3175
3220
|
```ts
|
|
3176
|
-
import {
|
|
3221
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3177
3222
|
```
|
|
3178
3223
|
|
|
3179
3224
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/routes/
|
|
@@ -3185,7 +3230,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/routes/
|
|
|
3185
3230
|
The default: each line leaves forwards and arrives forwards, so it shows which way the work goes, even when it loops back up.
|
|
3186
3231
|
|
|
3187
3232
|
```tsx
|
|
3188
|
-
import {
|
|
3233
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3189
3234
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
3190
3235
|
|
|
3191
3236
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -3207,8 +3252,9 @@ const HANDOVERS: Dependency[] = [
|
|
|
3207
3252
|
];
|
|
3208
3253
|
|
|
3209
3254
|
export default function Curve() {
|
|
3255
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
|
|
3210
3256
|
return (
|
|
3211
|
-
<Schedule ariaLabel="Search results, from layout to polish, curved lines"
|
|
3257
|
+
<Schedule ariaLabel="Search results, from layout to polish, curved lines" height={188}>
|
|
3212
3258
|
<Lane id="noah" label="Noah Fischer" />
|
|
3213
3259
|
<Lane id="chloe" label="Chloe Durand" />
|
|
3214
3260
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -3224,7 +3270,7 @@ export default function Curve() {
|
|
|
3224
3270
|
`route="straight"` draws the shortest line between the two ends, calm in a plan read from a distance.
|
|
3225
3271
|
|
|
3226
3272
|
```tsx
|
|
3227
|
-
import {
|
|
3273
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3228
3274
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
3229
3275
|
|
|
3230
3276
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -3246,8 +3292,9 @@ const HANDOVERS: Dependency[] = [
|
|
|
3246
3292
|
];
|
|
3247
3293
|
|
|
3248
3294
|
export default function Straight() {
|
|
3295
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
|
|
3249
3296
|
return (
|
|
3250
|
-
<Schedule ariaLabel="Search results, from layout to polish, straight lines"
|
|
3297
|
+
<Schedule ariaLabel="Search results, from layout to polish, straight lines" height={188} route="straight" attach="nearest">
|
|
3251
3298
|
<Lane id="noah" label="Noah Fischer" />
|
|
3252
3299
|
<Lane id="chloe" label="Chloe Durand" />
|
|
3253
3300
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -3263,7 +3310,7 @@ export default function Straight() {
|
|
|
3263
3310
|
`route="orthogonal"` leaves the bar, runs across and comes in again, like a flow chart.
|
|
3264
3311
|
|
|
3265
3312
|
```tsx
|
|
3266
|
-
import {
|
|
3313
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3267
3314
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
3268
3315
|
|
|
3269
3316
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -3285,8 +3332,9 @@ const HANDOVERS: Dependency[] = [
|
|
|
3285
3332
|
];
|
|
3286
3333
|
|
|
3287
3334
|
export default function Orthogonal() {
|
|
3335
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
|
|
3288
3336
|
return (
|
|
3289
|
-
<Schedule ariaLabel="Search results, from layout to polish, orthogonal lines"
|
|
3337
|
+
<Schedule ariaLabel="Search results, from layout to polish, orthogonal lines" height={188} route="orthogonal" attach="nearest">
|
|
3290
3338
|
<Lane id="noah" label="Noah Fischer" />
|
|
3291
3339
|
<Lane id="chloe" label="Chloe Durand" />
|
|
3292
3340
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -3302,7 +3350,7 @@ export default function Orthogonal() {
|
|
|
3302
3350
|
A `route` on a single dependency overrides the schedule's: here only the loop back up to the designer is drawn orthogonal.
|
|
3303
3351
|
|
|
3304
3352
|
```tsx
|
|
3305
|
-
import {
|
|
3353
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3306
3354
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
3307
3355
|
|
|
3308
3356
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -3325,8 +3373,9 @@ const HANDOVERS: Dependency[] = [
|
|
|
3325
3373
|
];
|
|
3326
3374
|
|
|
3327
3375
|
export default function PerDependency() {
|
|
3376
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(13, 30)] } });
|
|
3328
3377
|
return (
|
|
3329
|
-
<Schedule ariaLabel="Search results, from layout to polish, straight lines but one"
|
|
3378
|
+
<Schedule ariaLabel="Search results, from layout to polish, straight lines but one" height={188} route="straight" attach="nearest">
|
|
3330
3379
|
<Lane id="noah" label="Noah Fischer" />
|
|
3331
3380
|
<Lane id="chloe" label="Chloe Durand" />
|
|
3332
3381
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -3347,7 +3396,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
3347
3396
|
|
|
3348
3397
|
| Prop | Type | Default | Description |
|
|
3349
3398
|
|---|---|---|---|
|
|
3350
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
3351
3399
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
3352
3400
|
| [`route`](#/schedule/ScheduleProps-route) | [`DependencyRoute`](#/schedule/type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | `"curve"` | How the dependencies are drawn: a `"curve"` that leaves and arrives forwards, a `"straight"` line, or `"orthogonal"` segments. A dependency may say otherwise for itself. |
|
|
3353
3401
|
| [`attach`](#/schedule/ScheduleProps-attach) | [`DependencyAttachment`](#/schedule/type-DependencyAttachment)<br>`"centre" \| "nearest"` | `"centre"` | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone. |
|
|
@@ -3408,6 +3456,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
3408
3456
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
3409
3457
|
| [`route`](#/dependencies/Dependency-route) | [`DependencyRoute`](#/dependencies/type-DependencyRoute)<br>`"curve" \| "straight" \| "orthogonal"` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone. |
|
|
3410
3458
|
|
|
3459
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
3460
|
+
|
|
3461
|
+
| Prop | Type | Default | Description |
|
|
3462
|
+
|---|---|---|---|
|
|
3463
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
3464
|
+
|
|
3465
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
3466
|
+
|
|
3467
|
+
| Prop | Type | Default | Description |
|
|
3468
|
+
|---|---|---|---|
|
|
3469
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
3470
|
+
|
|
3411
3471
|
## Time
|
|
3412
3472
|
|
|
3413
3473
|
The axis the work stands on: the calendar, blocked time, the now line, and moving through it.
|
|
@@ -3417,7 +3477,7 @@ The axis the work stands on: the calendar, blocked time, the now line, and movin
|
|
|
3417
3477
|
A day band that names the days and a time band whose ticks follow the zoom, from quarter hours to days. A working calendar cuts the hours nobody works out of the axis.
|
|
3418
3478
|
|
|
3419
3479
|
```ts
|
|
3420
|
-
import {
|
|
3480
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3421
3481
|
```
|
|
3422
3482
|
|
|
3423
3483
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/time-axis/
|
|
@@ -3431,7 +3491,7 @@ The calendar is a list of intervals in which time counts, nothing more; deriving
|
|
|
3431
3491
|
A week of the on-call rota: the day band names each day, and the time band's step follows the zoom.
|
|
3432
3492
|
|
|
3433
3493
|
```tsx
|
|
3434
|
-
import {
|
|
3494
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3435
3495
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
3436
3496
|
|
|
3437
3497
|
/* Data from the operations world, written out here so the example runs on its own. */
|
|
@@ -3486,8 +3546,9 @@ const DUTIES: Subtask[] = ONCALL.map((duty) => ({ id: duty.id, task: duty.rotati
|
|
|
3486
3546
|
const WEEK: readonly [number, number] = [new Date(2026, 2, 16).getTime(), new Date(2026, 2, 23, 12).getTime()];
|
|
3487
3547
|
|
|
3488
3548
|
export default function AWeek() {
|
|
3549
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: WEEK } });
|
|
3489
3550
|
return (
|
|
3490
|
-
<Schedule ariaLabel="On-call rota, 16 to 23 March"
|
|
3551
|
+
<Schedule ariaLabel="On-call rota, 16 to 23 March" height={420}>
|
|
3491
3552
|
{ENGINEERS.map((engineer) => (
|
|
3492
3553
|
<Lane key={engineer.id} id={engineer.id} label={engineer.name} />
|
|
3493
3554
|
))}
|
|
@@ -3503,7 +3564,7 @@ The depot works 06:00 to 22:00: `calendar` lists those hours, the nights leave t
|
|
|
3503
3564
|
|
|
3504
3565
|
```tsx
|
|
3505
3566
|
import { useState } from "react";
|
|
3506
|
-
import {
|
|
3567
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
3507
3568
|
import type { Subtask, Task, Dependency } from "@umriss-ui/schedule";
|
|
3508
3569
|
|
|
3509
3570
|
/* Three days in the width of two; a dotted line marks each seam. Deriving the
|
|
@@ -3527,11 +3588,11 @@ const HANDOVERS: Dependency[] = [
|
|
|
3527
3588
|
];
|
|
3528
3589
|
|
|
3529
3590
|
export default function WorkingCalendar() {
|
|
3591
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(0, 6), at(2, 22)] } });
|
|
3530
3592
|
const [legs, setLegs] = useState<readonly Subtask[]>(LEGS);
|
|
3531
3593
|
return (
|
|
3532
3594
|
<Schedule
|
|
3533
3595
|
ariaLabel="Three days at East Gate depot, nights removed"
|
|
3534
|
-
initialDomain={[at(0, 6), at(2, 22)]}
|
|
3535
3596
|
calendar={OPENING_HOURS}
|
|
3536
3597
|
height={220}
|
|
3537
3598
|
intents={["move"]}
|
|
@@ -3552,7 +3613,7 @@ export default function WorkingCalendar() {
|
|
|
3552
3613
|
Weekdays 09:00 to 17:00 as the `calendar`: a two-week sprint reads as ten working days, with the evenings and the weekend gone.
|
|
3553
3614
|
|
|
3554
3615
|
```tsx
|
|
3555
|
-
import {
|
|
3616
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3556
3617
|
|
|
3557
3618
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
3558
3619
|
|
|
@@ -3639,10 +3700,10 @@ const WEB_TEAM = PEOPLE.filter((person) => person.team === "Web");
|
|
|
3639
3700
|
const ON_THE_WEB_TEAM = new Set(WEB_TEAM.map((person) => person.id));
|
|
3640
3701
|
|
|
3641
3702
|
export default function OfficeHours() {
|
|
3703
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(9, 9), at(20, 17)] } });
|
|
3642
3704
|
return (
|
|
3643
3705
|
<Schedule
|
|
3644
3706
|
ariaLabel="Sprint 14 of the web team, office hours only"
|
|
3645
|
-
initialDomain={[at(9, 9), at(20, 17)]}
|
|
3646
3707
|
calendar={OFFICE_HOURS}
|
|
3647
3708
|
height={280}
|
|
3648
3709
|
label={(item) => item.name ?? item.id}
|
|
@@ -3670,7 +3731,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
3670
3731
|
|
|
3671
3732
|
| Prop | Type | Default | Description |
|
|
3672
3733
|
|---|---|---|---|
|
|
3673
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
3674
3734
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
3675
3735
|
| [`calendar`](#/schedule/ScheduleProps-calendar) | [`CalendarInput`](#/schedule/type-CalendarInput) | `[]` | A working calendar: the intervals in which time counts. Nights and weekends outside them are cut out of the axis. Without intervals every hour counts, as on the wall clock. |
|
|
3676
3736
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
@@ -3739,12 +3799,24 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
3739
3799
|
| [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
3740
3800
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
3741
3801
|
|
|
3802
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
3803
|
+
|
|
3804
|
+
| Prop | Type | Default | Description |
|
|
3805
|
+
|---|---|---|---|
|
|
3806
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
3807
|
+
|
|
3808
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
3809
|
+
|
|
3810
|
+
| Prop | Type | Default | Description |
|
|
3811
|
+
|---|---|---|---|
|
|
3812
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
3813
|
+
|
|
3742
3814
|
### Blocked time
|
|
3743
3815
|
|
|
3744
3816
|
Leave, maintenance and other time a lane is not available: hatched behind the work, a finding where work covers it, and closed to a drag.
|
|
3745
3817
|
|
|
3746
3818
|
```ts
|
|
3747
|
-
import {
|
|
3819
|
+
import { useSchedule, findings } from "@umriss-ui/schedule";
|
|
3748
3820
|
```
|
|
3749
3821
|
|
|
3750
3822
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/blocked-time/
|
|
@@ -3761,7 +3833,7 @@ A person on leave, a server in maintenance: `BlockedTimes` hatches that time on
|
|
|
3761
3833
|
|
|
3762
3834
|
```tsx
|
|
3763
3835
|
import { Stack, Text } from "@umriss-ui/core";
|
|
3764
|
-
import {
|
|
3836
|
+
import { findings, useSchedule } from "@umriss-ui/schedule";
|
|
3765
3837
|
import type { BlockedTime, Subtask, Task } from "@umriss-ui/schedule";
|
|
3766
3838
|
|
|
3767
3839
|
/* Blocked time is data beside the work: one list, each interval naming its
|
|
@@ -3805,11 +3877,12 @@ const time = (instant: number) =>
|
|
|
3805
3877
|
new Date(instant).toLocaleString("en-GB", { weekday: "short", hour: "2-digit", minute: "2-digit" });
|
|
3806
3878
|
|
|
3807
3879
|
export default function Leave() {
|
|
3880
|
+
const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(0, 6), day(5, 0)] } });
|
|
3808
3881
|
const found = findings(WORK, [], BLOCKED);
|
|
3809
3882
|
|
|
3810
3883
|
return (
|
|
3811
3884
|
<Stack gap={3}>
|
|
3812
|
-
<Schedule ariaLabel="Team plan, week of 16 March"
|
|
3885
|
+
<Schedule ariaLabel="Team plan, week of 16 March" height={240}>
|
|
3813
3886
|
{PEOPLE.map((person) => (
|
|
3814
3887
|
<Lane key={person.id} id={person.id} label={person.label} />
|
|
3815
3888
|
))}
|
|
@@ -3837,7 +3910,7 @@ A drag does not put work into blocked time: the ghost stays where it was allowed
|
|
|
3837
3910
|
```tsx
|
|
3838
3911
|
import { useState } from "react";
|
|
3839
3912
|
import { Stack, Text } from "@umriss-ui/core";
|
|
3840
|
-
import {
|
|
3913
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
3841
3914
|
import type { BlockedTime, Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
3842
3915
|
|
|
3843
3916
|
/* Blocked time is part of where a subtask may go. Drag the review towards
|
|
@@ -3862,6 +3935,7 @@ const START: readonly Subtask[] = [
|
|
|
3862
3935
|
const BLOCKED: readonly BlockedTime[] = [{ id: "anna-leave", lane: "anna", from: day(2, 0), to: day(4, 0), label: "Leave" }];
|
|
3863
3936
|
|
|
3864
3937
|
export default function DragAround() {
|
|
3938
|
+
const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(0, 6), day(5, 0)] } });
|
|
3865
3939
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
3866
3940
|
const [last, setLast] = useState("Drag the design review into Anna's leave");
|
|
3867
3941
|
|
|
@@ -3875,7 +3949,6 @@ export default function DragAround() {
|
|
|
3875
3949
|
<Stack gap={3}>
|
|
3876
3950
|
<Schedule
|
|
3877
3951
|
ariaLabel="Two people, one on leave"
|
|
3878
|
-
initialDomain={[day(0, 6), day(5, 0)]}
|
|
3879
3952
|
height={150}
|
|
3880
3953
|
intents={["move", "lane"]}
|
|
3881
3954
|
onIntent={onIntent}
|
|
@@ -3899,7 +3972,7 @@ The team's holidays, sick days and training become `BlockedTimes`, and [`finding
|
|
|
3899
3972
|
|
|
3900
3973
|
```tsx
|
|
3901
3974
|
import { Stack, Text } from "@umriss-ui/core";
|
|
3902
|
-
import {
|
|
3975
|
+
import { findings, useSchedule } from "@umriss-ui/schedule";
|
|
3903
3976
|
import type { BlockedTime } from "@umriss-ui/schedule";
|
|
3904
3977
|
|
|
3905
3978
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
@@ -4006,10 +4079,11 @@ const NAME = new Map(WORK.map((item) => [item.id, item.name]));
|
|
|
4006
4079
|
const PERSON = new Map(PEOPLE.map((person) => [person.id, person.name]));
|
|
4007
4080
|
|
|
4008
4081
|
export default function TeamLeave() {
|
|
4082
|
+
const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: SPRINT_14 } });
|
|
4009
4083
|
const clashes = findings(WORK, [], BLOCKED).inBlockedTime;
|
|
4010
4084
|
return (
|
|
4011
4085
|
<Stack gap={3}>
|
|
4012
|
-
<Schedule ariaLabel="Sprint 14 with the team's leave"
|
|
4086
|
+
<Schedule ariaLabel="Sprint 14 with the team's leave" height={500}>
|
|
4013
4087
|
{PEOPLE.map((person) => (
|
|
4014
4088
|
<Lane key={person.id} id={person.id} label={person.name} />
|
|
4015
4089
|
))}
|
|
@@ -4057,7 +4131,7 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
|
4057
4131
|
A line across the lanes at the present moment, so that whatever should have happened by now stands to its left. It follows the clock, or stays at a fixed instant for a replay.
|
|
4058
4132
|
|
|
4059
4133
|
```ts
|
|
4060
|
-
import {
|
|
4134
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4061
4135
|
```
|
|
4062
4136
|
|
|
4063
4137
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/now-line/
|
|
@@ -4069,7 +4143,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/now-line/
|
|
|
4069
4143
|
Pass an instant to `now` and a line crosses the lanes there: a dispatcher at 10:30 sees which tours are under way.
|
|
4070
4144
|
|
|
4071
4145
|
```tsx
|
|
4072
|
-
import {
|
|
4146
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4073
4147
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
4074
4148
|
|
|
4075
4149
|
/* Data from the logistics world, written out here so the example runs on its own. */
|
|
@@ -4216,8 +4290,9 @@ const ROUNDS: Subtask[] = TOURS.map((tour) => ({
|
|
|
4216
4290
|
}));
|
|
4217
4291
|
|
|
4218
4292
|
export default function NowLine() {
|
|
4293
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
4219
4294
|
return (
|
|
4220
|
-
<Schedule ariaLabel="Today's tours, with the present"
|
|
4295
|
+
<Schedule ariaLabel="Today's tours, with the present" height={420} now={NOW}>
|
|
4221
4296
|
{VEHICLES.map((vehicle) => (
|
|
4222
4297
|
<Lane key={vehicle.id} id={vehicle.id} label={vehicle.plate} />
|
|
4223
4298
|
))}
|
|
@@ -4232,7 +4307,7 @@ export default function NowLine() {
|
|
|
4232
4307
|
`now` on its own reads the clock and moves on by the minute, so a plan left open on a wall screen stays true.
|
|
4233
4308
|
|
|
4234
4309
|
```tsx
|
|
4235
|
-
import {
|
|
4310
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4236
4311
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
4237
4312
|
|
|
4238
4313
|
const HOUR = 60 * 60_000;
|
|
@@ -4255,8 +4330,9 @@ const DUTIES: Subtask[] = [
|
|
|
4255
4330
|
];
|
|
4256
4331
|
|
|
4257
4332
|
export default function FollowTheClock() {
|
|
4333
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [current - 6 * HOUR, current + 6 * HOUR] } });
|
|
4258
4334
|
return (
|
|
4259
|
-
<Schedule ariaLabel="Today's on-call rota, with the present"
|
|
4335
|
+
<Schedule ariaLabel="Today's on-call rota, with the present" height={188} now>
|
|
4260
4336
|
<Lane id="ada" label="Ada Mwangi" />
|
|
4261
4337
|
<Lane id="tomasz" label="Tomasz Nowak" />
|
|
4262
4338
|
<Lane id="leila" label="Leila Haddad" />
|
|
@@ -4276,7 +4352,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
4276
4352
|
|
|
4277
4353
|
| Prop | Type | Default | Description |
|
|
4278
4354
|
|---|---|---|---|
|
|
4279
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
4280
4355
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
4281
4356
|
| [`now`](#/schedule/ScheduleProps-now) | `boolean \| number` | `false` | A line marking the present across the lanes: `true` follows the clock by the minute, an instant fixes it there. |
|
|
4282
4357
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -4320,19 +4395,31 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
4320
4395
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
4321
4396
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
4322
4397
|
|
|
4398
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
4399
|
+
|
|
4400
|
+
| Prop | Type | Default | Description |
|
|
4401
|
+
|---|---|---|---|
|
|
4402
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
4403
|
+
|
|
4404
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
4405
|
+
|
|
4406
|
+
| Prop | Type | Default | Description |
|
|
4407
|
+
|---|---|---|---|
|
|
4408
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
4409
|
+
|
|
4323
4410
|
### Pan and zoom
|
|
4324
4411
|
|
|
4325
4412
|
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.
|
|
4326
4413
|
|
|
4327
4414
|
```ts
|
|
4328
|
-
import {
|
|
4415
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4329
4416
|
```
|
|
4330
4417
|
|
|
4331
4418
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/pan-and-zoom/
|
|
4332
4419
|
|
|
4333
4420
|
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.
|
|
4334
4421
|
|
|
4335
|
-
|
|
4422
|
+
No intent is raised: pan and zoom change the visible span and nothing else ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md)). The span is part of the schedule's view, which `onViewChange` reports at most once per frame - see [View](#/view) and [Linked schedules](#/linked-schedules).
|
|
4336
4423
|
|
|
4337
4424
|
#### Examples
|
|
4338
4425
|
|
|
@@ -4341,7 +4428,7 @@ Nothing is reported and no intent is raised: pan and zoom change the visible spa
|
|
|
4341
4428
|
Drag, wheel, Ctrl with the wheel and a pinch need nothing switched on; `zoomLimits` only bounds the span, here two hours to two days.
|
|
4342
4429
|
|
|
4343
4430
|
```tsx
|
|
4344
|
-
import {
|
|
4431
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4345
4432
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
4346
4433
|
|
|
4347
4434
|
/* Without `zoomLimits` the span may run from an hour to twenty-eight days. */
|
|
@@ -4369,10 +4456,10 @@ const WORK: Subtask[] = [
|
|
|
4369
4456
|
];
|
|
4370
4457
|
|
|
4371
4458
|
export default function PanAndZoom() {
|
|
4459
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
4372
4460
|
return (
|
|
4373
4461
|
<Schedule
|
|
4374
4462
|
ariaLabel="A plan to pan and zoom"
|
|
4375
|
-
initialDomain={[at(5, 30), at(18)]}
|
|
4376
4463
|
height={196}
|
|
4377
4464
|
zoomLimits={{ min: 2 * HOUR, max: 48 * HOUR }}
|
|
4378
4465
|
>
|
|
@@ -4400,7 +4487,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
4400
4487
|
|
|
4401
4488
|
| Prop | Type | Default | Description |
|
|
4402
4489
|
|---|---|---|---|
|
|
4403
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
4404
4490
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
4405
4491
|
| [`zoomLimits`](#/schedule/ScheduleProps-zoomLimits) | [`ZoomLimits`](#/schedule/type-ZoomLimits) | one hour to 28 days | The narrowest and widest time span zoom may reach, in milliseconds. |
|
|
4406
4492
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -4445,6 +4531,18 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
4445
4531
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
4446
4532
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
4447
4533
|
|
|
4534
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
4535
|
+
|
|
4536
|
+
| Prop | Type | Default | Description |
|
|
4537
|
+
|---|---|---|---|
|
|
4538
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
4539
|
+
|
|
4540
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
4541
|
+
|
|
4542
|
+
| Prop | Type | Default | Description |
|
|
4543
|
+
|---|---|---|---|
|
|
4544
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
4545
|
+
|
|
4448
4546
|
## Reading
|
|
4449
4547
|
|
|
4450
4548
|
What a planner takes out of the picture: what is selected, what the pointer is on, and what a second view makes of it.
|
|
@@ -4454,7 +4552,7 @@ What a planner takes out of the picture: what is selected, what the pointer is o
|
|
|
4454
4552
|
A click on a bar selects its whole task on every lane, so a planner sees every part of one job at once (also called highlighting). Reach for it when a list or a detail panel beside the plan should follow what was picked.
|
|
4455
4553
|
|
|
4456
4554
|
```ts
|
|
4457
|
-
import {
|
|
4555
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4458
4556
|
```
|
|
4459
4557
|
|
|
4460
4558
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/selection/
|
|
@@ -4472,7 +4570,7 @@ The selection also steps the rest back, so a planner sees at once what they are
|
|
|
4472
4570
|
Click a leg and the whole consignment is outlined on every vehicle: the leg you clicked in full colour, its other legs halfway to grey, all other work grey. Click the empty plot to clear it.
|
|
4473
4571
|
|
|
4474
4572
|
```tsx
|
|
4475
|
-
import {
|
|
4573
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4476
4574
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
4477
4575
|
|
|
4478
4576
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -4497,8 +4595,9 @@ const TRANSFERS: readonly Dependency[] = [
|
|
|
4497
4595
|
];
|
|
4498
4596
|
|
|
4499
4597
|
export default function SelectATask() {
|
|
4598
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(13, 30)] } });
|
|
4500
4599
|
return (
|
|
4501
|
-
<Schedule ariaLabel="Two consignments at North depot"
|
|
4600
|
+
<Schedule ariaLabel="Two consignments at North depot" height={140}>
|
|
4502
4601
|
<Lane id="truck-118" label="Truck FP 118 R" />
|
|
4503
4602
|
<Lane id="van-214" label="Van FP 214 K" />
|
|
4504
4603
|
<Dependencies data={TRANSFERS} />
|
|
@@ -4515,7 +4614,7 @@ Hold `selectedTask` in your state to show the selection elsewhere; `onSelectedTa
|
|
|
4515
4614
|
```tsx
|
|
4516
4615
|
import { useState } from "react";
|
|
4517
4616
|
import { Stack, Text } from "@umriss-ui/core";
|
|
4518
|
-
import {
|
|
4617
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4519
4618
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
4520
4619
|
|
|
4521
4620
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -4554,6 +4653,7 @@ const TRANSFERS: readonly Dependency[] = [
|
|
|
4554
4653
|
];
|
|
4555
4654
|
|
|
4556
4655
|
export default function ControlTheSelection() {
|
|
4656
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: DAY } });
|
|
4557
4657
|
const [selected, setSelected] = useState<string | null>("c-2043");
|
|
4558
4658
|
const [leg, setLeg] = useState<string | null>(null);
|
|
4559
4659
|
const consignment = CONSIGNMENTS.find((c) => c.id === selected);
|
|
@@ -4562,7 +4662,6 @@ export default function ControlTheSelection() {
|
|
|
4562
4662
|
<Stack gap={3}>
|
|
4563
4663
|
<Schedule
|
|
4564
4664
|
ariaLabel="Legs at North depot on Tuesday, 17 March"
|
|
4565
|
-
initialDomain={DAY}
|
|
4566
4665
|
height={284}
|
|
4567
4666
|
selectedTask={selected}
|
|
4568
4667
|
onSelectedTaskChange={(task, subtask) => {
|
|
@@ -4607,7 +4706,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
4607
4706
|
|
|
4608
4707
|
| Prop | Type | Default | Description |
|
|
4609
4708
|
|---|---|---|---|
|
|
4610
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
4611
4709
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
4612
4710
|
| [`selectedTask`](#/schedule/ScheduleProps-selectedTask) | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself. |
|
|
4613
4711
|
| [`onSelectedTaskChange`](#/schedule/ScheduleProps-onSelectedTaskChange) | `(task: string \| null, subtask: string \| null) => void` | — | Called when a click selects a task or clears the selection, with the subtask that was clicked - null where the click was on a dependency or on nothing. It is called again when another subtask of the same task is clicked. |
|
|
@@ -4668,6 +4766,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
4668
4766
|
| [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
4669
4767
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
4670
4768
|
|
|
4769
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
4770
|
+
|
|
4771
|
+
| Prop | Type | Default | Description |
|
|
4772
|
+
|---|---|---|---|
|
|
4773
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
4774
|
+
|
|
4775
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
4776
|
+
|
|
4777
|
+
| Prop | Type | Default | Description |
|
|
4778
|
+
|---|---|---|---|
|
|
4779
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
4780
|
+
|
|
4671
4781
|
#### Known limits
|
|
4672
4782
|
|
|
4673
4783
|
- One task at a time: there is no multiple selection.
|
|
@@ -4679,7 +4789,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
4679
4789
|
Reports what the pointer does on the plan - hover, click, right-click - with what it hit and the time and lane under it. Reach for it to open a menu, fill a detail panel or write a status line.
|
|
4680
4790
|
|
|
4681
4791
|
```ts
|
|
4682
|
-
import {
|
|
4792
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4683
4793
|
```
|
|
4684
4794
|
|
|
4685
4795
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/interactions/
|
|
@@ -4697,7 +4807,7 @@ While `onInteraction` is set, a right-click opens no browser menu: your applicat
|
|
|
4697
4807
|
```tsx
|
|
4698
4808
|
import { useState } from "react";
|
|
4699
4809
|
import { Stack, Text } from "@umriss-ui/core";
|
|
4700
|
-
import {
|
|
4810
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4701
4811
|
import type { Dependency, ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
|
|
4702
4812
|
|
|
4703
4813
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -4730,13 +4840,13 @@ const describe = (interaction: ScheduleInteraction): string => {
|
|
|
4730
4840
|
};
|
|
4731
4841
|
|
|
4732
4842
|
export default function ReportThePointer() {
|
|
4843
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
4733
4844
|
const [last, setLast] = useState("Move the pointer over the plan");
|
|
4734
4845
|
|
|
4735
4846
|
return (
|
|
4736
4847
|
<Stack gap={3}>
|
|
4737
4848
|
<Schedule
|
|
4738
4849
|
ariaLabel="One consignment on a truck and a van"
|
|
4739
|
-
initialDomain={[at(5, 30), at(18)]}
|
|
4740
4850
|
height={152}
|
|
4741
4851
|
onInteraction={(interaction) => setLast(describe(interaction))}
|
|
4742
4852
|
>
|
|
@@ -4760,7 +4870,7 @@ On a `contextmenu` interaction, open `ContextMenu` from @umriss-ui/core at `clie
|
|
|
4760
4870
|
```tsx
|
|
4761
4871
|
import { useState } from "react";
|
|
4762
4872
|
import { ContextMenu, MenuItem } from "@umriss-ui/core";
|
|
4763
|
-
import {
|
|
4873
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
4764
4874
|
import type { ScheduleInteraction, Subtask, Task } from "@umriss-ui/schedule";
|
|
4765
4875
|
|
|
4766
4876
|
/* Data from the operations world, written out here so the example runs on its own. */
|
|
@@ -4823,6 +4933,7 @@ const START: readonly Subtask[] = ONCALL.map((duty) => ({
|
|
|
4823
4933
|
}));
|
|
4824
4934
|
|
|
4825
4935
|
export default function OpenAContextMenu() {
|
|
4936
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 6), day(23, 12)] } });
|
|
4826
4937
|
const [rota, setRota] = useState<readonly Subtask[]>(START);
|
|
4827
4938
|
const [menu, setMenu] = useState<{ interaction: ScheduleInteraction; duty: Subtask } | null>(null);
|
|
4828
4939
|
|
|
@@ -4836,7 +4947,6 @@ export default function OpenAContextMenu() {
|
|
|
4836
4947
|
<>
|
|
4837
4948
|
<Schedule
|
|
4838
4949
|
ariaLabel="On-call rota, 16 to 23 March"
|
|
4839
|
-
initialDomain={[day(16, 6), day(23, 12)]}
|
|
4840
4950
|
height={420}
|
|
4841
4951
|
onInteraction={onInteraction}
|
|
4842
4952
|
>
|
|
@@ -4912,7 +5022,7 @@ type ScheduleHit =
|
|
|
4912
5022
|
Names what the pointer rests on - a subtask with its times and findings, a dependency with its lag - without a click (also called a hover card). Replace its content when your application knows more than the schedule.
|
|
4913
5023
|
|
|
4914
5024
|
```ts
|
|
4915
|
-
import {
|
|
5025
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4916
5026
|
```
|
|
4917
5027
|
|
|
4918
5028
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/tooltip/
|
|
@@ -4928,7 +5038,7 @@ The keyboard's active subtask shows the same tooltip as the pointer.
|
|
|
4928
5038
|
Rest the pointer on a leg: the tooltip names the consignment, its times, loading and unloading, and every finding on it.
|
|
4929
5039
|
|
|
4930
5040
|
```tsx
|
|
4931
|
-
import {
|
|
5041
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4932
5042
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
4933
5043
|
|
|
4934
5044
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -4960,8 +5070,9 @@ const TRANSFERS: readonly Dependency[] = [
|
|
|
4960
5070
|
];
|
|
4961
5071
|
|
|
4962
5072
|
export default function DefaultTooltip() {
|
|
5073
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
4963
5074
|
return (
|
|
4964
|
-
<Schedule ariaLabel="Three vehicles with the default tooltip"
|
|
5075
|
+
<Schedule ariaLabel="Three vehicles with the default tooltip" height={196}>
|
|
4965
5076
|
{VEHICLES.map((vehicle) => (
|
|
4966
5077
|
<Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
4967
5078
|
))}
|
|
@@ -4978,7 +5089,7 @@ Pass `tooltip` a function to show what only your application knows - here the lo
|
|
|
4978
5089
|
|
|
4979
5090
|
```tsx
|
|
4980
5091
|
import { Badge, Stack, Text } from "@umriss-ui/core";
|
|
4981
|
-
import {
|
|
5092
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4982
5093
|
import type { Dependency, ScheduleTooltipTarget, Subtask, Task } from "@umriss-ui/schedule";
|
|
4983
5094
|
|
|
4984
5095
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -5030,10 +5141,10 @@ function LoadTooltip({ target }: { target: ScheduleTooltipTarget }) {
|
|
|
5030
5141
|
}
|
|
5031
5142
|
|
|
5032
5143
|
export default function OwnTooltip() {
|
|
5144
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
5033
5145
|
return (
|
|
5034
5146
|
<Schedule
|
|
5035
5147
|
ariaLabel="Three vehicles with the load in the tooltip"
|
|
5036
|
-
initialDomain={[at(5, 30), at(18)]}
|
|
5037
5148
|
height={196}
|
|
5038
5149
|
tooltip={(target) => <LoadTooltip target={target} />}
|
|
5039
5150
|
>
|
|
@@ -5054,7 +5165,7 @@ Set `tooltip={false}` when a panel of your own shows the item under the pointer,
|
|
|
5054
5165
|
```tsx
|
|
5055
5166
|
import { useState } from "react";
|
|
5056
5167
|
import { Badge, Card, CardBody, Stack, Text } from "@umriss-ui/core";
|
|
5057
|
-
import {
|
|
5168
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5058
5169
|
import type { ScheduleInteraction } from "@umriss-ui/schedule";
|
|
5059
5170
|
|
|
5060
5171
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
@@ -5141,6 +5252,7 @@ const WEB = PEOPLE.filter((person) => person.team === "Web");
|
|
|
5141
5252
|
const WEB_WORK = WORK.filter((item) => WEB.some((person) => person.id === item.lane));
|
|
5142
5253
|
|
|
5143
5254
|
export default function DetailPanelInstead() {
|
|
5255
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(9), day(21)] } });
|
|
5144
5256
|
const [item, setItem] = useState<WorkItem | null>(null);
|
|
5145
5257
|
|
|
5146
5258
|
const onInteraction = (interaction: ScheduleInteraction) => {
|
|
@@ -5153,7 +5265,6 @@ export default function DetailPanelInstead() {
|
|
|
5153
5265
|
<Stack gap={3}>
|
|
5154
5266
|
<Schedule
|
|
5155
5267
|
ariaLabel="Sprint 14 of the web team"
|
|
5156
|
-
initialDomain={[day(9), day(21)]}
|
|
5157
5268
|
height={260}
|
|
5158
5269
|
tooltip={false}
|
|
5159
5270
|
onInteraction={onInteraction}
|
|
@@ -5196,7 +5307,7 @@ The target says what is hovered: a bar brings its `subtask` and the `blocked` ti
|
|
|
5196
5307
|
|
|
5197
5308
|
```tsx
|
|
5198
5309
|
import { Stack, Text } from "@umriss-ui/core";
|
|
5199
|
-
import {
|
|
5310
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5200
5311
|
import type { BlockedTime, Dependency, ScheduleTooltipTarget, Subtask, Task } from "@umriss-ui/schedule";
|
|
5201
5312
|
|
|
5202
5313
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -5250,8 +5361,9 @@ function Covers({ target }: { target: ScheduleTooltipTarget }) {
|
|
|
5250
5361
|
}
|
|
5251
5362
|
|
|
5252
5363
|
export default function SayWhatABarCoversAndALineJoins() {
|
|
5364
|
+
const { Schedule, Lane, Subtasks, Dependencies, BlockedTimes } = useSchedule({ initialView: { domain: [at(6, 30), at(15, 30)] } });
|
|
5253
5365
|
return (
|
|
5254
|
-
<Schedule ariaLabel="Design, build and test, with leave under the build"
|
|
5366
|
+
<Schedule ariaLabel="Design, build and test, with leave under the build" height={188} tooltip={(target) => <Covers target={target} />}>
|
|
5255
5367
|
<Lane id="noah" label="Noah Fischer" />
|
|
5256
5368
|
<Lane id="arjun" label="Arjun Mehta" />
|
|
5257
5369
|
<Lane id="eva" label="Eva Novak" />
|
|
@@ -5284,17 +5396,163 @@ The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
|
5284
5396
|
| `to` | [`Subtask`](#/subtasks/type-Subtask)` \| undefined` | — | The subtask it reaches.<br>Shown in: [Say what a bar covers and a line joins](#/tooltip/say-what-a-bar-covers-and-a-line-joins) |
|
|
5285
5397
|
| `violated` | [`ViolatedDependency`](#/findings/type-ViolatedDependency)` \| undefined` | — | Its finding, where it is violated.<br>Shown in: [Write your own tooltip content](#/tooltip/own-tooltip) |
|
|
5286
5398
|
|
|
5399
|
+
### View
|
|
5400
|
+
|
|
5401
|
+
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.
|
|
5402
|
+
|
|
5403
|
+
```ts
|
|
5404
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5405
|
+
```
|
|
5406
|
+
|
|
5407
|
+
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/view/
|
|
5408
|
+
|
|
5409
|
+
The schedule keeps its view itself and puts it nowhere, not in the address and not in any storage. `view` on what [`useSchedule`](#/api/useSchedule) hands back holds it, and `onViewChange` reports every change of it - a pan or zoom at most once per frame; keep it where the application keeps things and hand it back through `initialView`.
|
|
5410
|
+
|
|
5411
|
+
A view that differs in content from the last one handed in applies at once, and what it leaves out goes back to its default; the same one again changes nothing. Without a span the schedule shows the extent of its subtasks, within `zoomLimits`. A group no `LaneGroup` declares falls out.
|
|
5412
|
+
|
|
5413
|
+
`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`).
|
|
5414
|
+
|
|
5415
|
+
#### Examples
|
|
5416
|
+
|
|
5417
|
+
##### Keep and restore a view
|
|
5418
|
+
|
|
5419
|
+
`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 hand it back through `initialView`. Pan, zoom, fold, keep, reload, restore.
|
|
5420
|
+
|
|
5421
|
+
```tsx
|
|
5422
|
+
import { useState } from "react";
|
|
5423
|
+
import { Button, Stack, Text } from "@umriss-ui/core";
|
|
5424
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5425
|
+
import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
|
|
5426
|
+
|
|
5427
|
+
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
5428
|
+
const min = (n: number) => n * 60_000;
|
|
5429
|
+
|
|
5430
|
+
const START: ScheduleView = { domain: [at(6), at(16)] };
|
|
5431
|
+
const KEPT = "umriss-schedule-demo-view";
|
|
5432
|
+
|
|
5433
|
+
/* Storage can be missing or refuse - a private window, a blocked site. The
|
|
5434
|
+
view is then simply not kept. */
|
|
5435
|
+
function readKept(): ScheduleView | null {
|
|
5436
|
+
try {
|
|
5437
|
+
const stored = localStorage.getItem(KEPT);
|
|
5438
|
+
return stored === null ? null : (JSON.parse(stored) as ScheduleView);
|
|
5439
|
+
} catch {
|
|
5440
|
+
return null;
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
|
|
5444
|
+
function keep(view: ScheduleView): void {
|
|
5445
|
+
try {
|
|
5446
|
+
localStorage.setItem(KEPT, JSON.stringify(view));
|
|
5447
|
+
} catch {
|
|
5448
|
+
/* Not kept; the schedule works on regardless. */
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
|
|
5452
|
+
const TEAMS: Task[] = [
|
|
5453
|
+
{ id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
|
|
5454
|
+
{ id: "billing", name: "Billing run", color: "light-dark(#0d9488, #3cc7b8)" },
|
|
5455
|
+
];
|
|
5456
|
+
|
|
5457
|
+
const WORK: Subtask[] = [
|
|
5458
|
+
{ id: "p1", task: "portal", lane: "noah", from: at(7), to: at(9, 30), leadOut: min(15) },
|
|
5459
|
+
{ id: "p2", task: "portal", lane: "arjun", from: at(10), to: at(13), leadIn: min(30) },
|
|
5460
|
+
{ id: "b1", task: "billing", lane: "mei", from: at(8), to: at(11) },
|
|
5461
|
+
{ id: "b2", task: "billing", lane: "lukas", from: at(11, 30), to: at(14, 30), leadIn: min(15) },
|
|
5462
|
+
];
|
|
5463
|
+
|
|
5464
|
+
export default function KeepAndRestore() {
|
|
5465
|
+
/* The application holds the view the schedule reports; handing it another
|
|
5466
|
+
one is all it takes to restore. */
|
|
5467
|
+
const [view, setView] = useState<ScheduleView>(START);
|
|
5468
|
+
const [kept, setKept] = useState<ScheduleView | null>(readKept);
|
|
5469
|
+
const { Schedule, Lane, LaneGroup, Subtasks, setDomain } = useSchedule({ initialView: view, onViewChange: setView });
|
|
5470
|
+
|
|
5471
|
+
return (
|
|
5472
|
+
<Stack gap={3}>
|
|
5473
|
+
<Schedule ariaLabel="Two teams on Tuesday, 17 March" height={290}>
|
|
5474
|
+
<LaneGroup id="product" label="Product">
|
|
5475
|
+
<Lane id="noah" label="Noah Fischer" />
|
|
5476
|
+
<Lane id="arjun" label="Arjun Mehta" />
|
|
5477
|
+
</LaneGroup>
|
|
5478
|
+
<LaneGroup id="finance" label="Finance">
|
|
5479
|
+
<Lane id="mei" label="Mei Tanaka" />
|
|
5480
|
+
<Lane id="lukas" label="Lukas Brandt" />
|
|
5481
|
+
</LaneGroup>
|
|
5482
|
+
<Subtasks data={WORK} tasks={TEAMS} />
|
|
5483
|
+
</Schedule>
|
|
5484
|
+
<Stack gap={1}>
|
|
5485
|
+
<Text as="span" size="sm" tone="secondary">
|
|
5486
|
+
What the application would keep
|
|
5487
|
+
</Text>
|
|
5488
|
+
{/* JSON has no space to break at: without this it ran past a phone's card. */}
|
|
5489
|
+
<Text as="code" size="sm" mono data-role="view" style={{ overflowWrap: "anywhere" }}>
|
|
5490
|
+
{JSON.stringify(view)}
|
|
5491
|
+
</Text>
|
|
5492
|
+
</Stack>
|
|
5493
|
+
<Stack direction="row" gap={2} wrap>
|
|
5494
|
+
<Button
|
|
5495
|
+
size="sm"
|
|
5496
|
+
onClick={() => {
|
|
5497
|
+
keep(view);
|
|
5498
|
+
setKept(view);
|
|
5499
|
+
}}
|
|
5500
|
+
data-keep
|
|
5501
|
+
>
|
|
5502
|
+
Keep this view
|
|
5503
|
+
</Button>
|
|
5504
|
+
<Button size="sm" variant="ghost" disabled={kept === null} onClick={() => kept && setView(kept)} data-restore>
|
|
5505
|
+
Restore the kept view
|
|
5506
|
+
</Button>
|
|
5507
|
+
<Button size="sm" variant="ghost" onClick={() => setDomain(null)} data-whole-plan>
|
|
5508
|
+
Show the whole plan
|
|
5509
|
+
</Button>
|
|
5510
|
+
</Stack>
|
|
5511
|
+
</Stack>
|
|
5512
|
+
);
|
|
5513
|
+
}
|
|
5514
|
+
```
|
|
5515
|
+
|
|
5516
|
+
#### When to use something else
|
|
5517
|
+
|
|
5518
|
+
- Two schedules on the same hours → Linked schedules
|
|
5519
|
+
|
|
5520
|
+
#### Keyboard
|
|
5521
|
+
|
|
5522
|
+
The keys of [First schedule](#/schedule/keyboard-schedule) apply here.
|
|
5523
|
+
|
|
5524
|
+
#### API
|
|
5525
|
+
|
|
5526
|
+
##### `ScheduleOptions`
|
|
5527
|
+
|
|
5528
|
+
| Prop | Type | Default | Description |
|
|
5529
|
+
|---|---|---|---|
|
|
5530
|
+
| `initialView` | [`ScheduleView`](#type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 75 more |
|
|
5531
|
+
|
|
5532
|
+
###### Events
|
|
5533
|
+
|
|
5534
|
+
| Prop | Type | Default | Description |
|
|
5535
|
+
|---|---|---|---|
|
|
5536
|
+
| `onViewChange` | `(view: `[`ScheduleView`](#type-ScheduleView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another schedule as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the schedule starts with.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [Keep two schedules in step (Linked schedules)](#/linked-schedules/in-step), [Read a rota beside the incidents (Linked schedules)](#/linked-schedules/rota-beside-incidents) |
|
|
5537
|
+
|
|
5538
|
+
##### `ScheduleView`
|
|
5539
|
+
|
|
5540
|
+
| Prop | Type | Default | Description |
|
|
5541
|
+
|---|---|---|---|
|
|
5542
|
+
| `domain` | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule) and 75 more |
|
|
5543
|
+
| `folded` | `readonly string[]` | — | The **Lane group**s 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.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside), [Read a folded group (Lane groups)](#/lane-groups/the-miniature), [Start with groups folded (Lane groups)](#/lane-groups/start-with-groups-folded) |
|
|
5544
|
+
|
|
5287
5545
|
### Linked schedules
|
|
5288
5546
|
|
|
5289
5547
|
Keeps two schedules on the same hours: pan or zoom one and the other follows (also called synchronised views). Reach for it when two sets of lanes stand apart on a screen but belong to the same time.
|
|
5290
5548
|
|
|
5291
5549
|
```ts
|
|
5292
|
-
import {
|
|
5550
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5293
5551
|
```
|
|
5294
5552
|
|
|
5295
5553
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/linked-schedules/
|
|
5296
5554
|
|
|
5297
|
-
`
|
|
5555
|
+
Each schedule has its own [`useSchedule`](#/api/useSchedule). Hand both the view one of them reports, through `initialView`: a view that differs from the last one handed in applies at once, without a remount, and the same one again changes nothing, so the two never feed each other.
|
|
5298
5556
|
|
|
5299
5557
|
While a drag is in flight the two can stand a frame apart; they settle as soon as it ends.
|
|
5300
5558
|
|
|
@@ -5302,13 +5560,13 @@ While a drag is in flight the two can stand a frame apart; they settle as soon a
|
|
|
5302
5560
|
|
|
5303
5561
|
##### Keep two schedules in step
|
|
5304
5562
|
|
|
5305
|
-
Hand the
|
|
5563
|
+
Hand the view `onViewChange` reports to both schedules' `initialView`: pan or zoom either one and the other follows - no `key`, no remount.
|
|
5306
5564
|
|
|
5307
5565
|
```tsx
|
|
5308
5566
|
import { useState } from "react";
|
|
5309
5567
|
import { Stack, Text } from "@umriss-ui/core";
|
|
5310
|
-
import {
|
|
5311
|
-
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
5568
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5569
|
+
import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
|
|
5312
5570
|
|
|
5313
5571
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
5314
5572
|
const min = (n: number) => n * 60_000;
|
|
@@ -5343,27 +5601,34 @@ const LEGS: readonly Subtask[] = [
|
|
|
5343
5601
|
];
|
|
5344
5602
|
|
|
5345
5603
|
const on = (lanes: readonly { id: string }[]) => LEGS.filter((leg) => lanes.some((lane) => lane.id === leg.lane));
|
|
5604
|
+
const DIESEL_LEGS = on(DIESEL);
|
|
5605
|
+
const ELECTRIC_LEGS = on(ELECTRIC);
|
|
5346
5606
|
|
|
5347
5607
|
const time = (instant: number) => new Date(instant).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
5348
5608
|
|
|
5349
5609
|
export default function InStep() {
|
|
5350
|
-
|
|
5610
|
+
/* One view for both: each schedule reports its own, and goes to the other's
|
|
5611
|
+
because it differs from the last one handed in. */
|
|
5612
|
+
const [view, setView] = useState<ScheduleView>({ domain: [at(5, 30), at(18)] });
|
|
5613
|
+
const diesel = useSchedule({ initialView: view, onViewChange: setView });
|
|
5614
|
+
const electric = useSchedule({ initialView: view, onViewChange: setView });
|
|
5615
|
+
const span = view.domain ?? [at(5, 30), at(18)];
|
|
5351
5616
|
return (
|
|
5352
5617
|
<Stack gap={2}>
|
|
5353
|
-
<Schedule ariaLabel="Diesel vehicles on Tuesday, 17 March"
|
|
5618
|
+
<diesel.Schedule ariaLabel="Diesel vehicles on Tuesday, 17 March" height={240}>
|
|
5354
5619
|
{DIESEL.map((vehicle) => (
|
|
5355
|
-
<Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
5620
|
+
<diesel.Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
5356
5621
|
))}
|
|
5357
|
-
<Subtasks data={
|
|
5358
|
-
</Schedule>
|
|
5359
|
-
<Schedule ariaLabel="Electric vans, the same hours"
|
|
5622
|
+
<diesel.Subtasks data={DIESEL_LEGS} tasks={CONSIGNMENTS} />
|
|
5623
|
+
</diesel.Schedule>
|
|
5624
|
+
<electric.Schedule ariaLabel="Electric vans, the same hours" height={152}>
|
|
5360
5625
|
{ELECTRIC.map((vehicle) => (
|
|
5361
|
-
<Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
5626
|
+
<electric.Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
5362
5627
|
))}
|
|
5363
|
-
<Subtasks data={
|
|
5364
|
-
</Schedule>
|
|
5628
|
+
<electric.Subtasks data={ELECTRIC_LEGS} tasks={CONSIGNMENTS} />
|
|
5629
|
+
</electric.Schedule>
|
|
5365
5630
|
<Text size="xs" mono tone="muted" data-span>
|
|
5366
|
-
{time(
|
|
5631
|
+
{time(span[0])} – {time(span[1])}
|
|
5367
5632
|
</Text>
|
|
5368
5633
|
</Stack>
|
|
5369
5634
|
);
|
|
@@ -5377,8 +5642,8 @@ Two schedules with different lanes over one span: who was on call, above the inc
|
|
|
5377
5642
|
```tsx
|
|
5378
5643
|
import { useState } from "react";
|
|
5379
5644
|
import { Stack } from "@umriss-ui/core";
|
|
5380
|
-
import {
|
|
5381
|
-
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
5645
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5646
|
+
import type { ScheduleView, Subtask, Task } from "@umriss-ui/schedule";
|
|
5382
5647
|
|
|
5383
5648
|
/* Data from the operations world, written out here so the example runs on its own. */
|
|
5384
5649
|
|
|
@@ -5505,20 +5770,22 @@ const PAGED: readonly Subtask[] = INCIDENTS.map((incident) => ({
|
|
|
5505
5770
|
const HIT = SERVICES.filter((service) => PAGED.some((incident) => incident.lane === service.id));
|
|
5506
5771
|
|
|
5507
5772
|
export default function RotaBesideIncidents() {
|
|
5508
|
-
const [
|
|
5773
|
+
const [view, setView] = useState<ScheduleView>({ domain: [day(16, 6), day(17, 14)] });
|
|
5774
|
+
const rota = useSchedule({ initialView: view, onViewChange: setView });
|
|
5775
|
+
const incidents = useSchedule({ initialView: view, onViewChange: setView });
|
|
5509
5776
|
return (
|
|
5510
5777
|
<Stack gap={2}>
|
|
5511
|
-
<Schedule ariaLabel="Who was on call"
|
|
5512
|
-
<Lane id="primary" label="Primary" />
|
|
5513
|
-
<Lane id="secondary" label="Secondary" />
|
|
5514
|
-
<Subtasks data={DUTIES} tasks={PEOPLE} />
|
|
5515
|
-
</Schedule>
|
|
5516
|
-
<Schedule ariaLabel="Incidents, the same hours"
|
|
5778
|
+
<rota.Schedule ariaLabel="Who was on call" height={140}>
|
|
5779
|
+
<rota.Lane id="primary" label="Primary" />
|
|
5780
|
+
<rota.Lane id="secondary" label="Secondary" />
|
|
5781
|
+
<rota.Subtasks data={DUTIES} tasks={PEOPLE} />
|
|
5782
|
+
</rota.Schedule>
|
|
5783
|
+
<incidents.Schedule ariaLabel="Incidents, the same hours" height={290}>
|
|
5517
5784
|
{HIT.map((service) => (
|
|
5518
|
-
<Lane key={service.id} id={service.id} label={service.name} />
|
|
5785
|
+
<incidents.Lane key={service.id} id={service.id} label={service.name} />
|
|
5519
5786
|
))}
|
|
5520
|
-
<Subtasks data={PAGED} tasks={SEVERITIES} />
|
|
5521
|
-
</Schedule>
|
|
5787
|
+
<incidents.Subtasks data={PAGED} tasks={SEVERITIES} />
|
|
5788
|
+
</incidents.Schedule>
|
|
5522
5789
|
</Stack>
|
|
5523
5790
|
);
|
|
5524
5791
|
}
|
|
@@ -5538,16 +5805,9 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
5538
5805
|
|
|
5539
5806
|
| Prop | Type | Default | Description |
|
|
5540
5807
|
|---|---|---|---|
|
|
5541
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
5542
5808
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
5543
5809
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
5544
5810
|
|
|
5545
|
-
###### Events
|
|
5546
|
-
|
|
5547
|
-
| Prop | Type | Default | Description |
|
|
5548
|
-
|---|---|---|---|
|
|
5549
|
-
| [`onDomainChange`](#/schedule/ScheduleProps-onDomainChange) | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back. |
|
|
5550
|
-
|
|
5551
5811
|
###### Accessibility
|
|
5552
5812
|
|
|
5553
5813
|
| Prop | Type | Default | Description |
|
|
@@ -5589,9 +5849,27 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
5589
5849
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
5590
5850
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
5591
5851
|
|
|
5852
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
5853
|
+
|
|
5854
|
+
| Prop | Type | Default | Description |
|
|
5855
|
+
|---|---|---|---|
|
|
5856
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
5857
|
+
|
|
5858
|
+
###### Events
|
|
5859
|
+
|
|
5860
|
+
| Prop | Type | Default | Description |
|
|
5861
|
+
|---|---|---|---|
|
|
5862
|
+
| [`onViewChange`](#/view/ScheduleOptions-onViewChange) | `(view: `[`ScheduleView`](#/view/type-ScheduleView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another schedule as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the schedule starts with. |
|
|
5863
|
+
|
|
5864
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
5865
|
+
|
|
5866
|
+
| Prop | Type | Default | Description |
|
|
5867
|
+
|---|---|---|---|
|
|
5868
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
5869
|
+
|
|
5592
5870
|
#### Known limits
|
|
5593
5871
|
|
|
5594
|
-
- Only the
|
|
5872
|
+
- Only the view is shared - the span and the folded groups, not the selection, the hover or the vertical scroll.
|
|
5595
5873
|
|
|
5596
5874
|
What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
|
|
5597
5875
|
|
|
@@ -5600,14 +5878,14 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
5600
5878
|
Converts between a point on the screen and a time on a lane, so marks of your own - a cut-off, a delivery window, a pin - stand at the right place beside the plan.
|
|
5601
5879
|
|
|
5602
5880
|
```ts
|
|
5603
|
-
import {
|
|
5881
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5604
5882
|
```
|
|
5605
5883
|
|
|
5606
5884
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/handle/
|
|
5607
5885
|
|
|
5608
5886
|
The handle holds three functions and nothing else: `clientPointOf(time, lane?)`, `positionAt(clientX, clientY)` and `visibleDomain()`. Everything else the schedule does is props.
|
|
5609
5887
|
|
|
5610
|
-
Place your marks again whenever `
|
|
5888
|
+
Place your marks again whenever `view.domain` changes.
|
|
5611
5889
|
|
|
5612
5890
|
#### Examples
|
|
5613
5891
|
|
|
@@ -5618,7 +5896,7 @@ Place your marks again whenever `onDomainChange` reports a new span.
|
|
|
5618
5896
|
```tsx
|
|
5619
5897
|
import { useEffect, useRef, useState } from "react";
|
|
5620
5898
|
import { Stack, Text } from "@umriss-ui/core";
|
|
5621
|
-
import {
|
|
5899
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5622
5900
|
import type { ScheduleHandle, Subtask, Task } from "@umriss-ui/schedule";
|
|
5623
5901
|
|
|
5624
5902
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -5648,9 +5926,9 @@ const CUT_OFF = at(14);
|
|
|
5648
5926
|
const clock = (time: number) => new Date(time).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
5649
5927
|
|
|
5650
5928
|
export default function PlaceAMark() {
|
|
5929
|
+
const { Schedule, Lane, Subtasks, view } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
5651
5930
|
const plan = useRef<ScheduleHandle>(null);
|
|
5652
5931
|
const host = useRef<HTMLDivElement>(null);
|
|
5653
|
-
const [domain, setDomain] = useState<readonly [number, number]>([at(5, 30), at(18)]);
|
|
5654
5932
|
const [pin, setPin] = useState<number | null>(null);
|
|
5655
5933
|
const [under, setUnder] = useState("Move the pointer over the plan");
|
|
5656
5934
|
|
|
@@ -5659,7 +5937,7 @@ export default function PlaceAMark() {
|
|
|
5659
5937
|
const point = plan.current?.clientPointOf(CUT_OFF);
|
|
5660
5938
|
const left = host.current?.getBoundingClientRect().left;
|
|
5661
5939
|
setPin(point == null || left === undefined ? null : point.x - left);
|
|
5662
|
-
}, [domain]);
|
|
5940
|
+
}, [view.domain]);
|
|
5663
5941
|
|
|
5664
5942
|
return (
|
|
5665
5943
|
<Stack gap={2}>
|
|
@@ -5673,9 +5951,7 @@ export default function PlaceAMark() {
|
|
|
5673
5951
|
<Schedule
|
|
5674
5952
|
ref={plan}
|
|
5675
5953
|
ariaLabel="Three vehicles with a cut-off of the application's own"
|
|
5676
|
-
initialDomain={domain}
|
|
5677
5954
|
height={196}
|
|
5678
|
-
onDomainChange={setDomain}
|
|
5679
5955
|
onInteraction={(interaction) => {
|
|
5680
5956
|
/* The interaction carries time and lane already; `positionAt` is for
|
|
5681
5957
|
listeners of your own, which have only a point. */
|
|
@@ -5710,7 +5986,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
5710
5986
|
|
|
5711
5987
|
| Prop | Type | Default | Description |
|
|
5712
5988
|
|---|---|---|---|
|
|
5713
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
5714
5989
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
5715
5990
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
5716
5991
|
|
|
@@ -5719,7 +5994,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
5719
5994
|
| Prop | Type | Default | Description |
|
|
5720
5995
|
|---|---|---|---|
|
|
5721
5996
|
| [`onInteraction`](#/schedule/ScheduleProps-onInteraction) | `(interaction: `[`ScheduleInteraction`](#/interactions/type-ScheduleInteraction)`) => void` | — | Click, context menu and hover, with what the pointer is on and where. While it is set, a right-click opens no browser menu. |
|
|
5722
|
-
| [`onDomainChange`](#/schedule/ScheduleProps-onDomainChange) | `(domain: readonly [number, number]) => void` | — | The visible time span after the planner panned or zoomed, as two wall-clock instants - for keeping a second schedule or a chart in step. A span handed in through `initialDomain` is not reported back. |
|
|
5723
5997
|
|
|
5724
5998
|
###### Accessibility
|
|
5725
5999
|
|
|
@@ -5768,6 +6042,18 @@ From `ScheduleInteraction` — the full table stands on [Interactions](#/interac
|
|
|
5768
6042
|
| [`clientX`](#/interactions/ScheduleInteraction-clientX) *required* | `number` | — | The pointer's horizontal client coordinate - where a `ContextMenu` opens. |
|
|
5769
6043
|
| [`clientY`](#/interactions/ScheduleInteraction-clientY) *required* | `number` | — | The pointer's vertical client coordinate. |
|
|
5770
6044
|
|
|
6045
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
6046
|
+
|
|
6047
|
+
| Prop | Type | Default | Description |
|
|
6048
|
+
|---|---|---|---|
|
|
6049
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
6050
|
+
|
|
6051
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
6052
|
+
|
|
6053
|
+
| Prop | Type | Default | Description |
|
|
6054
|
+
|---|---|---|---|
|
|
6055
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
6056
|
+
|
|
5771
6057
|
### Findings as data
|
|
5772
6058
|
|
|
5773
6059
|
Lists what is wrong with a plan - double bookings on a lane, dependencies that do not fit, work in blocked time - from the same data the schedule draws. Reach for it to list, count or filter the problems beside the picture.
|
|
@@ -5790,7 +6076,7 @@ Lead-in and lead-out count as occupied time; touching at a shared edge is no ove
|
|
|
5790
6076
|
|
|
5791
6077
|
```tsx
|
|
5792
6078
|
import { Stack, Text } from "@umriss-ui/core";
|
|
5793
|
-
import {
|
|
6079
|
+
import { findings, useSchedule } from "@umriss-ui/schedule";
|
|
5794
6080
|
import type { BlockedTime, Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
5795
6081
|
|
|
5796
6082
|
const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
|
|
@@ -5816,11 +6102,12 @@ const when = (instant: number) => new Date(instant).toLocaleString("en-GB", { we
|
|
|
5816
6102
|
const hours = (ms: number) => Math.round(ms / 3_600_000);
|
|
5817
6103
|
|
|
5818
6104
|
export default function ListTheFindings() {
|
|
6105
|
+
const { Schedule, Lane, Subtasks, Dependencies, BlockedTimes } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
|
|
5819
6106
|
const found = findings(WORK, HANDOVERS, LEAVE);
|
|
5820
6107
|
|
|
5821
6108
|
return (
|
|
5822
6109
|
<Stack gap={3}>
|
|
5823
|
-
<Schedule ariaLabel="Three people's week with its findings"
|
|
6110
|
+
<Schedule ariaLabel="Three people's week with its findings" height={184}>
|
|
5824
6111
|
<Lane id="noah" label="Noah Fischer" />
|
|
5825
6112
|
<Lane id="chloe" label="Chloe Durand" />
|
|
5826
6113
|
<Lane id="arjun" label="Arjun Mehta" />
|
|
@@ -5856,7 +6143,7 @@ When all three lists [`findings`](#/api/findings) returns are empty, say so plai
|
|
|
5856
6143
|
|
|
5857
6144
|
```tsx
|
|
5858
6145
|
import { EmptyState, Stack } from "@umriss-ui/core";
|
|
5859
|
-
import {
|
|
6146
|
+
import { findings, useSchedule } from "@umriss-ui/schedule";
|
|
5860
6147
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
5861
6148
|
|
|
5862
6149
|
const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
|
|
@@ -5869,12 +6156,13 @@ const WORK: readonly Subtask[] = [
|
|
|
5869
6156
|
];
|
|
5870
6157
|
|
|
5871
6158
|
export default function NothingToFix() {
|
|
6159
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
|
|
5872
6160
|
const found = findings(WORK, []);
|
|
5873
6161
|
const count = found.overlaps.length + found.violatedDependencies.length + found.inBlockedTime.length;
|
|
5874
6162
|
|
|
5875
6163
|
return (
|
|
5876
6164
|
<Stack gap={3}>
|
|
5877
|
-
<Schedule ariaLabel="Two developers' week, without findings"
|
|
6165
|
+
<Schedule ariaLabel="Two developers' week, without findings" height={140}>
|
|
5878
6166
|
<Lane id="hana" label="Hana Sato" />
|
|
5879
6167
|
<Lane id="kofi" label="Kofi Mensah" />
|
|
5880
6168
|
<Subtasks data={WORK} tasks={PROJECTS} />
|
|
@@ -5891,7 +6179,7 @@ export default function NothingToFix() {
|
|
|
5891
6179
|
|
|
5892
6180
|
```tsx
|
|
5893
6181
|
import { Badge, Stack } from "@umriss-ui/core";
|
|
5894
|
-
import {
|
|
6182
|
+
import { overlaps, useSchedule, violatedDependencies } from "@umriss-ui/schedule";
|
|
5895
6183
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
5896
6184
|
|
|
5897
6185
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -5921,6 +6209,7 @@ const TRANSFERS: readonly Dependency[] = [
|
|
|
5921
6209
|
];
|
|
5922
6210
|
|
|
5923
6211
|
export default function CountPerKind() {
|
|
6212
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
5924
6213
|
const doubleBooked = overlaps(LEGS).length;
|
|
5925
6214
|
const late = violatedDependencies(LEGS, TRANSFERS).length;
|
|
5926
6215
|
|
|
@@ -5930,7 +6219,7 @@ export default function CountPerKind() {
|
|
|
5930
6219
|
<Badge tone={doubleBooked > 0 ? "warning" : "success"}>{doubleBooked} double-booked</Badge>
|
|
5931
6220
|
<Badge tone={late > 0 ? "warning" : "success"}>{late} transfers too short</Badge>
|
|
5932
6221
|
</Stack>
|
|
5933
|
-
<Schedule ariaLabel="The day's legs with their findings counted"
|
|
6222
|
+
<Schedule ariaLabel="The day's legs with their findings counted" height={196}>
|
|
5934
6223
|
{VEHICLES.map((vehicle) => (
|
|
5935
6224
|
<Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
|
|
5936
6225
|
))}
|
|
@@ -5992,7 +6281,7 @@ Direct manipulation that changes nothing by itself: the schedule reports, the ap
|
|
|
5992
6281
|
Drag a bar to a new time or onto another lane (also called drag-and-drop rescheduling). A ghost shows where it would land and what that would cause; the drop reports the change for your application to apply.
|
|
5993
6282
|
|
|
5994
6283
|
```ts
|
|
5995
|
-
import {
|
|
6284
|
+
import { useSchedule, applyIntent } from "@umriss-ui/schedule";
|
|
5996
6285
|
```
|
|
5997
6286
|
|
|
5998
6287
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/move-and-lane/
|
|
@@ -6011,7 +6300,7 @@ List `move` in `intents` and apply what `onIntent` reports with [`applyIntent`](
|
|
|
6011
6300
|
|
|
6012
6301
|
```tsx
|
|
6013
6302
|
import { useState } from "react";
|
|
6014
|
-
import {
|
|
6303
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6015
6304
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
6016
6305
|
|
|
6017
6306
|
const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
|
|
@@ -6024,11 +6313,11 @@ const START: readonly Subtask[] = [
|
|
|
6024
6313
|
];
|
|
6025
6314
|
|
|
6026
6315
|
export default function MoveInTime() {
|
|
6316
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
|
|
6027
6317
|
const [work, setWork] = useState(START);
|
|
6028
6318
|
return (
|
|
6029
6319
|
<Schedule
|
|
6030
6320
|
ariaLabel="Two people's work this week"
|
|
6031
|
-
initialDomain={[day(16, 0), day(21, 0)]}
|
|
6032
6321
|
height={140}
|
|
6033
6322
|
intents={["move"]}
|
|
6034
6323
|
onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
|
|
@@ -6048,7 +6337,7 @@ Add `lane` to `intents`: a drop reports a move and a lane change separately, and
|
|
|
6048
6337
|
```tsx
|
|
6049
6338
|
import { useState } from "react";
|
|
6050
6339
|
import { Stack, Text } from "@umriss-ui/core";
|
|
6051
|
-
import {
|
|
6340
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6052
6341
|
import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
6053
6342
|
|
|
6054
6343
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6084,6 +6373,7 @@ const TRANSFERS: readonly Dependency[] = [
|
|
|
6084
6373
|
];
|
|
6085
6374
|
|
|
6086
6375
|
export default function MoveToAnotherLane() {
|
|
6376
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
6087
6377
|
const [legs, setLegs] = useState(LEGS);
|
|
6088
6378
|
const [last, setLast] = useState<Intent | null>(null);
|
|
6089
6379
|
|
|
@@ -6091,7 +6381,6 @@ export default function MoveToAnotherLane() {
|
|
|
6091
6381
|
<Stack gap={3}>
|
|
6092
6382
|
<Schedule
|
|
6093
6383
|
ariaLabel="Five vehicles on Tuesday, 17 March, editable"
|
|
6094
|
-
initialDomain={[at(5, 30), at(18)]}
|
|
6095
6384
|
height={284}
|
|
6096
6385
|
intents={["move", "lane"]}
|
|
6097
6386
|
onIntent={(intent) => {
|
|
@@ -6120,7 +6409,7 @@ Your `onIntent` decides: here it applies a drop only when [`overlaps`](#/api/ove
|
|
|
6120
6409
|
```tsx
|
|
6121
6410
|
import { useRef, useState } from "react";
|
|
6122
6411
|
import { Stack, Text } from "@umriss-ui/core";
|
|
6123
|
-
import {
|
|
6412
|
+
import { applyIntent, overlaps, useSchedule } from "@umriss-ui/schedule";
|
|
6124
6413
|
import type { Intent, Subtask } from "@umriss-ui/schedule";
|
|
6125
6414
|
|
|
6126
6415
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
@@ -6207,6 +6496,7 @@ const WEB = PEOPLE.filter((person) => person.team === "Web" && WORK.some((item)
|
|
|
6207
6496
|
const START: readonly Subtask[] = WORK.filter((item) => WEB.some((person) => person.id === item.lane));
|
|
6208
6497
|
|
|
6209
6498
|
export default function RefuseADoubleBooking() {
|
|
6499
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(9), day(21)] } });
|
|
6210
6500
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
6211
6501
|
/* A drop onto another lane at another time reports two intents in one
|
|
6212
6502
|
tick; each is decided on the plan the one before it left. */
|
|
@@ -6231,7 +6521,6 @@ export default function RefuseADoubleBooking() {
|
|
|
6231
6521
|
<Stack gap={3}>
|
|
6232
6522
|
<Schedule
|
|
6233
6523
|
ariaLabel="Sprint 14 of the web team, editable"
|
|
6234
|
-
initialDomain={[day(9), day(21)]}
|
|
6235
6524
|
height={240}
|
|
6236
6525
|
intents={["move", "lane"]}
|
|
6237
6526
|
onIntent={onIntent}
|
|
@@ -6270,7 +6559,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
6270
6559
|
|
|
6271
6560
|
| Prop | Type | Default | Description |
|
|
6272
6561
|
|---|---|---|---|
|
|
6273
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
6274
6562
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
6275
6563
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
6276
6564
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -6337,6 +6625,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
6337
6625
|
| [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
6338
6626
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
6339
6627
|
|
|
6628
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
6629
|
+
|
|
6630
|
+
| Prop | Type | Default | Description |
|
|
6631
|
+
|---|---|---|---|
|
|
6632
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
6633
|
+
|
|
6634
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
6635
|
+
|
|
6636
|
+
| Prop | Type | Default | Description |
|
|
6637
|
+
|---|---|---|---|
|
|
6638
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
6639
|
+
|
|
6340
6640
|
From `Overlap` — the full table stands on [Findings as data](#/findings/type-Overlap).
|
|
6341
6641
|
|
|
6342
6642
|
| Prop | Type | Default | Description |
|
|
@@ -6357,7 +6657,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
6357
6657
|
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.
|
|
6358
6658
|
|
|
6359
6659
|
```ts
|
|
6360
|
-
import {
|
|
6660
|
+
import { useSchedule, applyIntent } from "@umriss-ui/schedule";
|
|
6361
6661
|
```
|
|
6362
6662
|
|
|
6363
6663
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/stretch/
|
|
@@ -6374,7 +6674,7 @@ With `stretch` in `intents`, either edge of a bar drags to a new start or end; t
|
|
|
6374
6674
|
|
|
6375
6675
|
```tsx
|
|
6376
6676
|
import { useState } from "react";
|
|
6377
|
-
import {
|
|
6677
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6378
6678
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
6379
6679
|
|
|
6380
6680
|
const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
|
|
@@ -6387,11 +6687,11 @@ const START: readonly Subtask[] = [
|
|
|
6387
6687
|
];
|
|
6388
6688
|
|
|
6389
6689
|
export default function StretchTheMainTime() {
|
|
6690
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16, 0), day(21, 0)] } });
|
|
6390
6691
|
const [work, setWork] = useState(START);
|
|
6391
6692
|
return (
|
|
6392
6693
|
<Schedule
|
|
6393
6694
|
ariaLabel="Two estimates to adjust"
|
|
6394
|
-
initialDomain={[day(16, 0), day(21, 0)]}
|
|
6395
6695
|
height={140}
|
|
6396
6696
|
intents={["stretch"]}
|
|
6397
6697
|
onIntent={(intent) => setWork((current) => current.map((item) => applyIntent(item, intent)))}
|
|
@@ -6410,7 +6710,7 @@ Add `leadIn` and `leadOut`: a selected tour shows a grip for its loading and its
|
|
|
6410
6710
|
|
|
6411
6711
|
```tsx
|
|
6412
6712
|
import { useState } from "react";
|
|
6413
|
-
import {
|
|
6713
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6414
6714
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
6415
6715
|
|
|
6416
6716
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6425,12 +6725,12 @@ const START: Subtask[] = [
|
|
|
6425
6725
|
];
|
|
6426
6726
|
|
|
6427
6727
|
export default function ChangeLeadInAndLeadOut() {
|
|
6728
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(13, 30)] } });
|
|
6428
6729
|
const [tours, setTours] = useState(START);
|
|
6429
6730
|
|
|
6430
6731
|
return (
|
|
6431
6732
|
<Schedule
|
|
6432
6733
|
ariaLabel="Two tours from North depot"
|
|
6433
|
-
initialDomain={[at(6), at(13, 30)]}
|
|
6434
6734
|
height={150}
|
|
6435
6735
|
intents={["stretch", "leadIn", "leadOut"]}
|
|
6436
6736
|
onIntent={(intent) => setTours((current) => current.map((tour) => applyIntent(tour, intent)))}
|
|
@@ -6457,7 +6757,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
6457
6757
|
|
|
6458
6758
|
| Prop | Type | Default | Description |
|
|
6459
6759
|
|---|---|---|---|
|
|
6460
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
6461
6760
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
6462
6761
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
6463
6762
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -6509,6 +6808,18 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
6509
6808
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
6510
6809
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
6511
6810
|
|
|
6811
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
6812
|
+
|
|
6813
|
+
| Prop | Type | Default | Description |
|
|
6814
|
+
|---|---|---|---|
|
|
6815
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
6816
|
+
|
|
6817
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
6818
|
+
|
|
6819
|
+
| Prop | Type | Default | Description |
|
|
6820
|
+
|---|---|---|---|
|
|
6821
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
6822
|
+
|
|
6512
6823
|
#### Known limits
|
|
6513
6824
|
|
|
6514
6825
|
- A main time never becomes shorter than one raster step, or a minute without a raster.
|
|
@@ -6520,7 +6831,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
6520
6831
|
Makes a drag land on a raster - the axis' own step, a fixed duration, or a pattern with an offset - so dropped work starts on a time a planner can read.
|
|
6521
6832
|
|
|
6522
6833
|
```ts
|
|
6523
|
-
import {
|
|
6834
|
+
import { useSchedule, snapTime } from "@umriss-ui/schedule";
|
|
6524
6835
|
```
|
|
6525
6836
|
|
|
6526
6837
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/snapping/
|
|
@@ -6537,7 +6848,7 @@ Without `snap`, a drag lands on the fine band's step: quarter hours when zoomed
|
|
|
6537
6848
|
|
|
6538
6849
|
```tsx
|
|
6539
6850
|
import { useState } from "react";
|
|
6540
|
-
import {
|
|
6851
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6541
6852
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
6542
6853
|
|
|
6543
6854
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6547,11 +6858,11 @@ const TOURS: readonly Task[] = [{ id: "river", name: "Riverside depot tours", co
|
|
|
6547
6858
|
const START: readonly Subtask[] = [{ id: "T-04", task: "river", lane: "van-402", from: at(7, 30), to: at(12, 10) }];
|
|
6548
6859
|
|
|
6549
6860
|
export default function SnapToTheTicks() {
|
|
6861
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6), at(16)] } });
|
|
6550
6862
|
const [tours, setTours] = useState(START);
|
|
6551
6863
|
return (
|
|
6552
6864
|
<Schedule
|
|
6553
6865
|
ariaLabel="A tour on the default raster"
|
|
6554
|
-
initialDomain={[at(6), at(16)]}
|
|
6555
6866
|
height={100}
|
|
6556
6867
|
intents={["move"]}
|
|
6557
6868
|
onIntent={(intent) => setTours((current) => current.map((tour) => applyIntent(tour, intent)))}
|
|
@@ -6569,7 +6880,7 @@ Give `snap` a number of milliseconds - here half a day - and every drag lands on
|
|
|
6569
6880
|
|
|
6570
6881
|
```tsx
|
|
6571
6882
|
import { useState } from "react";
|
|
6572
|
-
import {
|
|
6883
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6573
6884
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
6574
6885
|
|
|
6575
6886
|
const day = (d: number, hours = 0) => new Date(2026, 2, d, hours).getTime();
|
|
@@ -6583,11 +6894,11 @@ const START: readonly Subtask[] = [
|
|
|
6583
6894
|
];
|
|
6584
6895
|
|
|
6585
6896
|
export default function SnapToADuration() {
|
|
6897
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [day(16), day(21)] } });
|
|
6586
6898
|
const [work, setWork] = useState(START);
|
|
6587
6899
|
return (
|
|
6588
6900
|
<Schedule
|
|
6589
6901
|
ariaLabel="Work planned in half days"
|
|
6590
|
-
initialDomain={[day(16), day(21)]}
|
|
6591
6902
|
height={140}
|
|
6592
6903
|
snap={HALF_DAY}
|
|
6593
6904
|
intents={["move", "stretch"]}
|
|
@@ -6607,7 +6918,7 @@ A `snap` raster with a step and an offset: eight-hour watches handed over at 06:
|
|
|
6607
6918
|
|
|
6608
6919
|
```tsx
|
|
6609
6920
|
import { useState } from "react";
|
|
6610
|
-
import {
|
|
6921
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6611
6922
|
import type { SnapRaster, Subtask, Task } from "@umriss-ui/schedule";
|
|
6612
6923
|
|
|
6613
6924
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6621,11 +6932,11 @@ const ENGINEERS: readonly Task[] = [{ id: "ada", name: "Ada Mwangi", color: "lig
|
|
|
6621
6932
|
const START: readonly Subtask[] = [{ id: "watch-1", task: "ada", lane: "primary", from: at(6), to: at(14) }];
|
|
6622
6933
|
|
|
6623
6934
|
export default function SnapToHandovers() {
|
|
6935
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(4), at(24)] } });
|
|
6624
6936
|
const [watches, setWatches] = useState(START);
|
|
6625
6937
|
return (
|
|
6626
6938
|
<Schedule
|
|
6627
6939
|
ariaLabel="An on-call watch on the handover raster"
|
|
6628
|
-
initialDomain={[at(4), at(24)]}
|
|
6629
6940
|
height={110}
|
|
6630
6941
|
snap={HANDOVERS}
|
|
6631
6942
|
intents={["move"]}
|
|
@@ -6644,7 +6955,7 @@ export default function SnapToHandovers() {
|
|
|
6644
6955
|
|
|
6645
6956
|
```tsx
|
|
6646
6957
|
import { useState } from "react";
|
|
6647
|
-
import {
|
|
6958
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6648
6959
|
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
6649
6960
|
|
|
6650
6961
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6654,11 +6965,11 @@ const ENGINEERS: readonly Task[] = [{ id: "sam", name: "Sam Okafor", color: "lig
|
|
|
6654
6965
|
const START: readonly Subtask[] = [{ id: "window-1", task: "sam", lane: "notifications", from: at(13, 5), to: at(13, 50), name: "Maintenance window" }];
|
|
6655
6966
|
|
|
6656
6967
|
export default function NoSnapping() {
|
|
6968
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(10), at(18)] } });
|
|
6657
6969
|
const [windows, setWindows] = useState(START);
|
|
6658
6970
|
return (
|
|
6659
6971
|
<Schedule
|
|
6660
6972
|
ariaLabel="A maintenance window placed freely"
|
|
6661
|
-
initialDomain={[at(10), at(18)]}
|
|
6662
6973
|
height={100}
|
|
6663
6974
|
snap={false}
|
|
6664
6975
|
intents={["move"]}
|
|
@@ -6681,7 +6992,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
6681
6992
|
|
|
6682
6993
|
| Prop | Type | Default | Description |
|
|
6683
6994
|
|---|---|---|---|
|
|
6684
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
6685
6995
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
6686
6996
|
| [`snap`](#/schedule/ScheduleProps-snap) | `"ticks" \| number \| `[`SnapRaster`](#/schedule/type-SnapRaster)` \| false` | `"ticks"` | The raster a drag lands on: `"ticks"` - the fine band's current step -, a step in milliseconds from local midnight, a step with an offset (shifts at 06:00, 14:00 and 22:00 are eight hours offset by six), or `false` for none. It shapes the ghost and the intent, never the data. |
|
|
6687
6997
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
@@ -6732,12 +7042,24 @@ From `Task` — the full table stands on [Subtasks](#/subtasks/type-Task).
|
|
|
6732
7042
|
| [`color`](#/subtasks/Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
6733
7043
|
| [`name`](#/subtasks/Task-name) | `string` | — | What the task is called, where the schedule names it. |
|
|
6734
7044
|
|
|
7045
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
7046
|
+
|
|
7047
|
+
| Prop | Type | Default | Description |
|
|
7048
|
+
|---|---|---|---|
|
|
7049
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
7050
|
+
|
|
7051
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
7052
|
+
|
|
7053
|
+
| Prop | Type | Default | Description |
|
|
7054
|
+
|---|---|---|---|
|
|
7055
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
7056
|
+
|
|
6735
7057
|
### Placing from outside
|
|
6736
7058
|
|
|
6737
7059
|
Drag work that is not on the plan yet - from a backlog or a list of unassigned jobs - onto a lane, with the browser's own drag and drop. The drop reports where it should go; your application creates it.
|
|
6738
7060
|
|
|
6739
7061
|
```ts
|
|
6740
|
-
import {
|
|
7062
|
+
import { useSchedule, subtaskFromPlace } from "@umriss-ui/schedule";
|
|
6741
7063
|
```
|
|
6742
7064
|
|
|
6743
7065
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/placing/
|
|
@@ -6755,7 +7077,7 @@ Set `placing` on your list's `dragstart` and clear it on `dragend`; the drop rep
|
|
|
6755
7077
|
```tsx
|
|
6756
7078
|
import { useState } from "react";
|
|
6757
7079
|
import { Card, Stack, Text } from "@umriss-ui/core";
|
|
6758
|
-
import {
|
|
7080
|
+
import { applyIntent, subtaskFromPlace, useSchedule } from "@umriss-ui/schedule";
|
|
6759
7081
|
import type { Dependency, Intent, PlacingItem, Subtask, Task } from "@umriss-ui/schedule";
|
|
6760
7082
|
|
|
6761
7083
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6789,6 +7111,7 @@ const BACKLOG: readonly (PlacingItem & { label: string })[] = [
|
|
|
6789
7111
|
];
|
|
6790
7112
|
|
|
6791
7113
|
export default function DragFromTheBacklog() {
|
|
7114
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
|
|
6792
7115
|
const [plan, setPlan] = useState<{ work: readonly Subtask[]; placed: number; last: string }>({
|
|
6793
7116
|
work: TODAY,
|
|
6794
7117
|
placed: 0,
|
|
@@ -6831,7 +7154,6 @@ export default function DragFromTheBacklog() {
|
|
|
6831
7154
|
</Stack>
|
|
6832
7155
|
<Schedule
|
|
6833
7156
|
ariaLabel="Today's plan of three people, with the backlog beside it"
|
|
6834
|
-
initialDomain={[at(5, 30), at(18)]}
|
|
6835
7157
|
height={196}
|
|
6836
7158
|
intents={["place", "move", "lane"]}
|
|
6837
7159
|
placing={placing}
|
|
@@ -6865,7 +7187,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
6865
7187
|
|
|
6866
7188
|
| Prop | Type | Default | Description |
|
|
6867
7189
|
|---|---|---|---|
|
|
6868
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
6869
7190
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
6870
7191
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
6871
7192
|
| [`placing`](#/schedule/ScheduleProps-placing) | [`PlacingItem`](#/schedule/type-PlacingItem)` \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted. |
|
|
@@ -6931,12 +7252,24 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
6931
7252
|
| [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
6932
7253
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
6933
7254
|
|
|
7255
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
7256
|
+
|
|
7257
|
+
| Prop | Type | Default | Description |
|
|
7258
|
+
|---|---|---|---|
|
|
7259
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
7260
|
+
|
|
7261
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
7262
|
+
|
|
7263
|
+
| Prop | Type | Default | Description |
|
|
7264
|
+
|---|---|---|---|
|
|
7265
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
7266
|
+
|
|
6934
7267
|
### Where a subtask may go
|
|
6935
7268
|
|
|
6936
7269
|
Marks the lanes and times a piece of work may not go to as soon as a drag begins, and holds the ghost where it is allowed. Reach for it when rules of your domain - equipment, skills, leave - limit where work can stand.
|
|
6937
7270
|
|
|
6938
7271
|
```ts
|
|
6939
|
-
import {
|
|
7272
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
6940
7273
|
```
|
|
6941
7274
|
|
|
6942
7275
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/where-it-may-go/
|
|
@@ -6956,7 +7289,7 @@ A refusal costs only what it refuses - a drop across a closed lane still reports
|
|
|
6956
7289
|
```tsx
|
|
6957
7290
|
import { useState } from "react";
|
|
6958
7291
|
import { Stack, Text } from "@umriss-ui/core";
|
|
6959
|
-
import {
|
|
7292
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
6960
7293
|
import type { Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
6961
7294
|
|
|
6962
7295
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -6982,6 +7315,7 @@ const START: Subtask[] = [
|
|
|
6982
7315
|
const mayGo = (subtask: Subtask, lane: string) => subtask.task !== "chilled" || COOLED.includes(lane);
|
|
6983
7316
|
|
|
6984
7317
|
export default function LanesThatFit() {
|
|
7318
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(15)] } });
|
|
6985
7319
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
6986
7320
|
const [last, setLast] = useState("Drag the vaccines onto the dry van");
|
|
6987
7321
|
|
|
@@ -6997,7 +7331,6 @@ export default function LanesThatFit() {
|
|
|
6997
7331
|
<Stack gap={3}>
|
|
6998
7332
|
<Schedule
|
|
6999
7333
|
ariaLabel="Two cooled vans and a dry van"
|
|
7000
|
-
initialDomain={[at(6, 30), at(15)]}
|
|
7001
7334
|
height={190}
|
|
7002
7335
|
intents={["move", "lane"]}
|
|
7003
7336
|
canMoveTo={mayGo}
|
|
@@ -7023,7 +7356,7 @@ export default function LanesThatFit() {
|
|
|
7023
7356
|
```tsx
|
|
7024
7357
|
import { useState } from "react";
|
|
7025
7358
|
import { Card, Stack, Text } from "@umriss-ui/core";
|
|
7026
|
-
import {
|
|
7359
|
+
import { subtaskFromPlace, useSchedule } from "@umriss-ui/schedule";
|
|
7027
7360
|
import type { Intent, PlacingItem, Subtask, Task } from "@umriss-ui/schedule";
|
|
7028
7361
|
|
|
7029
7362
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -7054,6 +7387,7 @@ const WAITING: PlacingItem & { label: string } = {
|
|
|
7054
7387
|
};
|
|
7055
7388
|
|
|
7056
7389
|
export default function DraggedIn() {
|
|
7390
|
+
const { Schedule, Lane, Subtasks } = useSchedule({ initialView: { domain: [at(6, 30), at(15)] } });
|
|
7057
7391
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
7058
7392
|
const [placing, setPlacing] = useState<PlacingItem | null>(null);
|
|
7059
7393
|
const [last, setLast] = useState("Drag the chilled delivery onto the dry van");
|
|
@@ -7083,7 +7417,6 @@ export default function DraggedIn() {
|
|
|
7083
7417
|
</Card>
|
|
7084
7418
|
<Schedule
|
|
7085
7419
|
ariaLabel="A chilled delivery waiting beside two cooled vans and a dry van"
|
|
7086
|
-
initialDomain={[at(6, 30), at(15)]}
|
|
7087
7420
|
height={190}
|
|
7088
7421
|
intents={["place"]}
|
|
7089
7422
|
placing={placing}
|
|
@@ -7110,7 +7443,7 @@ Blocked time refuses a place in time where `canMoveTo` refuses a lane; both hold
|
|
|
7110
7443
|
```tsx
|
|
7111
7444
|
import { useState } from "react";
|
|
7112
7445
|
import { Stack, Text } from "@umriss-ui/core";
|
|
7113
|
-
import {
|
|
7446
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
7114
7447
|
import type { BlockedTime, Intent, Subtask } from "@umriss-ui/schedule";
|
|
7115
7448
|
|
|
7116
7449
|
/* Data from the planning world, written out here so the example runs on its own. */
|
|
@@ -7227,6 +7560,7 @@ const START_ROLE = new Map(START.map((item) => [item.id, roleOf(item.lane)]));
|
|
|
7227
7560
|
const mayGo = (item: Subtask, lane: string) => START_ROLE.get(item.id) === roleOf(lane);
|
|
7228
7561
|
|
|
7229
7562
|
export default function AroundLeave() {
|
|
7563
|
+
const { Schedule, Lane, Subtasks, BlockedTimes } = useSchedule({ initialView: { domain: [day(9), day(21)] } });
|
|
7230
7564
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
7231
7565
|
const [last, setLast] = useState("Drag Freya's store screenshots into her holiday");
|
|
7232
7566
|
|
|
@@ -7240,7 +7574,6 @@ export default function AroundLeave() {
|
|
|
7240
7574
|
<Stack gap={3}>
|
|
7241
7575
|
<Schedule
|
|
7242
7576
|
ariaLabel="The apps team this week, with leave"
|
|
7243
|
-
initialDomain={[day(9), day(21)]}
|
|
7244
7577
|
height={236}
|
|
7245
7578
|
intents={["move", "lane"]}
|
|
7246
7579
|
canMoveTo={mayGo}
|
|
@@ -7274,7 +7607,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
7274
7607
|
|
|
7275
7608
|
| Prop | Type | Default | Description |
|
|
7276
7609
|
|---|---|---|---|
|
|
7277
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
7278
7610
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
7279
7611
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
7280
7612
|
| [`canMoveTo`](#/schedule/ScheduleProps-canMoveTo) | `(subtask: `[`Subtask`](#/subtasks/type-Subtask)`, lane: string) => boolean` | every lane is open | Whether a subtask may go to a lane. It narrows `"lane"`, it does not enable it. Asked once per lane when a drag takes hold, and asked again at the drop. The lanes it turns down are marked from the first frame of the drag - a planner sees the refusal before meeting it - and over one of them the ghost stays on the last lane that was allowed, the cursor says no and a line ties the ghost to the pointer. A refused lane costs the lane and nothing else: a drop after it still reports the move in time. It is asked for work dragged in from outside as well, with the key and task the application declared in `placing`. |
|
|
@@ -7341,6 +7673,18 @@ From `BlockedTime` — the full table stands on [Blocked time](#/blocked-time/ty
|
|
|
7341
7673
|
| [`to`](#/blocked-time/BlockedTime-to) *required* | `number` | — | Where it ends. |
|
|
7342
7674
|
| [`label`](#/blocked-time/BlockedTime-label) | `string` | — | Why the lane is blocked - "Leave", "Maintenance" -, where the schedule names it. |
|
|
7343
7675
|
|
|
7676
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
7677
|
+
|
|
7678
|
+
| Prop | Type | Default | Description |
|
|
7679
|
+
|---|---|---|---|
|
|
7680
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
7681
|
+
|
|
7682
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
7683
|
+
|
|
7684
|
+
| Prop | Type | Default | Description |
|
|
7685
|
+
|---|---|---|---|
|
|
7686
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
7687
|
+
|
|
7344
7688
|
#### Known limits
|
|
7345
7689
|
|
|
7346
7690
|
- A rule that changes while a drag runs is noticed at the drop, not before.
|
|
@@ -7370,7 +7714,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/schedule/ripple/
|
|
|
7370
7714
|
```tsx
|
|
7371
7715
|
import { useState } from "react";
|
|
7372
7716
|
import { Button, Stack, Text } from "@umriss-ui/core";
|
|
7373
|
-
import {
|
|
7717
|
+
import { applyIntent, ripple, useSchedule } from "@umriss-ui/schedule";
|
|
7374
7718
|
import type { Dependency, MoveIntent, Subtask, Task } from "@umriss-ui/schedule";
|
|
7375
7719
|
|
|
7376
7720
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -7396,6 +7740,7 @@ const DRIVES: readonly Dependency[] = [
|
|
|
7396
7740
|
const LATE: MoveIntent = { kind: "move", subtask: "stop-1", from: at(8, 10), to: at(8, 20) };
|
|
7397
7741
|
|
|
7398
7742
|
export default function PushTheLaterStops() {
|
|
7743
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7, 15), at(10, 30)] } });
|
|
7399
7744
|
const [stops, setStops] = useState<readonly Subtask[]>(START);
|
|
7400
7745
|
/* Nothing to push until the first stop runs late. */
|
|
7401
7746
|
const late = stops.some((s) => s.id === LATE.subtask && s.from === LATE.from);
|
|
@@ -7403,7 +7748,7 @@ export default function PushTheLaterStops() {
|
|
|
7403
7748
|
|
|
7404
7749
|
return (
|
|
7405
7750
|
<Stack gap={3}>
|
|
7406
|
-
<Schedule ariaLabel="Tour T-01 on van FP 214 K"
|
|
7751
|
+
<Schedule ariaLabel="Tour T-01 on van FP 214 K" height={100}>
|
|
7407
7752
|
<Lane id="van-214" label="Van FP 214 K" />
|
|
7408
7753
|
<Dependencies data={DRIVES} />
|
|
7409
7754
|
<Subtasks data={stops} tasks={TOURS} />
|
|
@@ -7438,7 +7783,7 @@ export default function PushTheLaterStops() {
|
|
|
7438
7783
|
```tsx
|
|
7439
7784
|
import { useState } from "react";
|
|
7440
7785
|
import { Button, Stack } from "@umriss-ui/core";
|
|
7441
|
-
import {
|
|
7786
|
+
import { applyIntent, shiftTask, useSchedule } from "@umriss-ui/schedule";
|
|
7442
7787
|
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
7443
7788
|
|
|
7444
7789
|
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
@@ -7465,13 +7810,14 @@ const DRIVES: readonly Dependency[] = [
|
|
|
7465
7810
|
];
|
|
7466
7811
|
|
|
7467
7812
|
export default function MoveAWholeTour() {
|
|
7813
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(7), at(10, 30)] } });
|
|
7468
7814
|
const [stops, setStops] = useState<readonly Subtask[]>(START);
|
|
7469
7815
|
const move = (by: number) =>
|
|
7470
7816
|
setStops((current) => shiftTask(current, "T-01", by).reduce((data, one) => data.map((s) => applyIntent(s, one)), current));
|
|
7471
7817
|
|
|
7472
7818
|
return (
|
|
7473
7819
|
<Stack gap={3}>
|
|
7474
|
-
<Schedule ariaLabel="Two tours from North depot"
|
|
7820
|
+
<Schedule ariaLabel="Two tours from North depot" height={140}>
|
|
7475
7821
|
<Lane id="van-214" label="Van FP 214 K" />
|
|
7476
7822
|
<Lane id="van-377" label="E-van FP 377 K" />
|
|
7477
7823
|
<Dependencies data={DRIVES} />
|
|
@@ -7496,7 +7842,7 @@ Run [`ripple`](#/api/ripple) inside `onIntent` and apply its moves with the drop
|
|
|
7496
7842
|
|
|
7497
7843
|
```tsx
|
|
7498
7844
|
import { useState } from "react";
|
|
7499
|
-
import {
|
|
7845
|
+
import { applyIntent, ripple, useSchedule } from "@umriss-ui/schedule";
|
|
7500
7846
|
import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
7501
7847
|
|
|
7502
7848
|
const day = (d: number, hours = 9) => new Date(2026, 2, d, hours).getTime();
|
|
@@ -7516,6 +7862,7 @@ const HANDOVERS: readonly Dependency[] = [
|
|
|
7516
7862
|
];
|
|
7517
7863
|
|
|
7518
7864
|
export default function RippleOnEveryDrag() {
|
|
7865
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [day(16, 0), day(24, 0)] } });
|
|
7519
7866
|
const [work, setWork] = useState<readonly Subtask[]>(START);
|
|
7520
7867
|
|
|
7521
7868
|
const onIntent = (intent: Intent) =>
|
|
@@ -7527,7 +7874,6 @@ export default function RippleOnEveryDrag() {
|
|
|
7527
7874
|
return (
|
|
7528
7875
|
<Schedule
|
|
7529
7876
|
ariaLabel="Design, build and test of the shop search, rippling"
|
|
7530
|
-
initialDomain={[day(16, 0), day(24, 0)]}
|
|
7531
7877
|
height={184}
|
|
7532
7878
|
intents={["move", "stretch"]}
|
|
7533
7879
|
onIntent={onIntent}
|
|
@@ -7552,7 +7898,6 @@ From `ScheduleProps` — the full table stands on [First schedule](#/schedule/ty
|
|
|
7552
7898
|
|
|
7553
7899
|
| Prop | Type | Default | Description |
|
|
7554
7900
|
|---|---|---|---|
|
|
7555
|
-
| [`initialDomain`](#/schedule/ScheduleProps-initialDomain) *required* | `readonly [number, number]` | — | The time in view when the schedule mounts, as two wall-clock instants. Pan and zoom take it from there; a new pair of values puts it back. |
|
|
7556
7901
|
| [`height`](#/schedule/ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
|
|
7557
7902
|
| [`intents`](#/schedule/ScheduleProps-intents) | `readonly `[`IntentKind`](#/schedule/type-IntentKind)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
7558
7903
|
| [`children`](#/schedule/ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
@@ -7617,6 +7962,18 @@ From `Dependency` — the full table stands on [Dependencies](#/dependencies/typ
|
|
|
7617
7962
|
| [`to`](#/dependencies/Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
7618
7963
|
| [`lag`](#/dependencies/Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
7619
7964
|
|
|
7965
|
+
From `ScheduleOptions` — the full table stands on [View](#/view/type-ScheduleOptions).
|
|
7966
|
+
|
|
7967
|
+
| Prop | Type | Default | Description |
|
|
7968
|
+
|---|---|---|---|
|
|
7969
|
+
| [`initialView`](#/view/ScheduleOptions-initialView) | [`ScheduleView`](#/view/type-ScheduleView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the schedule reported itself, handed back late, is its own state coming back and not gone to. The schedule remembers none: where a view is kept is the application's decision. |
|
|
7970
|
+
|
|
7971
|
+
From `ScheduleView` — the full table stands on [View](#/view/type-ScheduleView).
|
|
7972
|
+
|
|
7973
|
+
| Prop | Type | Default | Description |
|
|
7974
|
+
|---|---|---|---|
|
|
7975
|
+
| [`domain`](#/view/ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
7976
|
+
|
|
7620
7977
|
#### Known limits
|
|
7621
7978
|
|
|
7622
7979
|
- No dependency types, slack or critical path: the schedule is no project-planning engine ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/)).
|
|
@@ -7634,56 +7991,20 @@ Everything `@umriss-ui/schedule` exports, with its signature. A component's prop
|
|
|
7634
7991
|
|
|
7635
7992
|
Demo page: https://romanhaendler.github.io/umriss-ui/schedule/api/
|
|
7636
7993
|
|
|
7637
|
-
####
|
|
7638
|
-
|
|
7639
|
-
##### `BlockedTimes`
|
|
7640
|
-
|
|
7641
|
-
[Blocked time](#/blocked-time) – A layer of blocked time. Wherever it is declared, it is drawn behind every
|
|
7642
|
-
subtask and dependency: it is a property of the lane, not something on it.
|
|
7643
|
-
|
|
7644
|
-
Used on [Scenarios](#/), [Blocked time](#/blocked-time), [Tooltip](#/tooltip), [Findings as data](#/findings) and [Where a subtask may go](#/where-it-may-go).
|
|
7645
|
-
|
|
7646
|
-
##### `Dependencies`
|
|
7647
|
-
|
|
7648
|
-
[Dependencies](#/dependencies) – Lines from one subtask's end to the next one's start within a task, with the lag that must pass between them (also called links or predecessors). A lag that does not fit is marked, not repaired.
|
|
7649
|
-
|
|
7650
|
-
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lane groups](#/lane-groups), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Placing from outside](#/placing) and [Ripple](#/ripple).
|
|
7651
|
-
|
|
7652
|
-
##### `Lane`
|
|
7653
|
-
|
|
7654
|
-
[Lanes](#/lane) – One lane: a person, a vehicle, a room. The lanes run top to bottom in the order
|
|
7655
|
-
they are declared - whichever group they are declared in.
|
|
7656
|
-
|
|
7657
|
-
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
7658
|
-
|
|
7659
|
-
##### `LaneGroup`
|
|
7660
|
-
|
|
7661
|
-
[Lane groups](#/lane-groups) – A group of lanes: structure over them, and never a lane itself.
|
|
7994
|
+
#### Hooks
|
|
7662
7995
|
|
|
7663
|
-
|
|
7664
|
-
group and a group reads in JSX as it reads in the organisation. Folded, it becomes
|
|
7665
|
-
one row showing a **Miniature** of everything in it; open, it shows a slim
|
|
7666
|
-
head above its lanes. Which groups are folded is `collapsedGroups` on
|
|
7667
|
-
`Schedule` - a view state, never an **Intent**.
|
|
7996
|
+
##### `useSchedule`
|
|
7668
7997
|
|
|
7669
|
-
|
|
7998
|
+
Declares a schedule: hands back `Schedule` and the parts declared inside it
|
|
7999
|
+
- `Lane`, `LaneGroup`, `Subtasks`, `Dependencies`, `BlockedTimes` -, its
|
|
8000
|
+
`view` and the setters `setDomain`, `toggleGroup`, `foldAll` and
|
|
8001
|
+
`unfoldAll`. A ref on `Schedule` still gives the `ScheduleHandle`.
|
|
7670
8002
|
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
dependencies between them. The contents are declared as children (`Lane`,
|
|
7675
|
-
`LaneGroup`, `Subtasks`, `Dependencies`, `BlockedTimes`); the planner pans,
|
|
7676
|
-
zooms and drags, and every drag ends as an **Intent** through `onIntent`.
|
|
7677
|
-
The data changes only where the caller applies it; without `intents` the
|
|
7678
|
-
schedule is read-only. A ref gives the `ScheduleHandle`.
|
|
7679
|
-
|
|
7680
|
-
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
7681
|
-
|
|
7682
|
-
##### `Subtasks`
|
|
7683
|
-
|
|
7684
|
-
[Subtasks](#/subtasks) – The bars: a main time with an optional lead-in before it and lead-out after it, in the colour of the task they belong to (also called bookings or assignments).
|
|
8003
|
+
```ts
|
|
8004
|
+
function useSchedule(options?: ScheduleOptions): ScheduleParts;
|
|
8005
|
+
```
|
|
7685
8006
|
|
|
7686
|
-
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
8007
|
+
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [View](#/view), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
7687
8008
|
|
|
7688
8009
|
#### Functions
|
|
7689
8010
|
|
|
@@ -8072,6 +8393,29 @@ Its table stands on [Interactions](#/interactions/type-ScheduleInteraction).
|
|
|
8072
8393
|
|
|
8073
8394
|
Used on [Scenarios](#/), [Interactions](#/interactions) and [Tooltip](#/tooltip).
|
|
8074
8395
|
|
|
8396
|
+
##### `ScheduleOptions`
|
|
8397
|
+
|
|
8398
|
+
Its table stands on [View](#/view/type-ScheduleOptions).
|
|
8399
|
+
|
|
8400
|
+
##### `ScheduleParts`
|
|
8401
|
+
|
|
8402
|
+
What `useSchedule` hands back: the schedule and what is declared inside
|
|
8403
|
+
it, its view and the setters that change it.
|
|
8404
|
+
|
|
8405
|
+
| Prop | Type | Default | Description |
|
|
8406
|
+
|---|---|---|---|
|
|
8407
|
+
| `Schedule` *required* | `ForwardRefExoticComponent<`[`ScheduleProps`](#/schedule/type-ScheduleProps)` & RefAttributes<`[`ScheduleHandle`](#type-ScheduleHandle)`>>` | — | The schedule. One per `useSchedule`: a second schedule is a second call.<br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Start from an empty plan (First schedule)](#/schedule/no-work-yet) and 75 more |
|
|
8408
|
+
| `Lane` *required* | `typeof Lane` | — | <br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Start from an empty plan (First schedule)](#/schedule/no-work-yet) and 75 more |
|
|
8409
|
+
| `LaneGroup` *required* | `typeof LaneGroup` | — | <br>Shown in: [Group lanes under a head (Lane groups)](#/lane-groups/a-group), [Nest groups to any depth (Lane groups)](#/lane-groups/nesting), [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside) and 5 more |
|
|
8410
|
+
| `Subtasks` *required* | `typeof Subtasks` | — | <br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Start from an empty plan (First schedule)](#/schedule/no-work-yet) and 75 more |
|
|
8411
|
+
| `Dependencies` *required* | `typeof Dependencies` | — | <br>Shown in: [The smallest schedule (Installation)](#/installation/smallest-schedule), [A day of tours (First schedule)](#/schedule/first-schedule), [Plan a day in the machine shop (First schedule)](#/schedule/machine-plan) and 27 more |
|
|
8412
|
+
| `BlockedTimes` *required* | `typeof BlockedTimes` | — | <br>Shown in: [Block out leave and maintenance (Blocked time)](#/blocked-time/leave), [Plan work around someone's leave (Blocked time)](#/blocked-time/drag-around), [Check a sprint against the leave calendar (Blocked time)](#/blocked-time/team-leave) and 5 more |
|
|
8413
|
+
| `view` *required* | [`ScheduleView`](#/view/type-ScheduleView) | — | How the planner is looking at the plan: the span and the folded groups.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside), [Place a mark of your own (The handle)](#/handle/place-a-mark) |
|
|
8414
|
+
| `setDomain` *required* | `(span: readonly [number, number] \| null) => void` | — | Puts a span of two wall-clock instants in view; `null` shows the subtasks' extent.<br>Shown in: [Keep and restore a view (View)](#/view/keep-and-restore) |
|
|
8415
|
+
| `toggleGroup` *required* | `(id: string) => void` | — | Folds or unfolds one **Lane group**. |
|
|
8416
|
+
| `foldAll` *required* | `() => void` | — | Folds every group.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside) |
|
|
8417
|
+
| `unfoldAll` *required* | `() => void` | — | Unfolds every group.<br>Shown in: [Fold groups from outside (Lane groups)](#/lane-groups/fold-from-outside) |
|
|
8418
|
+
|
|
8075
8419
|
##### `ScheduleProps`
|
|
8076
8420
|
|
|
8077
8421
|
Its table stands on [First schedule](#/schedule/type-ScheduleProps).
|
|
@@ -8082,6 +8426,12 @@ Its table stands on [Tooltip](#/tooltip/type-ScheduleTooltipTarget).
|
|
|
8082
8426
|
|
|
8083
8427
|
Used on [Tooltip](#/tooltip).
|
|
8084
8428
|
|
|
8429
|
+
##### `ScheduleView`
|
|
8430
|
+
|
|
8431
|
+
Its table stands on [View](#/view/type-ScheduleView).
|
|
8432
|
+
|
|
8433
|
+
Used on [View](#/view) and [Linked schedules](#/linked-schedules).
|
|
8434
|
+
|
|
8085
8435
|
##### `SnapRaster`
|
|
8086
8436
|
|
|
8087
8437
|
A raster: a step, and where it starts after local midnight.
|
|
@@ -8108,7 +8458,7 @@ Stretch the main time at one or both of its edges.
|
|
|
8108
8458
|
|
|
8109
8459
|
Its table stands on [Subtasks](#/subtasks/type-Subtask).
|
|
8110
8460
|
|
|
8111
|
-
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
8461
|
+
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [View](#/view), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
8112
8462
|
|
|
8113
8463
|
##### `SubtaskAppearance`
|
|
8114
8464
|
|
|
@@ -8126,7 +8476,7 @@ Its table stands on [Subtasks](#/subtasks/type-SubtasksProps).
|
|
|
8126
8476
|
|
|
8127
8477
|
Its table stands on [Subtasks](#/subtasks/type-Task).
|
|
8128
8478
|
|
|
8129
|
-
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
8479
|
+
Used on [Scenarios](#/), [Installation](#/installation), [First schedule](#/schedule), [Lanes](#/lane), [Lane groups](#/lane-groups), [Subtasks](#/subtasks), [Bar labels](#/bar-labels), [Appearances](#/appearances), [Overlap](#/overlap), [Dependencies](#/dependencies), [Routes](#/routes), [Time axis and calendar](#/time-axis), [Blocked time](#/blocked-time), [Now line](#/now-line), [Pan and zoom](#/pan-and-zoom), [Selection](#/selection), [Interactions](#/interactions), [Tooltip](#/tooltip), [View](#/view), [Linked schedules](#/linked-schedules), [The handle](#/handle), [Findings as data](#/findings), [Move and lane](#/move-and-lane), [Stretch, lead-in, lead-out](#/stretch), [Snapping](#/snapping), [Placing from outside](#/placing), [Where a subtask may go](#/where-it-may-go) and [Ripple](#/ripple).
|
|
8130
8480
|
|
|
8131
8481
|
##### `ViolatedDependency`
|
|
8132
8482
|
|