@remit/web-client 0.0.232 → 0.0.233

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.
@@ -8,6 +8,7 @@ import { describe, it } from "node:test";
8
8
  import { CALENDAR_DENSITY_KEY } from "./calendar-density.js";
9
9
  import {
10
10
  calendarSearchSchema,
11
+ calendarViewMountsAgenda,
11
12
  calendarViewMountsGrid,
12
13
  calendarViews,
13
14
  canonicalCalendarParams,
@@ -89,13 +90,31 @@ describe("calendarViewMountsGrid", () => {
89
90
  assert.equal(calendarViewMountsGrid("day"), true);
90
91
  });
91
92
 
92
- it("has the other three still to come", () => {
93
+ it("leaves the agenda to the strip and the other two to a later stage", () => {
93
94
  assert.equal(calendarViewMountsGrid("year"), false);
94
95
  assert.equal(calendarViewMountsGrid("month"), false);
95
96
  assert.equal(calendarViewMountsGrid("agenda"), false);
96
97
  });
97
98
  });
98
99
 
100
+ describe("calendarViewMountsAgenda", () => {
101
+ it("draws the strip at one zoom and at no other", () => {
102
+ for (const view of calendarViews) {
103
+ assert.equal(calendarViewMountsAgenda(view), view === "agenda");
104
+ }
105
+ });
106
+
107
+ it("never draws the same zoom as the grid", () => {
108
+ for (const view of calendarViews) {
109
+ assert.equal(
110
+ calendarViewMountsAgenda(view) && calendarViewMountsGrid(view),
111
+ false,
112
+ `${view} claimed two surfaces`,
113
+ );
114
+ }
115
+ });
116
+ });
117
+
99
118
  describe("stepCalendarDate", () => {
100
119
  it("steps a screenful in the view's own unit", () => {
101
120
  assert.equal(stepCalendarDate("2026-06-10", "day", 1), "2026-06-11");
@@ -32,15 +32,24 @@ const calendarViewSchema = z.enum(calendarViews);
32
32
  export const DEFAULT_CALENDAR_VIEW: CalendarViewId = "week";
33
33
 
34
34
  /**
35
- * The views the grid draws today. The other three are named surfaces that
36
- * arrive in later stages, and they say so on screen rather than matching a
37
- * route that renders nothing.
35
+ * The views the grid draws. Year and month are named surfaces that arrive in
36
+ * later stages, and they say so on screen rather than matching a route that
37
+ * renders nothing.
38
38
  */
39
39
  const GRID_VIEWS = new Set<CalendarViewId>(["week", "day"]);
40
40
 
41
41
  export const calendarViewMountsGrid = (view: CalendarViewId): boolean =>
42
42
  GRID_VIEWS.has(view);
43
43
 
44
+ /**
45
+ * The one view the strip draws. It is a zoom of the same ladder rather than a
46
+ * different screen: the grid is what a reader drops into for a day that has
47
+ * earned it, and the strip is what they come back out to, with the day and the
48
+ * ticked calendars they left with.
49
+ */
50
+ export const calendarViewMountsAgenda = (view: CalendarViewId): boolean =>
51
+ view === "agenda";
52
+
44
53
  /** The view a segment names, or `undefined` where it names none. */
45
54
  export function parseCalendarView(segment: string): CalendarViewId | undefined {
46
55
  const parsed = calendarViewSchema.safeParse(segment);
@@ -101,7 +110,8 @@ export function canonicalCalendarParams(
101
110
  /**
102
111
  * The day the previous or next screenful starts on. A step is measured in the
103
112
  * view's own unit, which is what makes back-and-forward land where the reader
104
- * came from at every zoom. Agenda steps a week, the range it loads.
113
+ * came from at every zoom. Agenda steps a week, which is far enough to be worth
114
+ * a press and near enough to stay inside the days it is already holding.
105
115
  */
106
116
  export function stepCalendarDate(
107
117
  date: string,
@@ -16,6 +16,7 @@
16
16
  import type { CalendarSlotPick } from "@remit/ui";
17
17
  import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
18
18
  import { useCallback, useState } from "react";
19
+ import { AgendaView } from "@/components/calendar/AgendaView";
19
20
  import { CalendarComposeSeedProvider } from "@/components/calendar/CalendarComposeSeed";
20
21
  import { CalendarShell } from "@/components/calendar/CalendarShell";
21
22
  import { CalendarWorkspace } from "@/components/calendar/CalendarWorkspace";
@@ -27,6 +28,7 @@ import {
27
28
  } from "@/lib/calendar-density";
28
29
  import {
29
30
  calendarSearchSchema,
31
+ calendarViewMountsAgenda,
30
32
  canonicalCalendarParams,
31
33
  isoDate,
32
34
  } from "@/lib/calendar-route";
@@ -55,8 +57,13 @@ export const Route = createFileRoute("/calendar/$view/$date")({
55
57
 
56
58
  function CalendarViewLayout() {
57
59
  const { view, date, calendarIds } = useCalendarAddress();
60
+ // The strip fetches the days it holds a week at a time and draws none of
61
+ // this, so at that zoom the layout asks for nothing: the address rewrites on
62
+ // every scroll, and each rewrite would otherwise fetch a week nobody renders
63
+ // along with the two beside it.
64
+ const drawsGrid = !calendarViewMountsAgenda(view);
58
65
  const { events, colorByCalendarId, isLoading, error, retry, instanceOf } =
59
- useCalendarData({ view, date, calendarIds });
66
+ useCalendarData({ view, date, calendarIds, enabled: drawsGrid });
60
67
  const { goToView, goToToday, step, openEvent, openComposer } =
61
68
  useCalendarNavigation();
62
69
  const openedEvent = useOpenCalendarEvent();
@@ -93,6 +100,7 @@ function CalendarViewLayout() {
93
100
  date={date}
94
101
  events={events}
95
102
  colorByCalendarId={colorByCalendarId}
103
+ agenda={<AgendaView density={density} onPickSlot={pickSlot} />}
96
104
  isLoading={isLoading}
97
105
  error={error}
98
106
  onRetry={retry}
@@ -82,6 +82,14 @@ export interface CalendarNavigation {
82
82
  step: (direction: -1 | 1) => void;
83
83
  /** Home, from every view and every distance away. */
84
84
  goToToday: () => void;
85
+ /**
86
+ * The day the strip has scrolled to, written by `replace`. Scrolling is not
87
+ * somewhere the reader went — Back belongs to the screen they arrived from,
88
+ * not to every row they passed on the way down.
89
+ */
90
+ setAnchorDate: (date: string) => void;
91
+ /** Out of the list and into the grid for one day. */
92
+ zoomToDay: (date: string) => void;
85
93
  /**
86
94
  * One occurrence names the series it belongs to and the instance under it;
87
95
  * a resource that does not recur names only itself.
@@ -122,6 +130,15 @@ export function useCalendarNavigation(): CalendarNavigation {
122
130
  step: (direction: -1 | 1) =>
123
131
  goTo({ view, date: stepCalendarDate(date, view, direction) }),
124
132
  goToToday: () => goTo({ view, date: isoDate(new Date()) }),
133
+ setAnchorDate: (next: string) =>
134
+ navigate({
135
+ to: VIEW_ROUTE,
136
+ params: { view, date: next },
137
+ search: true,
138
+ hash: retainPanels,
139
+ replace: true,
140
+ }),
141
+ zoomToDay: (next: string) => goTo({ view: "day", date: next }),
125
142
  openEvent: (calendarObjectId: string, recurrenceId?: string) =>
126
143
  recurrenceId === undefined || recurrenceId === ""
127
144
  ? navigate({