@tensor_1/react 2.32.0 → 2.34.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/README.md +42 -0
- package/dist/SchedulingCalendar-DSLQ6AY5.d.ts +376 -0
- package/dist/components/SchedulingAgenda.d.ts +31 -0
- package/dist/components/SchedulingAgenda.js +161 -0
- package/dist/components/SchedulingCalendar.d.ts +4 -241
- package/dist/components/SchedulingCalendar.js +116 -24
- package/dist/components/SchedulingTimeGrid.d.ts +4 -0
- package/dist/components/SchedulingTimeGrid.js +521 -0
- package/dist/components/SchedulingTimeline.d.ts +1 -1
- package/dist/index.cjs +1349 -490
- package/dist/index.d.cts +211 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -1
- package/dist/labels-context.d.ts +1 -1
- package/dist/scheduling-calendar-model.d.ts +104 -1
- package/dist/scheduling-calendar-model.js +95 -1
- package/package.json +1 -1
|
@@ -1,241 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type SchedulingCalendarView = 'month' | 'week' | 'agenda';
|
|
7
|
-
/**
|
|
8
|
-
* @deprecated Use `band`, `status` and `markers` on the entry: they carry no product
|
|
9
|
-
* vocabulary and the consumer supplies every word. The values keep working in 2.x.
|
|
10
|
-
*/
|
|
11
|
-
type SchedulingEntryState = 'scheduled' | 'conflicted' | 'in-progress' | 'blackout-violation';
|
|
12
|
-
/** @deprecated Use `pattern` and `kindLabel` on the overlay. The values keep working in 2.x. */
|
|
13
|
-
type SchedulingOverlayKind = 'maintenance' | 'blackout';
|
|
14
|
-
/**
|
|
15
|
-
* The one dimension that takes a hue. `none` and `low` are untinted, `medium` gets a
|
|
16
|
-
* heavier edge, and only `high` is filled. The consumer decides what a band means.
|
|
17
|
-
*/
|
|
18
|
-
type SchedulingBand = 'none' | 'low' | 'medium' | 'high';
|
|
19
|
-
/**
|
|
20
|
-
* The state channel, drawn without hue: `tentative` is dashed, `committed` is the plain
|
|
21
|
-
* item, `live` carries a leading dot, `done` is dimmed and `dead` is struck through.
|
|
22
|
-
*/
|
|
23
|
-
type SchedulingStatus = 'tentative' | 'committed' | 'live' | 'done' | 'dead';
|
|
24
|
-
type SchedulingMarkerEmphasis = 'refused' | 'warning' | 'neutral';
|
|
25
|
-
/** How a window is hatched. Windows are neutral: the pattern and the visible label tell them apart. */
|
|
26
|
-
type SchedulingOverlayPattern = 'diagonal' | 'cross' | 'dotted' | 'solid';
|
|
27
|
-
/**
|
|
28
|
-
* Which date text a `formatDate` call is for (TENSOR B1/C9, HAR-1347):
|
|
29
|
-
* `day` names one day (each day cell's accessible name, the week-range title);
|
|
30
|
-
* `weekday` is the short column header (the date is any day of that weekday);
|
|
31
|
-
* `month` is the month-view title (the date is the first of the month).
|
|
32
|
-
*/
|
|
33
|
-
type SchedulingDatePart = 'day' | 'weekday' | 'month';
|
|
34
|
-
/** A shape or icon with text. The text is always in the accessible name; a marker never relies on colour. */
|
|
35
|
-
interface SchedulingMarker {
|
|
36
|
-
id: string;
|
|
37
|
-
/** An icon from `@tensor_1/react/icons`. Without one the marker draws a shape for its `emphasis`. */
|
|
38
|
-
icon?: ReactNode;
|
|
39
|
-
label: string;
|
|
40
|
-
emphasis?: SchedulingMarkerEmphasis;
|
|
41
|
-
}
|
|
42
|
-
interface SchedulingCalendarEntry {
|
|
43
|
-
id: string;
|
|
44
|
-
title: string;
|
|
45
|
-
start: string;
|
|
46
|
-
end: string;
|
|
47
|
-
/** @deprecated Use `band`, `status` and `markers`. */
|
|
48
|
-
state?: SchedulingEntryState;
|
|
49
|
-
meta?: string;
|
|
50
|
-
band?: SchedulingBand;
|
|
51
|
-
status?: SchedulingStatus;
|
|
52
|
-
markers?: SchedulingMarker[];
|
|
53
|
-
/** The complete accessible name, built by the consumer from its own words. Replaces `labels.entry`. */
|
|
54
|
-
accessibleName?: string;
|
|
55
|
-
}
|
|
56
|
-
interface SchedulingCalendarOverlay {
|
|
57
|
-
id: string;
|
|
58
|
-
/** The window's name, shown as text. */
|
|
59
|
-
label: string;
|
|
60
|
-
start: string;
|
|
61
|
-
end: string;
|
|
62
|
-
/** @deprecated Use `pattern` and `kindLabel`. */
|
|
63
|
-
kind?: SchedulingOverlayKind;
|
|
64
|
-
/** Defaults to `'solid'`. */
|
|
65
|
-
pattern?: SchedulingOverlayPattern;
|
|
66
|
-
/** What kind of window this is, in the consumer's words. Shown before the name. */
|
|
67
|
-
kindLabel?: string;
|
|
68
|
-
/** What the window applies to. Shown after the name. */
|
|
69
|
-
scopeLabel?: string;
|
|
70
|
-
/** The window applies to everything. A window that is not global never gets the global treatment. */
|
|
71
|
-
global?: boolean;
|
|
72
|
-
/** The complete accessible name. Replaces `labels.overlay`. */
|
|
73
|
-
accessibleName?: string;
|
|
74
|
-
}
|
|
75
|
-
/** What the consumer knows about one day (`YYYY-MM-DD`). The calendar shows these numbers and never derives them. */
|
|
76
|
-
interface SchedulingCalendarDay {
|
|
77
|
-
/** Every item on the day, shown in the cell even when no chip fits. */
|
|
78
|
-
count: number;
|
|
79
|
-
/** The "+N" of the cell. 0 shows none. */
|
|
80
|
-
overflowCount: number;
|
|
81
|
-
/** The accessible text of the count, e.g. "7 items, 2 need review". */
|
|
82
|
-
label?: string;
|
|
83
|
-
markers?: SchedulingMarker[];
|
|
84
|
-
}
|
|
85
|
-
interface SchedulingLegendItem {
|
|
86
|
-
id: string;
|
|
87
|
-
label: string;
|
|
88
|
-
swatch: {
|
|
89
|
-
band?: SchedulingBand;
|
|
90
|
-
status?: SchedulingStatus;
|
|
91
|
-
pattern?: SchedulingOverlayPattern;
|
|
92
|
-
icon?: ReactNode;
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Every word the calendar renders (TENSOR RX-125, UIX-12). `{timeZone}`, `{view}`,
|
|
97
|
-
* `{title}`, `{state}`, `{start}`, `{end}` are placeholders.
|
|
98
|
-
*/
|
|
99
|
-
interface SchedulingCalendarLabels {
|
|
100
|
-
region: string;
|
|
101
|
-
/** @deprecated Use `previousMonth` and `previousWeek`. Still used when those are not set. */
|
|
102
|
-
previous: string;
|
|
103
|
-
/** @deprecated Use `nextMonth` and `nextWeek`. Still used when those are not set. */
|
|
104
|
-
next: string;
|
|
105
|
-
previousMonth?: string;
|
|
106
|
-
nextMonth?: string;
|
|
107
|
-
/** Also names the agenda's step, which moves by a week. */
|
|
108
|
-
previousWeek?: string;
|
|
109
|
-
nextWeek?: string;
|
|
110
|
-
viewGroup: string;
|
|
111
|
-
viewMonth: string;
|
|
112
|
-
viewWeek: string;
|
|
113
|
-
viewAgenda: string;
|
|
114
|
-
loading: string;
|
|
115
|
-
retry: string;
|
|
116
|
-
agenda: string;
|
|
117
|
-
agendaEmpty: string;
|
|
118
|
-
grid: string;
|
|
119
|
-
/** An entry's accessible name unless it sets `accessibleName`. Its marker labels are appended. */
|
|
120
|
-
entry: string;
|
|
121
|
-
/** A window's accessible name unless it sets `accessibleName`. `{title}` is its kind, name and scope. */
|
|
122
|
-
overlay?: string;
|
|
123
|
-
legend: string;
|
|
124
|
-
/** The "+N more" toggle on a day with more entries than `maxEntriesPerDay`. `{count}` placeholder. */
|
|
125
|
-
moreEntries: string;
|
|
126
|
-
/** Accessible name of that toggle. `{count}` and `{date}` placeholders. */
|
|
127
|
-
moreEntriesLabel: string;
|
|
128
|
-
/** The toggle once the day is expanded in place. */
|
|
129
|
-
fewerEntries: string;
|
|
130
|
-
/** The "+N" at the end of a week row whose spans did not all get a lane. `{count}` placeholder. */
|
|
131
|
-
moreSpans?: string;
|
|
132
|
-
/** Accessible name of that control. `{count}` and `{date}` (the first day with a hidden span). */
|
|
133
|
-
moreSpansLabel?: string;
|
|
134
|
-
/** The accessible text of a day's count when `days[date].label` is not given. `{count}` placeholder. */
|
|
135
|
-
dayCount?: string;
|
|
136
|
-
states: Record<SchedulingEntryState, string>;
|
|
137
|
-
/** The words for `status`, used in the default accessible name and the agenda. */
|
|
138
|
-
statuses?: Partial<Record<SchedulingStatus, string>>;
|
|
139
|
-
}
|
|
140
|
-
declare const DEFAULT_SCHEDULING_CALENDAR_LABELS: SchedulingCalendarLabels;
|
|
141
|
-
interface SchedulingCalendarProps {
|
|
142
|
-
/**
|
|
143
|
-
* The entries. Without `dayEntries` the calendar places each on its day in the order
|
|
144
|
-
* given; with `dayEntries` only the entries that cover more than one day are read from
|
|
145
|
-
* here (they are drawn as spans).
|
|
146
|
-
*/
|
|
147
|
-
entries: SchedulingCalendarEntry[];
|
|
148
|
-
anchorDate: string;
|
|
149
|
-
timeZone: string;
|
|
150
|
-
view?: SchedulingCalendarView;
|
|
151
|
-
onViewChange?: (view: SchedulingCalendarView) => void;
|
|
152
|
-
onAnchorDateChange?: (date: string) => void;
|
|
153
|
-
/** Called by a click or Enter on a chip, a span or an agenda row. */
|
|
154
|
-
onSelectEntry?: (entry: SchedulingCalendarEntry) => void;
|
|
155
|
-
overlays?: SchedulingCalendarOverlay[];
|
|
156
|
-
/** Called by a click or Enter on a window span. */
|
|
157
|
-
onSelectOverlay?: (overlay: SchedulingCalendarOverlay) => void;
|
|
158
|
-
filter?: (entry: SchedulingCalendarEntry) => boolean;
|
|
159
|
-
/** Replaces a chip's content. The default is the start time in `timeZone` (24 h) and the title. */
|
|
160
|
-
renderEntry?: (entry: SchedulingCalendarEntry) => ReactNode;
|
|
161
|
-
locale?: string;
|
|
162
|
-
/** First column of the month and week grids: 0 = Sunday … 6 = Saturday. Defaults to 1 (Monday). */
|
|
163
|
-
weekStartsOn?: CalendarWeekday;
|
|
164
|
-
/**
|
|
165
|
-
* Formats every calendar date the component writes (`date` is `YYYY-MM-DD`). Use it to
|
|
166
|
-
* apply a product date format such as DD.MM.YYYY; defaults to `Intl` in `locale`.
|
|
167
|
-
*/
|
|
168
|
-
formatDate?: (date: string, part: SchedulingDatePart) => string;
|
|
169
|
-
/**
|
|
170
|
-
* Formats a start or end instant (ISO 8601) for the agenda and the accessible names.
|
|
171
|
-
* The component does not shift it: render it in `timeZone` yourself. Defaults to the
|
|
172
|
-
* `day` date text followed by the 24-hour time in `timeZone`.
|
|
173
|
-
*/
|
|
174
|
-
formatInstant?: (instant: string) => string;
|
|
175
|
-
/**
|
|
176
|
-
* How many chips a day shows. Without `dayEntries` the rest collapse behind "+N more";
|
|
177
|
-
* with `dayEntries` it only sizes the cell (the consumer already cut the list, and a longer
|
|
178
|
-
* list makes the cells taller, never clipped). Unset shows every entry where the cells
|
|
179
|
-
* grow, and sizes a fixed cell for three chips.
|
|
180
|
-
*/
|
|
181
|
-
maxEntriesPerDay?: number;
|
|
182
|
-
/**
|
|
183
|
-
* Called by a day's "+N more" and by a week row's "+N" with the day (and, without
|
|
184
|
-
* `dayEntries`, every entry on it). When set, the consumer shows them (a popover, a
|
|
185
|
-
* drawer, the day view) and the day stays collapsed. Required with `days` or `dayEntries`:
|
|
186
|
-
* a controlled day never expands in place.
|
|
187
|
-
*/
|
|
188
|
-
onShowMore?: (date: string, entries: SchedulingCalendarEntry[]) => void;
|
|
189
|
-
/**
|
|
190
|
-
* A small badge beside a day's date, e.g. a collision count. Keep it short (a number or a
|
|
191
|
-
* glyph): it shares a narrow column with the date and is clipped, not wrapped. It sits
|
|
192
|
-
* outside the date button, so give it its own accessible text (a visually hidden word next
|
|
193
|
-
* to the number). Return null for no badge.
|
|
194
|
-
*/
|
|
195
|
-
renderDayBadge?: (date: string, entries: SchedulingCalendarEntry[]) => ReactNode;
|
|
196
|
-
/**
|
|
197
|
-
* Consumer-owned numbers per day (`YYYY-MM-DD`). When set, each listed day shows its
|
|
198
|
-
* `count` and markers even with no chip, and its "+N" is `overflowCount`, never a number
|
|
199
|
-
* derived from the entries given. Without `dayEntries` the chips are still this day's
|
|
200
|
-
* `entries`, cut to `maxEntriesPerDay`; the day never expands in place.
|
|
201
|
-
*/
|
|
202
|
-
days?: Record<string, SchedulingCalendarDay>;
|
|
203
|
-
/**
|
|
204
|
-
* The single-day chips of each day, already ranked and cut by the consumer. When set, the
|
|
205
|
-
* calendar renders exactly these in the order given: it does not place, rank, cut or count.
|
|
206
|
-
*/
|
|
207
|
-
dayEntries?: Record<string, SchedulingCalendarEntry[]>;
|
|
208
|
-
/**
|
|
209
|
-
* The result of `layoutMonthSpans` for this grid. When set, the calendar draws exactly
|
|
210
|
-
* these spans and never packs lanes itself. Compute it with `schedulingGridDays`.
|
|
211
|
-
*/
|
|
212
|
-
spanLayout?: MonthSpanLayout;
|
|
213
|
-
/**
|
|
214
|
-
* Lanes kept for windows above the chips. Windows never take a chip slot. Default 2 where
|
|
215
|
-
* the cells have a fixed height; where they grow with their content every window gets a lane.
|
|
216
|
-
*/
|
|
217
|
-
windowLaneCap?: number;
|
|
218
|
-
/** Lanes kept for entries that cover more than one day. Default as for `windowLaneCap`. */
|
|
219
|
-
spanLaneCap?: number;
|
|
220
|
-
/** When set, the legend is exactly these items (an empty list draws no legend). Unset keeps the built-in state legend. */
|
|
221
|
-
legend?: SchedulingLegendItem[];
|
|
222
|
-
/** A line under the legend, e.g. the display zone. */
|
|
223
|
-
legendCaption?: ReactNode;
|
|
224
|
-
/** `false` hides the previous/next buttons, the title and the view switch (the page has its own toolbar). */
|
|
225
|
-
showHeader?: boolean;
|
|
226
|
-
/** Shown above the grid, e.g. "Showing the first 2,000 items". */
|
|
227
|
-
notice?: ReactNode;
|
|
228
|
-
/** Shown inside the grid, below the day cells, when there is nothing to show. */
|
|
229
|
-
emptyNote?: ReactNode;
|
|
230
|
-
/** Called by a click or Enter on a day number. */
|
|
231
|
-
onSelectDate?: (date: string) => void;
|
|
232
|
-
loading?: boolean;
|
|
233
|
-
error?: string;
|
|
234
|
-
onRetry?: () => void;
|
|
235
|
-
className?: string;
|
|
236
|
-
labels?: Partial<SchedulingCalendarLabels>;
|
|
237
|
-
}
|
|
238
|
-
/** Month/week/agenda calendar for UTC ranges rendered in an explicit IANA time zone. */
|
|
239
|
-
declare function SchedulingCalendar({ entries, anchorDate, timeZone, view: controlledView, onViewChange, onAnchorDateChange, onSelectEntry, overlays, onSelectOverlay, filter, renderEntry, locale, weekStartsOn, formatDate, formatInstant: formatInstantProp, maxEntriesPerDay, onShowMore, renderDayBadge, days: dayInfo, dayEntries, spanLayout, windowLaneCap, spanLaneCap, legend, legendCaption, showHeader, notice, emptyNote, onSelectDate, loading, error, onRetry, className, labels: labelOverrides, }: SchedulingCalendarProps): react.JSX.Element;
|
|
240
|
-
|
|
241
|
-
export { DEFAULT_SCHEDULING_CALENDAR_LABELS, type SchedulingBand, SchedulingCalendar, type SchedulingCalendarDay, type SchedulingCalendarEntry, type SchedulingCalendarLabels, type SchedulingCalendarOverlay, type SchedulingCalendarProps, type SchedulingCalendarView, type SchedulingDatePart, type SchedulingEntryState, type SchedulingLegendItem, type SchedulingMarker, type SchedulingMarkerEmphasis, type SchedulingOverlayKind, type SchedulingOverlayPattern, type SchedulingStatus };
|
|
1
|
+
import 'react';
|
|
2
|
+
import '../calendar-model.js';
|
|
3
|
+
import '../scheduling-calendar-model.js';
|
|
4
|
+
export { D as DEFAULT_SCHEDULING_CALENDAR_LABELS, S as SchedulingAgendaGroup, a as SchedulingBand, b as SchedulingCalendar, c as SchedulingCalendarDay, d as SchedulingCalendarEntry, e as SchedulingCalendarLabels, f as SchedulingCalendarOverlay, g as SchedulingCalendarProps, h as SchedulingCalendarView, i as SchedulingDatePart, j as SchedulingEntryState, k as SchedulingLegendItem, l as SchedulingMarker, m as SchedulingMarkerEmphasis, n as SchedulingOverlayKind, o as SchedulingOverlayPattern, p as SchedulingStatus } from '../SchedulingCalendar-DSLQ6AY5.js';
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { addCalendarDays, addCalendarMonths, buildMonthGrid, cachedDateTimeFormat, startOfMonth, zonedDateKey, zonedTimeOfDay } from "../calendar-model.js";
|
|
5
|
-
import { itemDaySpan, layoutMonthSpans } from "../scheduling-calendar-model.js";
|
|
5
|
+
import { TOP_LANE_CROSS_MIDNIGHT_MINUTES, itemDaySpan, layoutMonthSpans } from "../scheduling-calendar-model.js";
|
|
6
|
+
import { SchedulingAgenda } from "./SchedulingAgenda.js";
|
|
7
|
+
import { SchedulingTimeGrid } from "./SchedulingTimeGrid.js";
|
|
6
8
|
import { cx } from "../cx.js";
|
|
7
9
|
import { useUixLabels } from "../labels-context.js";
|
|
8
10
|
import { StatusPill } from "./StatusPill.js";
|
|
@@ -15,9 +17,12 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
|
|
|
15
17
|
nextMonth: "Next month",
|
|
16
18
|
previousWeek: "Previous week",
|
|
17
19
|
nextWeek: "Next week",
|
|
20
|
+
previousDay: "Previous day",
|
|
21
|
+
nextDay: "Next day",
|
|
18
22
|
viewGroup: "Calendar view",
|
|
19
23
|
viewMonth: "Month",
|
|
20
24
|
viewWeek: "Week",
|
|
25
|
+
viewDay: "Day",
|
|
21
26
|
viewAgenda: "Agenda",
|
|
22
27
|
loading: "Loading schedule\u2026",
|
|
23
28
|
retry: "Try again",
|
|
@@ -34,17 +39,32 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
|
|
|
34
39
|
moreSpansLabel: "{count} more spanning this week, from {date}",
|
|
35
40
|
dayCount: "{count} entries",
|
|
36
41
|
states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" },
|
|
37
|
-
statuses: { tentative: "Tentative", committed: "Scheduled", live: "In progress", done: "Done", dead: "Cancelled" }
|
|
42
|
+
statuses: { tentative: "Tentative", committed: "Scheduled", live: "In progress", done: "Done", dead: "Cancelled" },
|
|
43
|
+
timeZone: "{timeZone}",
|
|
44
|
+
windows: "Windows",
|
|
45
|
+
moreWindows: "+{count} windows",
|
|
46
|
+
moreWindowsLabel: "{count} more windows, from {date}",
|
|
47
|
+
topLane: "All-day and longer entries",
|
|
48
|
+
continuesFrom: "from {time}",
|
|
49
|
+
allDay: "All day",
|
|
50
|
+
hiddenInDay: "{count} not shown \u2014 open day",
|
|
51
|
+
continuesInDay: "Continues: {count} listed under an earlier day",
|
|
52
|
+
moveHint: "Hold Shift and press an arrow key to move it. Enter confirms, Escape cancels.",
|
|
53
|
+
moveProposed: "Move to {start} \u2013 {end}. Enter confirms, Escape cancels.",
|
|
54
|
+
moveCancelled: "Move cancelled.",
|
|
55
|
+
gapForward: "That time does not exist on this day. Moved forward to {time}."
|
|
38
56
|
};
|
|
39
57
|
const stateTone = (state) => state === "scheduled" ? "info" : state === "in-progress" ? "success" : "danger";
|
|
40
58
|
const utcDate = (date) => /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
41
59
|
const INTL_PARTS = {
|
|
42
60
|
day: { dateStyle: "full", timeZone: "UTC" },
|
|
43
61
|
weekday: { weekday: "short", timeZone: "UTC" },
|
|
44
|
-
month: { month: "long", year: "numeric", timeZone: "UTC" }
|
|
62
|
+
month: { month: "long", year: "numeric", timeZone: "UTC" },
|
|
63
|
+
column: { weekday: "short", day: "2-digit", month: "2-digit", timeZone: "UTC" }
|
|
45
64
|
};
|
|
46
65
|
const EMPTY_OVERLAYS = [];
|
|
47
66
|
const EMPTY_ENTRIES = [];
|
|
67
|
+
const EMPTY_IDS = [];
|
|
48
68
|
const DEFAULT_LANE_CAP = 2;
|
|
49
69
|
const DEFAULT_CHIP_ROWS = 3;
|
|
50
70
|
function coveredDays(start, end, timeZone) {
|
|
@@ -54,13 +74,14 @@ function coveredDays(start, end, timeZone) {
|
|
|
54
74
|
return itemDaySpan(start, start, timeZone);
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
|
-
function Marker({ marker }) {
|
|
77
|
+
function Marker({ marker, showLabel }) {
|
|
58
78
|
const emphasis = marker.emphasis ?? "neutral";
|
|
59
79
|
return /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__marker", "data-emphasis": emphasis, "data-glyph": marker.icon == null ? emphasis : void 0, children: [
|
|
60
80
|
marker.icon != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__marker-icon", "aria-hidden": "true", children: marker.icon }),
|
|
61
|
-
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: marker.label })
|
|
81
|
+
/* @__PURE__ */ jsx("span", { className: showLabel ? "uix-scheduling-calendar__marker-label" : "uix-visually-hidden", children: marker.label })
|
|
62
82
|
] });
|
|
63
83
|
}
|
|
84
|
+
const renderMarker = (marker, showLabel) => /* @__PURE__ */ jsx(Marker, { marker, showLabel });
|
|
64
85
|
function SchedulingCalendar({
|
|
65
86
|
entries,
|
|
66
87
|
anchorDate,
|
|
@@ -91,6 +112,18 @@ function SchedulingCalendar({
|
|
|
91
112
|
notice,
|
|
92
113
|
emptyNote,
|
|
93
114
|
onSelectDate,
|
|
115
|
+
agendaGroups,
|
|
116
|
+
agendaHeadingLevel = 3,
|
|
117
|
+
virtualizeAbove = 200,
|
|
118
|
+
monthDensity = "full",
|
|
119
|
+
timeGrid = false,
|
|
120
|
+
now,
|
|
121
|
+
canMove = false,
|
|
122
|
+
step = 15,
|
|
123
|
+
onProposeMove,
|
|
124
|
+
topLaneCrossMidnightMinutes = TOP_LANE_CROSS_MIDNIGHT_MINUTES,
|
|
125
|
+
topLaneCap = 3,
|
|
126
|
+
maxLanes = 4,
|
|
94
127
|
loading,
|
|
95
128
|
error,
|
|
96
129
|
onRetry,
|
|
@@ -106,7 +139,7 @@ function SchedulingCalendar({
|
|
|
106
139
|
statuses: { ...DEFAULT_SCHEDULING_CALENDAR_LABELS.statuses, ...uixLabels.schedulingCalendar?.statuses, ...labelOverrides?.statuses }
|
|
107
140
|
};
|
|
108
141
|
const stateLabel = labels.states;
|
|
109
|
-
const viewLabel = { month: labels.viewMonth, week: labels.viewWeek, agenda: labels.viewAgenda };
|
|
142
|
+
const viewLabel = { month: labels.viewMonth, week: labels.viewWeek, day: labels.viewDay, agenda: labels.viewAgenda };
|
|
110
143
|
const [internalView, setInternalView] = useState("month");
|
|
111
144
|
const view = controlledView ?? internalView;
|
|
112
145
|
const [activeDate, setActiveDate] = useState(anchorDate);
|
|
@@ -116,7 +149,8 @@ function SchedulingCalendar({
|
|
|
116
149
|
const gridRef = useRef(null);
|
|
117
150
|
const picksGiven = dayEntries !== void 0;
|
|
118
151
|
const controlled = picksGiven || dayInfo !== void 0;
|
|
119
|
-
const
|
|
152
|
+
const countsOnly = monthDensity === "counts" && view === "month";
|
|
153
|
+
const fixed = controlled || maxEntriesPerDay !== void 0 && onShowMore !== void 0 || countsOnly;
|
|
120
154
|
const windowCap = Math.max(0, windowLaneCap ?? (fixed ? DEFAULT_LANE_CAP : Infinity));
|
|
121
155
|
const spanCap = Math.max(0, spanLaneCap ?? (fixed ? DEFAULT_LANE_CAP : Infinity));
|
|
122
156
|
const visibleEntries = useMemo(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
|
|
@@ -124,7 +158,8 @@ function SchedulingCalendar({
|
|
|
124
158
|
const weekday = (utcDate(anchorDate).getUTCDay() - weekStartsOn + 7) % 7;
|
|
125
159
|
const weekStart = addCalendarDays(anchorDate, -weekday);
|
|
126
160
|
const weekDays = useMemo(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
|
|
127
|
-
const days = useMemo(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
|
|
161
|
+
const days = useMemo(() => view === "month" ? monthDays.map((day) => day.date) : view === "day" ? [anchorDate] : weekDays, [view, monthDays, weekDays, anchorDate]);
|
|
162
|
+
const timeGridView = view === "day" || view === "week" && timeGrid;
|
|
128
163
|
const { singles, multiDay, coverage } = useMemo(() => {
|
|
129
164
|
const byDay = /* @__PURE__ */ new Map();
|
|
130
165
|
const spans = [];
|
|
@@ -144,7 +179,7 @@ function SchedulingCalendar({
|
|
|
144
179
|
const overlayById = useMemo(() => new Map([...overlays].reverse().map((overlay) => [overlay.id, overlay])), [overlays]);
|
|
145
180
|
const layout = useMemo(() => {
|
|
146
181
|
if (spanLayout) return spanLayout;
|
|
147
|
-
if (view === "agenda") return { placed: [], hiddenByRow: {}, firstHiddenDayByRow: {} };
|
|
182
|
+
if (view === "agenda" || timeGridView) return { placed: [], hiddenByRow: {}, firstHiddenDayByRow: {} };
|
|
148
183
|
const inputs = [];
|
|
149
184
|
const windowIds = /* @__PURE__ */ new Set();
|
|
150
185
|
const itemIds = /* @__PURE__ */ new Set();
|
|
@@ -153,13 +188,13 @@ function SchedulingCalendar({
|
|
|
153
188
|
windowIds.add(overlay.id);
|
|
154
189
|
inputs.push({ id: overlay.id, start: overlay.start, end: overlay.end, group: "window" });
|
|
155
190
|
}
|
|
156
|
-
for (const { entry } of multiDay) {
|
|
191
|
+
for (const { entry } of countsOnly ? [] : multiDay) {
|
|
157
192
|
if (itemIds.has(entry.id)) continue;
|
|
158
193
|
itemIds.add(entry.id);
|
|
159
194
|
inputs.push({ id: entry.id, start: entry.start, end: entry.end, group: "item" });
|
|
160
195
|
}
|
|
161
196
|
return layoutMonthSpans(inputs, days, { timeZone, laneCap: { window: windowCap, item: spanCap } });
|
|
162
|
-
}, [spanLayout, view, overlays, multiDay, days, timeZone, windowCap, spanCap]);
|
|
197
|
+
}, [spanLayout, view, timeGridView, overlays, multiDay, days, timeZone, windowCap, spanCap, countsOnly]);
|
|
163
198
|
const placedByRow = useMemo(() => {
|
|
164
199
|
const rows = /* @__PURE__ */ new Map();
|
|
165
200
|
for (const placed of layout.placed) {
|
|
@@ -178,9 +213,9 @@ function SchedulingCalendar({
|
|
|
178
213
|
if (controlledView === void 0) setInternalView(next);
|
|
179
214
|
onViewChange?.(next);
|
|
180
215
|
};
|
|
181
|
-
const movePeriod = (direction) => onAnchorDateChange?.(view === "month" ? addCalendarMonths(anchorDate, direction) : addCalendarDays(anchorDate, direction * 7));
|
|
216
|
+
const movePeriod = (direction) => onAnchorDateChange?.(view === "month" ? addCalendarMonths(anchorDate, direction) : addCalendarDays(anchorDate, view === "day" ? direction : direction * 7));
|
|
182
217
|
const periodLabel = (direction) => {
|
|
183
|
-
const key = `${direction}${view === "month" ? "Month" : "Week"}`;
|
|
218
|
+
const key = `${direction}${view === "month" ? "Month" : view === "day" ? "Day" : "Week"}`;
|
|
184
219
|
return overrides[key] ?? overrides[direction] ?? labels[key];
|
|
185
220
|
};
|
|
186
221
|
const focusDate = (date) => {
|
|
@@ -248,13 +283,13 @@ function SchedulingCalendar({
|
|
|
248
283
|
["--uix-scheduling-calendar-lane"]: placed.group === "window" ? placed.lane : windowLanes + placed.lane
|
|
249
284
|
});
|
|
250
285
|
const nothingToShow = visibleEntries.length === 0 && !Object.values(dayEntries ?? {}).some((list) => list.length > 0) && !Object.values(dayInfo ?? {}).some((day) => day.count > 0);
|
|
251
|
-
const chipRows = Math.max(0, maxEntriesPerDay ?? DEFAULT_CHIP_ROWS, ...Object.values(dayEntries ?? {}).map((list) => list.length));
|
|
286
|
+
const chipRows = countsOnly ? -1 : Math.max(0, maxEntriesPerDay ?? DEFAULT_CHIP_ROWS, ...Object.values(dayEntries ?? {}).map((list) => list.length));
|
|
252
287
|
const renderDay = (date, index) => {
|
|
253
288
|
const info = dayInfo?.[date];
|
|
254
289
|
const all = picksGiven ? dayEntries[date] ?? EMPTY_ENTRIES : singles.get(date) ?? EMPTY_ENTRIES;
|
|
255
290
|
const overflowing = !picksGiven && maxEntriesPerDay !== void 0 && all.length > maxEntriesPerDay;
|
|
256
291
|
const collapsed = overflowing && (controlled || onShowMore !== void 0 || !expandedDates.has(date));
|
|
257
|
-
const shown = collapsed ? all.slice(0, Math.max(0, maxEntriesPerDay)) : all;
|
|
292
|
+
const shown = countsOnly ? EMPTY_ENTRIES : collapsed ? all.slice(0, Math.max(0, maxEntriesPerDay)) : all;
|
|
258
293
|
const hiddenCount = info ? info.overflowCount : picksGiven ? 0 : all.length - shown.length;
|
|
259
294
|
const onDay = picksGiven ? all : entriesOn(date);
|
|
260
295
|
const badge = renderDayBadge?.(date, onDay);
|
|
@@ -274,7 +309,7 @@ function SchedulingCalendar({
|
|
|
274
309
|
] }) : dateButton,
|
|
275
310
|
/* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__entries", children: [
|
|
276
311
|
shown.map((entry) => /* @__PURE__ */ jsx("button", { className: "uix-scheduling-calendar__entry", ...entryProps(entry), children: entryContent(entry) }, entry.id)),
|
|
277
|
-
controlled ? hiddenCount > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, onDay), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
|
|
312
|
+
countsOnly ? null : controlled ? hiddenCount > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, onDay), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
|
|
278
313
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
|
|
279
314
|
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
|
|
280
315
|
] })) : overflowing && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-expanded": onShowMore ? void 0 : !collapsed, "aria-label": collapsed ? moreName : void 0, onClick: () => toggleMore(date, onDay), children: collapsed ? moreText : labels.fewerEntries })
|
|
@@ -284,8 +319,10 @@ function SchedulingCalendar({
|
|
|
284
319
|
const renderWeek = (row) => {
|
|
285
320
|
const rowDays = days.slice(row * 7, row * 7 + 7);
|
|
286
321
|
const windowLanes = lanesUsed(row, "window");
|
|
287
|
-
const spanLanes = lanesUsed(row, "item");
|
|
288
|
-
const
|
|
322
|
+
const spanLanes = countsOnly ? 0 : lanesUsed(row, "item");
|
|
323
|
+
const hiddenIds = layout.hiddenByRow?.[row] ?? EMPTY_IDS;
|
|
324
|
+
const drawnWindows = countsOnly ? new Set((placedByRow.get(row) ?? []).filter((placed) => placed.group === "window").map((placed) => placed.id)) : void 0;
|
|
325
|
+
const hidden = countsOnly ? hiddenIds.filter((id) => overlayById.has(id) && !drawnWindows.has(id)).length : hiddenIds.length;
|
|
289
326
|
const firstHiddenDay = layout.firstHiddenDayByRow?.[row] ?? rowDays[0];
|
|
290
327
|
const moreText = fillLabel(labels.moreSpans, { count: hidden });
|
|
291
328
|
const moreName = fillLabel(labels.moreSpansLabel, { count: hidden, date: dateText(firstHiddenDay, "day") });
|
|
@@ -304,7 +341,7 @@ function SchedulingCalendar({
|
|
|
304
341
|
] }) }, `window-${placed.id}`);
|
|
305
342
|
}
|
|
306
343
|
const entry = entryById.get(placed.id);
|
|
307
|
-
if (!entry) return null;
|
|
344
|
+
if (!entry || countsOnly) return null;
|
|
308
345
|
return /* @__PURE__ */ jsx("button", { className: "uix-scheduling-calendar__entry uix-scheduling-calendar__span", ...entryProps(entry), ...continues, style: laneStyle(placed, windowLanes), children: entryContent(entry, !placed.continuesBefore) }, `span-${placed.id}`);
|
|
309
346
|
}),
|
|
310
347
|
hidden > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__rowmore", "aria-label": moreName, onClick: () => onShowMore(firstHiddenDay, picksGiven ? dayEntries[firstHiddenDay] ?? EMPTY_ENTRIES : entriesOn(firstHiddenDay)), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__rowmore", children: [
|
|
@@ -316,7 +353,7 @@ function SchedulingCalendar({
|
|
|
316
353
|
const gridStyle = fixed ? {
|
|
317
354
|
["--uix-scheduling-calendar-chips"]: chipRows,
|
|
318
355
|
["--uix-scheduling-calendar-lane-cap"]: Math.max(
|
|
319
|
-
Math.max(Number.isFinite(windowCap) ? windowCap : 0, lanesUsedAnywhere("window")) + Math.max(Number.isFinite(spanCap) ? spanCap : 0, lanesUsedAnywhere("item")),
|
|
356
|
+
Math.max(Number.isFinite(windowCap) ? windowCap : 0, lanesUsedAnywhere("window")) + (countsOnly ? 0 : Math.max(Number.isFinite(spanCap) ? spanCap : 0, lanesUsedAnywhere("item"))),
|
|
320
357
|
Object.keys(layout.hiddenByRow ?? {}).length > 0 ? 1 : 0
|
|
321
358
|
)
|
|
322
359
|
} : void 0;
|
|
@@ -326,14 +363,35 @@ function SchedulingCalendar({
|
|
|
326
363
|
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(-1), disabled: !onAnchorDateChange, children: periodLabel("previous") }),
|
|
327
364
|
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children: periodLabel("next") })
|
|
328
365
|
] }),
|
|
329
|
-
/* @__PURE__ */ jsx("strong", { children: view === "month" ? dateText(startOfMonth(anchorDate), "month") : `${dateText(weekStart, "day")} \u2013 ${dateText(weekDays[weekDays.length - 1], "day")}` }),
|
|
330
|
-
/* @__PURE__ */ jsx("div", { className: "uix-segmented", role: "group", "aria-label": labels.viewGroup, children: ["month", "week", "agenda"].map((option) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-segmented__option", "aria-pressed": view === option, "data-selected": view === option || void 0, onClick: () => setView(option), children: viewLabel[option] }, option)) })
|
|
366
|
+
/* @__PURE__ */ jsx("strong", { children: view === "month" ? dateText(startOfMonth(anchorDate), "month") : view === "day" ? dateText(anchorDate, "day") : `${dateText(weekStart, "day")} \u2013 ${dateText(weekDays[weekDays.length - 1], "day")}` }),
|
|
367
|
+
/* @__PURE__ */ jsx("div", { className: "uix-segmented", role: "group", "aria-label": labels.viewGroup, children: (timeGrid || view === "day" ? ["month", "week", "day", "agenda"] : ["month", "week", "agenda"]).map((option) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-segmented__option", "aria-pressed": view === option, "data-selected": view === option || void 0, onClick: () => setView(option), children: viewLabel[option] }, option)) })
|
|
331
368
|
] }),
|
|
332
369
|
notice != null && notice !== false && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__notice", children: notice }),
|
|
333
370
|
loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__state", role: "alert", children: [
|
|
334
371
|
/* @__PURE__ */ jsx("p", { children: error }),
|
|
335
372
|
onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
|
|
336
|
-
] }) : view === "agenda"
|
|
373
|
+
] }) : view === "agenda" && agendaGroups ? /* @__PURE__ */ jsx(
|
|
374
|
+
SchedulingAgenda,
|
|
375
|
+
{
|
|
376
|
+
groups: agendaGroups,
|
|
377
|
+
timeZone,
|
|
378
|
+
labels,
|
|
379
|
+
headingLevel: agendaHeadingLevel,
|
|
380
|
+
virtualizeAbove,
|
|
381
|
+
dateText,
|
|
382
|
+
formatInstant,
|
|
383
|
+
entryName,
|
|
384
|
+
entryStatus: statusOf,
|
|
385
|
+
entryStatusText: stateText,
|
|
386
|
+
entryMarkers: markersOf,
|
|
387
|
+
overlayName,
|
|
388
|
+
renderEntry,
|
|
389
|
+
renderMarker,
|
|
390
|
+
onSelectEntry,
|
|
391
|
+
onSelectOverlay,
|
|
392
|
+
onShowMore
|
|
393
|
+
}
|
|
394
|
+
) : view === "agenda" ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": labels.agenda, children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx("p", { children: labels.agendaEmpty }) : /* @__PURE__ */ jsx("ol", { children: [...visibleEntries].sort((a, b) => a.start.localeCompare(b.start)).map((entry) => {
|
|
337
395
|
const markers = markersOf(entry);
|
|
338
396
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", "data-item-id": entry.id, "data-band": entry.band ?? "none", "data-status": statusOf(entry), "data-state": entry.state, onClick: () => onSelectEntry?.(entry), children: [
|
|
339
397
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -348,7 +406,41 @@ function SchedulingCalendar({
|
|
|
348
406
|
markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: markers.map((marker) => /* @__PURE__ */ jsx(Marker, { marker }, marker.id)) }),
|
|
349
407
|
entry.state ? /* @__PURE__ */ jsx(StatusPill, { tone: stateTone(entry.state), children: stateLabel[entry.state] }) : /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__status", children: stateText(entry) })
|
|
350
408
|
] }) }, entry.id);
|
|
351
|
-
}) }) }) : /* @__PURE__ */
|
|
409
|
+
}) }) }) : timeGridView ? /* @__PURE__ */ jsx(
|
|
410
|
+
SchedulingTimeGrid,
|
|
411
|
+
{
|
|
412
|
+
view: view === "day" ? "day" : "week",
|
|
413
|
+
days,
|
|
414
|
+
timeZone,
|
|
415
|
+
entries: visibleEntries,
|
|
416
|
+
overlays,
|
|
417
|
+
dayInfo,
|
|
418
|
+
labels,
|
|
419
|
+
gridLabel: fillLabel(labels.grid, { view: viewLabel[view] }),
|
|
420
|
+
dateText,
|
|
421
|
+
formatInstant,
|
|
422
|
+
entryName,
|
|
423
|
+
entryStatus: statusOf,
|
|
424
|
+
entryMarkers: markersOf,
|
|
425
|
+
overlayName,
|
|
426
|
+
renderEntry,
|
|
427
|
+
renderMarker,
|
|
428
|
+
onSelectEntry,
|
|
429
|
+
onSelectOverlay,
|
|
430
|
+
onSelectDate,
|
|
431
|
+
onShowMore,
|
|
432
|
+
now,
|
|
433
|
+
canMove,
|
|
434
|
+
step,
|
|
435
|
+
onProposeMove,
|
|
436
|
+
topLaneCrossMidnightMinutes,
|
|
437
|
+
topLaneCap,
|
|
438
|
+
windowLaneCap: windowLaneCap ?? DEFAULT_LANE_CAP,
|
|
439
|
+
maxLanes,
|
|
440
|
+
emptyNote,
|
|
441
|
+
nothingToShow
|
|
442
|
+
}
|
|
443
|
+
) : /* @__PURE__ */ jsxs("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), "data-fixed": fixed || void 0, "data-density": countsOnly ? "counts" : void 0, style: gridStyle, role: "group", "aria-label": fillLabel(labels.grid, { view: viewLabel[view] }), children: [
|
|
352
444
|
days.slice(0, 7).map((date) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__weekday", "aria-hidden": "true", children: dateText(date, "weekday") }, `weekday-${date}`)),
|
|
353
445
|
Array.from({ length: days.length / 7 }, (_, row) => renderWeek(row)),
|
|
354
446
|
emptyNote != null && emptyNote !== false && nothingToShow && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__empty", children: emptyNote })
|