@rapidmx/web-client 0.3.1 → 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.
Files changed (202) hide show
  1. package/apps/admin/audit-log/index.tsx +35 -8
  2. package/apps/admin/branding/index.tsx +39 -404
  3. package/apps/admin/data-requests/index.tsx +480 -485
  4. package/apps/admin/distribution-lists/[uid].tsx +19 -1
  5. package/apps/admin/domains/[uid].tsx +92 -259
  6. package/apps/admin/encryption-policy/index.tsx +19 -0
  7. package/apps/admin/escrow-scopes/[uid].tsx +348 -237
  8. package/apps/admin/escrow-scopes/new/index.tsx +8 -2
  9. package/apps/admin/index.tsx +118 -82
  10. package/apps/admin/ingest-queue/index.tsx +44 -5
  11. package/apps/admin/mailbox-policy/index.tsx +19 -0
  12. package/apps/admin/mailboxes/[uid].tsx +211 -184
  13. package/apps/admin/mailboxes/new/index.tsx +28 -291
  14. package/apps/admin/plugins/index.tsx +15 -0
  15. package/apps/admin/quarantine/index.tsx +46 -14
  16. package/apps/admin/retention-policy/index.tsx +39 -132
  17. package/apps/admin/setup/index.tsx +15 -0
  18. package/apps/admin/transport-rules/[uid].tsx +30 -1
  19. package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
  20. package/apps/admin/transport-rules/index.tsx +24 -6
  21. package/apps/admin/transport-rules/new/index.tsx +30 -1
  22. package/apps/escrow/_layout.tsx +2 -3
  23. package/apps/escrow/audit-log/index.tsx +196 -168
  24. package/apps/escrow/matters/[uid].tsx +621 -521
  25. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
  26. package/apps/shared/components/admin/layout/AdminShell.tsx +251 -210
  27. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
  28. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  29. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
  30. package/apps/shared/components/admin/settings/BrandingForm.tsx +385 -0
  31. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +187 -0
  32. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +112 -0
  33. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +34 -0
  34. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +308 -0
  35. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +152 -0
  36. package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -0
  37. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -0
  38. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +290 -0
  39. package/apps/shared/components/admin/setup/SetupWizard.tsx +416 -0
  40. package/apps/shared/components/admin/signOut.ts +79 -0
  41. package/apps/shared/components/admin/usePagedList.tsx +129 -0
  42. package/apps/shared/components/calendar/CalendarListSidebar.tsx +120 -76
  43. package/apps/shared/components/calendar/EventModal.tsx +717 -513
  44. package/apps/shared/components/calendar/MonthView.tsx +173 -139
  45. package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
  46. package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
  47. package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
  48. package/apps/shared/components/calendar/allDay.ts +124 -0
  49. package/apps/shared/components/calendar/layout/CalendarShell.tsx +80 -99
  50. package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
  51. package/apps/shared/components/contacts/ContactForm.tsx +377 -314
  52. package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
  53. package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
  54. package/apps/shared/components/contacts/contactKeys.ts +78 -0
  55. package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
  56. package/apps/shared/components/layout/AppShell.tsx +270 -169
  57. package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
  58. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +150 -26
  59. package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
  60. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
  61. package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -0
  62. package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
  63. package/apps/shared/components/mail/MessageDetailPane.tsx +799 -76
  64. package/apps/shared/components/mail/compose/ComposeContext.tsx +26 -16
  65. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1652 -765
  66. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
  67. package/apps/shared/components/mail/layout/MailShell.tsx +418 -302
  68. package/apps/shared/components/mail/pinnedSigners.ts +124 -0
  69. package/apps/shared/components/mail/verificationSeals.ts +125 -0
  70. package/apps/shared/components/mail/writableMailboxes.ts +101 -0
  71. package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
  72. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  73. package/apps/shared/mail/findWellKnownFolderUid.ts +13 -0
  74. package/apps/shared/mail/listAllPages.ts +39 -0
  75. package/apps/shared/search/LocalIndexLifecycle.tsx +98 -0
  76. package/apps/shared/search/localIndexBlockCipher.ts +83 -0
  77. package/apps/shared/search/localIndexBuilder.ts +481 -0
  78. package/apps/shared/search/localIndexKey.ts +27 -0
  79. package/apps/shared/search/localIndexRpcClient.ts +316 -0
  80. package/apps/shared/search/localIndexSchema.ts +236 -0
  81. package/apps/shared/search/localIndexSizePreference.ts +88 -0
  82. package/apps/shared/search/localIndexStorage.ts +98 -0
  83. package/apps/shared/search/localIndexVFS.ts +266 -0
  84. package/apps/shared/search/localIndexWorker.ts +926 -0
  85. package/apps/shared/search/searchTier2.ts +79 -0
  86. package/apps/shared/search/wa-sqlite-shims.d.ts +44 -0
  87. package/apps/www/calendar/index.tsx +438 -407
  88. package/apps/www/contacts/[uid].tsx +21 -2
  89. package/apps/www/contacts/index.tsx +148 -17
  90. package/apps/www/index.tsx +1314 -486
  91. package/apps/www/messages/[uid].tsx +23 -7
  92. package/apps/www/settings/auto-reply/index.tsx +134 -129
  93. package/apps/www/settings/booking-types/[uid].tsx +306 -300
  94. package/apps/www/settings/encryption/index.tsx +800 -215
  95. package/apps/www/settings/filters/[uid].tsx +171 -155
  96. package/apps/www/settings/filters/new/index.tsx +8 -1
  97. package/apps/www/settings/focused-inbox/index.tsx +150 -148
  98. package/apps/www/settings/privacy/index.tsx +494 -436
  99. package/apps/www/settings/read-receipts/index.tsx +148 -125
  100. package/apps/www/settings/sharing/index.tsx +274 -0
  101. package/apps/www/tasks/index.tsx +638 -530
  102. package/dist/apps/admin/audit-log/index.js +38 -8
  103. package/dist/apps/admin/branding/index.js +4 -98
  104. package/dist/apps/admin/data-requests/index.js +52 -72
  105. package/dist/apps/admin/distribution-lists/[uid].js +1 -1
  106. package/dist/apps/admin/domains/[uid].js +5 -68
  107. package/dist/apps/admin/encryption-policy/index.js +8 -0
  108. package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
  109. package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
  110. package/dist/apps/admin/index.js +17 -1
  111. package/dist/apps/admin/ingest-queue/index.js +24 -6
  112. package/dist/apps/admin/mailbox-policy/index.js +8 -0
  113. package/dist/apps/admin/mailboxes/[uid].js +20 -4
  114. package/dist/apps/admin/mailboxes/new/index.js +4 -89
  115. package/dist/apps/admin/plugins/index.js +6 -0
  116. package/dist/apps/admin/quarantine/index.js +20 -11
  117. package/dist/apps/admin/retention-policy/index.js +3 -36
  118. package/dist/apps/admin/setup/index.js +6 -0
  119. package/dist/apps/admin/transport-rules/[uid].js +18 -2
  120. package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
  121. package/dist/apps/admin/transport-rules/index.js +22 -6
  122. package/dist/apps/admin/transport-rules/new/index.js +18 -2
  123. package/dist/apps/escrow/_layout.js +3 -3
  124. package/dist/apps/escrow/audit-log/index.js +26 -1
  125. package/dist/apps/escrow/matters/[uid].js +77 -42
  126. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
  127. package/dist/apps/shared/components/admin/layout/AdminShell.js +37 -4
  128. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
  129. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  130. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
  131. package/dist/apps/shared/components/admin/settings/BrandingForm.js +116 -0
  132. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +83 -0
  133. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +62 -0
  134. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +25 -0
  135. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +112 -0
  136. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +74 -0
  137. package/dist/apps/shared/components/admin/settings/PluginsManager.js +572 -0
  138. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +73 -0
  139. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +147 -0
  140. package/dist/apps/shared/components/admin/setup/SetupWizard.js +208 -0
  141. package/dist/apps/shared/components/admin/signOut.js +75 -0
  142. package/dist/apps/shared/components/admin/usePagedList.js +99 -0
  143. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +24 -13
  144. package/dist/apps/shared/components/calendar/EventModal.js +154 -21
  145. package/dist/apps/shared/components/calendar/MonthView.js +12 -5
  146. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
  147. package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
  148. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
  149. package/dist/apps/shared/components/calendar/allDay.js +111 -0
  150. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +47 -39
  151. package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
  152. package/dist/apps/shared/components/contacts/ContactForm.js +45 -12
  153. package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
  154. package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
  155. package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
  156. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
  157. package/dist/apps/shared/components/layout/AppShell.js +100 -8
  158. package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
  159. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +63 -14
  160. package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
  161. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
  162. package/dist/apps/shared/components/layout/UnlockPromptProvider.js +136 -0
  163. package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
  164. package/dist/apps/shared/components/mail/MessageDetailPane.js +464 -34
  165. package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
  166. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +807 -116
  167. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
  168. package/dist/apps/shared/components/mail/layout/MailShell.js +116 -41
  169. package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
  170. package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
  171. package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
  172. package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
  173. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  174. package/dist/apps/shared/mail/findWellKnownFolderUid.js +12 -0
  175. package/dist/apps/shared/mail/listAllPages.js +25 -0
  176. package/dist/apps/shared/search/LocalIndexLifecycle.js +80 -0
  177. package/dist/apps/shared/search/localIndexBlockCipher.js +63 -0
  178. package/dist/apps/shared/search/localIndexBuilder.js +408 -0
  179. package/dist/apps/shared/search/localIndexKey.js +25 -0
  180. package/dist/apps/shared/search/localIndexRpcClient.js +241 -0
  181. package/dist/apps/shared/search/localIndexSchema.js +185 -0
  182. package/dist/apps/shared/search/localIndexSizePreference.js +78 -0
  183. package/dist/apps/shared/search/localIndexStorage.js +86 -0
  184. package/dist/apps/shared/search/localIndexVFS.js +258 -0
  185. package/dist/apps/shared/search/localIndexWorker.js +687 -0
  186. package/dist/apps/shared/search/searchTier2.js +38 -0
  187. package/dist/apps/www/calendar/index.js +38 -18
  188. package/dist/apps/www/contacts/[uid].js +13 -2
  189. package/dist/apps/www/contacts/index.js +94 -15
  190. package/dist/apps/www/index.js +770 -137
  191. package/dist/apps/www/messages/[uid].js +23 -7
  192. package/dist/apps/www/settings/auto-reply/index.js +9 -4
  193. package/dist/apps/www/settings/booking-types/[uid].js +17 -12
  194. package/dist/apps/www/settings/encryption/index.js +541 -96
  195. package/dist/apps/www/settings/filters/[uid].js +22 -7
  196. package/dist/apps/www/settings/filters/new/index.js +8 -2
  197. package/dist/apps/www/settings/focused-inbox/index.js +3 -1
  198. package/dist/apps/www/settings/privacy/index.js +57 -22
  199. package/dist/apps/www/settings/read-receipts/index.js +10 -3
  200. package/dist/apps/www/settings/sharing/index.js +124 -0
  201. package/dist/apps/www/tasks/index.js +76 -14
  202. package/package.json +3 -2
@@ -3,45 +3,67 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import React, { FormEvent, useState } from "react";
6
- import { CALENDAR_COLOR_PALETTE, colorForFolder } from "@rapidmx/react-shared/calendar/calendarColors.js";
7
- import { Folder } from "@rapidmx/react-shared/mail/mailApi.js";
6
+ import { CALENDAR_COLOR_PALETTE, accentColorForMailbox, colorForFolder } from "@rapidmx/react-shared/calendar/calendarColors.js";
7
+ import { Folder, Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
8
8
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
9
9
 
10
+ export interface CalendarListSidebarMailbox {
11
+ mailbox: Mailbox;
12
+ calendarFolders: Folder[];
13
+ error?: string;
14
+ }
15
+
10
16
  export interface CalendarListSidebarProps {
11
- /** Every `type === "calendar"` folder for the currently-resolved mailbox. */
12
- calendars: Folder[];
17
+ /** Every accessible mailbox's `calendar`-type folders, grouped per mailbox (see `CalendarShell`). */
18
+ mailboxCalendars: CalendarListSidebarMailbox[];
13
19
  checkedFolderUids: Set<string>;
14
20
  onToggle: (folderUid: string) => void;
15
- onAddCalendar: (name: string, color: string) => Promise<void>;
21
+ onAddCalendar: (mailboxUid: string, name: string, color: string) => Promise<void>;
22
+ /** A calendar's display color - defaults to its own `color` (see `CalendarShell`'s `colorFor`). */
23
+ colorFor?: (folder: Folder) => string;
16
24
  }
17
25
 
18
26
  /**
19
- * Calendar's left-sidebar checklist of "My calendars" — one row per `calendar`-type folder, a color
20
- * swatch matching `colorForFolder`, and a checkbox controlling whether that calendar's events are
21
- * currently shown (the checked set is owned by `apps/www/calendar/index.tsx`, since it drives which
22
- * folders get fanned out to on every reload). "+ Add calendar" is a real `createFolder({type:
23
- * "calendar", ...})` call — see the Phase 4 plan's own note that folder creation has no type
24
- * restriction server-side, so this needed no new backend route.
27
+ * Calendar's left-sidebar checklist of calendars — one row per `calendar`-type folder, a color swatch,
28
+ * and a checkbox controlling whether that calendar's events are currently shown (the checked set is owned
29
+ * by `apps/www/calendar/index.tsx`, since it drives which folders get fanned out to on every reload).
30
+ * With one mailbox this is a single "My calendars" list, as before; with several, each mailbox gets its
31
+ * own section (a shared mailbox labeled as such, with a color-dot accent) and its own "+ Add calendar",
32
+ * which creates the calendar in that mailbox. "+ Add calendar" is a real `createFolder({type: "calendar",
33
+ * ...})` call — folder creation has no type restriction server-side, so this needed no new backend route.
25
34
  */
26
- export default function CalendarListSidebar({ calendars, checkedFolderUids, onToggle, onAddCalendar }: CalendarListSidebarProps) {
27
- const [adding, setAdding] = useState(false);
35
+ export default function CalendarListSidebar({
36
+ mailboxCalendars,
37
+ checkedFolderUids,
38
+ onToggle,
39
+ onAddCalendar,
40
+ colorFor = (folder) => colorForFolder(folder),
41
+ }: CalendarListSidebarProps) {
42
+ const [addingFor, setAddingFor] = useState<string | null>(null);
28
43
  const [name, setName] = useState("");
29
44
  const [color, setColor] = useState(CALENDAR_COLOR_PALETTE[0]);
30
45
  const [error, setError] = useState<string | null>(null);
31
46
  const [saving, setSaving] = useState(false);
47
+ const multiple = mailboxCalendars.length > 1;
48
+
49
+ function startAdding(mailboxUid: string) {
50
+ setAddingFor(mailboxUid);
51
+ setName("");
52
+ setColor(CALENDAR_COLOR_PALETTE[0]);
53
+ }
32
54
 
33
55
  async function handleAdd(e: FormEvent) {
34
56
  e.preventDefault();
35
- if (!name.trim()) {
57
+ if (!name.trim() || !addingFor) {
36
58
  return;
37
59
  }
38
60
  setError(null);
39
61
  setSaving(true);
40
62
  try {
41
- await onAddCalendar(name.trim(), color);
63
+ await onAddCalendar(addingFor, name.trim(), color);
42
64
  setName("");
43
65
  setColor(CALENDAR_COLOR_PALETTE[0]);
44
- setAdding(false);
66
+ setAddingFor(null);
45
67
  } catch {
46
68
  setError("Could not create this calendar.");
47
69
  } finally {
@@ -49,71 +71,93 @@ export default function CalendarListSidebar({ calendars, checkedFolderUids, onTo
49
71
  }
50
72
  }
51
73
 
52
- return (
53
- <nav aria-label="My calendars" className="p-3 border-t border-border">
54
- <div className="flex items-center justify-between mb-1">
55
- <h2 className="text-xs font-bold uppercase tracking-wide text-text-muted">My calendars</h2>
56
- <button
57
- type="button"
58
- onClick={() => setAdding(true)}
59
- aria-label="Add calendar"
60
- className="text-xs font-bold text-primary-dark hover:underline"
61
- >
62
- +
63
- </button>
64
- </div>
65
- {error && (
66
- <div className="mb-1">
67
- <Alert>{error}</Alert>
68
- </div>
69
- )}
70
- <div className="flex flex-col gap-0.5">
71
- {calendars.map((folder) => (
72
- <label key={folder.uid} className="flex items-center gap-2 text-sm px-1 py-1 rounded-sm hover:bg-surface-alt cursor-pointer">
73
- <input
74
- type="checkbox"
75
- checked={checkedFolderUids.has(folder.uid)}
76
- onChange={() => onToggle(folder.uid)}
77
- style={{ accentColor: colorForFolder(folder) }}
78
- />
79
- <span aria-hidden="true" className="w-2.5 h-2.5 rounded-full shrink-0" style={{ backgroundColor: colorForFolder(folder) }} />
80
- <span className="truncate">{folder.name}</span>
81
- </label>
74
+ const addForm = (
75
+ <form onSubmit={handleAdd} className="flex flex-col gap-1.5 mt-2">
76
+ <input
77
+ type="text"
78
+ autoFocus
79
+ aria-label="New calendar name"
80
+ value={name}
81
+ onChange={(e) => setName(e.target.value)}
82
+ className="text-xs py-1 px-1.5 border border-border rounded-sm bg-surface"
83
+ />
84
+ <div className="flex items-center gap-1" role="radiogroup" aria-label="Calendar color">
85
+ {CALENDAR_COLOR_PALETTE.map((swatch) => (
86
+ <button
87
+ key={swatch}
88
+ type="button"
89
+ aria-label={`Color ${swatch}`}
90
+ aria-pressed={color === swatch}
91
+ onClick={() => setColor(swatch)}
92
+ className={["w-4 h-4 rounded-full shrink-0", color === swatch ? "ring-2 ring-offset-1 ring-text" : ""].join(" ")}
93
+ style={{ backgroundColor: swatch }}
94
+ />
82
95
  ))}
83
96
  </div>
84
- {adding && (
85
- <form onSubmit={handleAdd} className="flex flex-col gap-1.5 mt-2">
86
- <input
87
- type="text"
88
- autoFocus
89
- aria-label="New calendar name"
90
- value={name}
91
- onChange={(e) => setName(e.target.value)}
92
- className="text-xs py-1 px-1.5 border border-border rounded-sm bg-surface"
93
- />
94
- <div className="flex items-center gap-1" role="radiogroup" aria-label="Calendar color">
95
- {CALENDAR_COLOR_PALETTE.map((swatch) => (
97
+ <div className="flex items-center gap-2">
98
+ <button type="submit" disabled={saving} className="text-xs font-semibold text-primary-dark">
99
+ Add
100
+ </button>
101
+ <button type="button" onClick={() => setAddingFor(null)} className="text-xs text-text-muted">
102
+ Cancel
103
+ </button>
104
+ </div>
105
+ </form>
106
+ );
107
+
108
+ return (
109
+ <nav aria-label="My calendars" className="p-3 border-t border-border flex flex-col gap-3">
110
+ {error && <Alert>{error}</Alert>}
111
+ {mailboxCalendars.map(({ mailbox, calendarFolders, error: mailboxError }) => {
112
+ const title = multiple ? `${mailbox.displayName}${mailbox.ownerUserUid ? "" : " (shared)"}` : "My calendars";
113
+ return (
114
+ <div key={mailbox.uid}>
115
+ <div className="flex items-center justify-between mb-1 gap-2">
116
+ <h2 className="flex items-center gap-1.5 text-xs font-bold uppercase tracking-wide text-text-muted min-w-0">
117
+ {multiple && (
118
+ <span
119
+ aria-hidden="true"
120
+ className="w-2 h-2 rounded-full shrink-0"
121
+ style={{ backgroundColor: accentColorForMailbox(mailbox.uid) }}
122
+ />
123
+ )}
124
+ <span className="truncate">{title}</span>
125
+ </h2>
96
126
  <button
97
- key={swatch}
98
127
  type="button"
99
- aria-label={`Color ${swatch}`}
100
- aria-pressed={color === swatch}
101
- onClick={() => setColor(swatch)}
102
- className={["w-4 h-4 rounded-full shrink-0", color === swatch ? "ring-2 ring-offset-1 ring-text" : ""].join(" ")}
103
- style={{ backgroundColor: swatch }}
104
- />
105
- ))}
106
- </div>
107
- <div className="flex items-center gap-2">
108
- <button type="submit" disabled={saving} className="text-xs font-semibold text-primary-dark">
109
- Add
110
- </button>
111
- <button type="button" onClick={() => setAdding(false)} className="text-xs text-text-muted">
112
- Cancel
113
- </button>
128
+ onClick={() => startAdding(mailbox.uid)}
129
+ aria-label={multiple ? `Add calendar to ${mailbox.displayName}` : "Add calendar"}
130
+ className="text-xs font-bold text-primary-dark hover:underline shrink-0"
131
+ >
132
+ +
133
+ </button>
134
+ </div>
135
+ {mailboxError && (
136
+ <div className="mb-1">
137
+ <Alert>{mailboxError}</Alert>
138
+ </div>
139
+ )}
140
+ <div className="flex flex-col gap-0.5">
141
+ {calendarFolders.map((folder) => (
142
+ <label
143
+ key={folder.uid}
144
+ className="flex items-center gap-2 text-sm px-1 py-1 rounded-sm hover:bg-surface-alt cursor-pointer"
145
+ >
146
+ <input
147
+ type="checkbox"
148
+ checked={checkedFolderUids.has(folder.uid)}
149
+ onChange={() => onToggle(folder.uid)}
150
+ style={{ accentColor: colorFor(folder) }}
151
+ />
152
+ <span aria-hidden="true" className="w-2.5 h-2.5 rounded-full shrink-0" style={{ backgroundColor: colorFor(folder) }} />
153
+ <span className="truncate">{folder.name}</span>
154
+ </label>
155
+ ))}
156
+ </div>
157
+ {addingFor === mailbox.uid && addForm}
114
158
  </div>
115
- </form>
116
- )}
159
+ );
160
+ })}
117
161
  </nav>
118
162
  );
119
163
  }