@rapidmx/web-client 0.14.1 → 0.16.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 (314) hide show
  1. package/README.md +373 -373
  2. package/apps/admin/branding/index.tsx +39 -39
  3. package/apps/admin/data-requests/index.tsx +482 -480
  4. package/apps/admin/domains/[uid].tsx +164 -163
  5. package/apps/admin/escrow-scopes/[uid].tsx +348 -348
  6. package/apps/admin/index.tsx +127 -124
  7. package/apps/admin/mailboxes/[uid].tsx +269 -224
  8. package/apps/admin/mailboxes/new/index.tsx +28 -28
  9. package/apps/admin/plugins/index.tsx +15 -15
  10. package/apps/admin/retention-policy/index.tsx +39 -39
  11. package/apps/admin/signing-certificates/index.tsx +343 -343
  12. package/apps/escrow/audit-log/index.tsx +196 -196
  13. package/apps/escrow/matters/[uid].tsx +621 -621
  14. package/apps/shared/appearance/AppearanceForm.tsx +9 -1
  15. package/apps/shared/auth/adminAccess.ts +99 -99
  16. package/apps/shared/calendar/bookingPlugin.ts +100 -0
  17. package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
  18. package/apps/shared/components/admin/layout/AdminShell.tsx +378 -346
  19. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -0
  20. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
  21. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -0
  22. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  23. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  24. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +53 -4
  25. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +64 -10
  27. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  28. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  29. package/apps/shared/components/admin/setup/SetupWizard.tsx +444 -444
  30. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  31. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
  32. package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
  33. package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
  34. package/apps/shared/components/calendar/EventDetails.tsx +401 -0
  35. package/apps/shared/components/calendar/EventEditor.tsx +635 -0
  36. package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
  37. package/apps/shared/components/calendar/EventFormParts.tsx +591 -0
  38. package/apps/shared/components/calendar/EventModal.tsx +206 -946
  39. package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
  40. package/apps/shared/components/calendar/EventShell.tsx +202 -0
  41. package/apps/shared/components/calendar/FindATime.tsx +379 -0
  42. package/apps/shared/components/calendar/GuestInput.tsx +100 -0
  43. package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
  44. package/apps/shared/components/calendar/MonthView.tsx +185 -173
  45. package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
  46. package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
  47. package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
  48. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -222
  49. package/apps/shared/components/calendar/RequestChangeForm.tsx +240 -0
  50. package/apps/shared/components/calendar/SplitDayView.tsx +144 -143
  51. package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
  52. package/apps/shared/components/calendar/TimeGridView.tsx +246 -244
  53. package/apps/shared/components/calendar/allDay.ts +124 -124
  54. package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
  55. package/apps/shared/components/calendar/eventForm.ts +108 -0
  56. package/apps/shared/components/calendar/eventFormat.ts +210 -0
  57. package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
  58. package/apps/shared/components/calendar/layout/CalendarShell.tsx +201 -200
  59. package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
  60. package/apps/shared/components/contacts/ContactForm.tsx +383 -383
  61. package/apps/shared/components/contacts/ContactsSidebar.tsx +1 -1
  62. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -98
  63. package/apps/shared/components/contacts/layout/ContactsShell.tsx +217 -216
  64. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  65. package/apps/shared/components/layout/AppShell.tsx +480 -480
  66. package/apps/shared/components/layout/FloatingActionButton.tsx +31 -0
  67. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  68. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
  69. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  70. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  71. package/apps/shared/components/layout/UserMenu.tsx +439 -372
  72. package/apps/shared/components/mail/ConversationList.tsx +323 -271
  73. package/apps/shared/components/mail/ConversationThreadPane.tsx +130 -5
  74. package/apps/shared/components/mail/InviteCard.tsx +387 -0
  75. package/apps/shared/components/mail/MailSelectionBar.tsx +236 -227
  76. package/apps/shared/components/mail/MessageDetailPane.tsx +1559 -1537
  77. package/apps/shared/components/mail/SwipeRow.tsx +120 -0
  78. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
  79. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  80. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1681 -1673
  81. package/apps/shared/components/mail/compose/RecipientInput.tsx +54 -11
  82. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  83. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  84. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  85. package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
  86. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
  87. package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
  88. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
  89. package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
  90. package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
  91. package/apps/shared/components/mail/layout/FolderBadgeChip.tsx +21 -0
  92. package/apps/shared/components/mail/layout/MailShell.tsx +546 -490
  93. package/apps/shared/components/mail/layout/SidebarSection.tsx +66 -0
  94. package/apps/shared/components/mail/reading/PendingMessageCard.tsx +68 -0
  95. package/apps/shared/components/mail/verificationSeals.ts +125 -125
  96. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  97. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +255 -253
  99. package/apps/shared/components/tasks/TasksSidebar.tsx +1 -1
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +218 -217
  101. package/apps/shared/gestures/useEnterSlide.ts +54 -0
  102. package/apps/shared/gestures/useSwipe.ts +114 -0
  103. package/apps/shared/gestures/useSwipeSlide.ts +113 -0
  104. package/apps/shared/gestures/useWheelPaging.ts +107 -0
  105. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  106. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  107. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  108. package/apps/shared/keyboard/dispatch.ts +124 -124
  109. package/apps/shared/keyboard/format.ts +89 -89
  110. package/apps/shared/keyboard/keymap.ts +114 -114
  111. package/apps/shared/keyboard/registry.ts +65 -65
  112. package/apps/shared/keyboard/targets.ts +79 -79
  113. package/apps/shared/mail/folderCounts.ts +17 -0
  114. package/apps/shared/mail/folderTree.ts +143 -143
  115. package/apps/shared/mail/listAllPages.ts +39 -39
  116. package/apps/shared/mail/newMailNotifications.ts +3 -15
  117. package/apps/shared/mail/outbox/outgoingReplies.ts +192 -0
  118. package/apps/shared/mail/outbox/sendJob.ts +19 -1
  119. package/apps/shared/mail/outbox/sendOutcomes.ts +13 -4
  120. package/apps/shared/mail/primaryMailbox.ts +71 -0
  121. package/apps/shared/mail/useCollapsedSections.ts +146 -0
  122. package/apps/shared/mail/useMailConnection.ts +205 -203
  123. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  124. package/apps/shared/mail/useMarkMessageRead.ts +47 -47
  125. package/apps/shared/mail/useNewMailNotifications.ts +2 -2
  126. package/apps/shared/navigation/AppRouter.tsx +300 -300
  127. package/apps/shared/navigation/routerContext.tsx +83 -83
  128. package/apps/shared/notifications/preferences.ts +36 -0
  129. package/apps/shared/notifications/store.ts +9 -0
  130. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
  131. package/apps/shared/search/crossMailboxSearch.ts +169 -0
  132. package/apps/shared/search/localIndexBuilder.ts +481 -481
  133. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  134. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  135. package/apps/shared/signing/enrollmentView.ts +251 -251
  136. package/apps/shared/signing/useNow.ts +19 -19
  137. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
  138. package/apps/shared/styles/app.css +396 -386
  139. package/apps/www/_routes.ts +1 -0
  140. package/apps/www/calendar/index.tsx +578 -469
  141. package/apps/www/contacts/[uid].tsx +109 -109
  142. package/apps/www/contacts/index.tsx +6 -0
  143. package/apps/www/index.tsx +2847 -2332
  144. package/apps/www/messages/[uid].tsx +135 -108
  145. package/apps/www/settings/auto-reply/index.tsx +133 -133
  146. package/apps/www/settings/encryption/index.tsx +1287 -1287
  147. package/apps/www/settings/filters/[uid].tsx +176 -176
  148. package/apps/www/settings/filters/index.tsx +102 -102
  149. package/apps/www/settings/filters/new/index.tsx +140 -140
  150. package/apps/www/settings/labels/index.tsx +204 -204
  151. package/apps/www/settings/privacy/index.tsx +492 -492
  152. package/apps/www/settings/profile/index.tsx +248 -0
  153. package/apps/www/settings/read-receipts/index.tsx +147 -147
  154. package/apps/www/settings/sharing/index.tsx +256 -256
  155. package/apps/www/settings/signatures/[uid].tsx +172 -172
  156. package/apps/www/settings/signatures/index.tsx +88 -88
  157. package/apps/www/settings/signatures/new/index.tsx +135 -135
  158. package/apps/www/tasks/index.tsx +649 -649
  159. package/dist/apps/admin/data-requests/index.js +1 -1
  160. package/dist/apps/admin/domains/[uid].js +4 -2
  161. package/dist/apps/admin/index.js +2 -1
  162. package/dist/apps/admin/mailboxes/[uid].js +12 -2
  163. package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
  164. package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
  165. package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
  166. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
  167. package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
  168. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  169. package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
  170. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.d.ts +35 -0
  171. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +115 -0
  172. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.d.ts +16 -0
  173. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +92 -0
  174. package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +28 -4
  176. package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
  177. package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
  178. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
  179. package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
  180. package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
  181. package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
  182. package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
  183. package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
  184. package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
  185. package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
  186. package/dist/apps/shared/components/calendar/EventEditor.js +544 -0
  187. package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
  188. package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
  189. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +70 -0
  190. package/dist/apps/shared/components/calendar/EventFormParts.js +191 -0
  191. package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
  192. package/dist/apps/shared/components/calendar/EventModal.js +57 -459
  193. package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
  194. package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
  195. package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
  196. package/dist/apps/shared/components/calendar/EventShell.js +134 -0
  197. package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
  198. package/dist/apps/shared/components/calendar/FindATime.js +190 -0
  199. package/dist/apps/shared/components/calendar/GuestInput.d.ts +45 -0
  200. package/dist/apps/shared/components/calendar/GuestInput.js +30 -0
  201. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
  202. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
  203. package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
  204. package/dist/apps/shared/components/calendar/MonthView.js +11 -5
  205. package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
  206. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
  207. package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
  208. package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
  209. package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
  210. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
  211. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  212. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
  213. package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
  214. package/dist/apps/shared/components/calendar/RequestChangeForm.js +127 -0
  215. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
  216. package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
  217. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
  218. package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
  219. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
  220. package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
  221. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
  222. package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
  223. package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
  224. package/dist/apps/shared/components/calendar/eventForm.js +1 -0
  225. package/dist/apps/shared/components/calendar/eventFormat.d.ts +80 -0
  226. package/dist/apps/shared/components/calendar/eventFormat.js +190 -0
  227. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
  228. package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
  229. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -2
  230. package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
  231. package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
  232. package/dist/apps/shared/components/contacts/ContactsSidebar.js +1 -1
  233. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
  234. package/dist/apps/shared/components/contacts/ContactsToolbar.js +4 -2
  235. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -3
  236. package/dist/apps/shared/components/layout/FloatingActionButton.d.ts +13 -0
  237. package/dist/apps/shared/components/layout/FloatingActionButton.js +9 -0
  238. package/dist/apps/shared/components/layout/UserMenu.d.ts +14 -9
  239. package/dist/apps/shared/components/layout/UserMenu.js +74 -26
  240. package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
  241. package/dist/apps/shared/components/mail/ConversationList.js +21 -4
  242. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
  243. package/dist/apps/shared/components/mail/ConversationThreadPane.js +127 -22
  244. package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
  245. package/dist/apps/shared/components/mail/InviteCard.js +150 -0
  246. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -1
  247. package/dist/apps/shared/components/mail/MailSelectionBar.js +3 -3
  248. package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
  249. package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
  250. package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
  251. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +16 -7
  252. package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +21 -1
  253. package/dist/apps/shared/components/mail/compose/RecipientInput.js +18 -10
  254. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
  255. package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
  256. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
  257. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
  258. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
  259. package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
  260. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
  261. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
  262. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
  263. package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
  264. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
  265. package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
  266. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.d.ts +6 -0
  267. package/dist/apps/shared/components/mail/layout/FolderBadgeChip.js +9 -0
  268. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +10 -0
  269. package/dist/apps/shared/components/mail/layout/MailShell.js +76 -46
  270. package/dist/apps/shared/components/mail/layout/SidebarSection.d.ts +23 -0
  271. package/dist/apps/shared/components/mail/layout/SidebarSection.js +16 -0
  272. package/dist/apps/shared/components/mail/reading/PendingMessageCard.d.ts +14 -0
  273. package/dist/apps/shared/components/mail/reading/PendingMessageCard.js +16 -0
  274. package/dist/apps/shared/components/settings/layout/SettingsShell.js +7 -5
  275. package/dist/apps/shared/components/tasks/TasksSidebar.js +1 -1
  276. package/dist/apps/shared/components/tasks/layout/TasksShell.js +4 -3
  277. package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
  278. package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
  279. package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
  280. package/dist/apps/shared/gestures/useSwipe.js +90 -0
  281. package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
  282. package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
  283. package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
  284. package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
  285. package/dist/apps/shared/mail/folderCounts.d.ts +10 -0
  286. package/dist/apps/shared/mail/folderCounts.js +12 -0
  287. package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
  288. package/dist/apps/shared/mail/newMailNotifications.js +3 -12
  289. package/dist/apps/shared/mail/outbox/outgoingReplies.d.ts +80 -0
  290. package/dist/apps/shared/mail/outbox/outgoingReplies.js +131 -0
  291. package/dist/apps/shared/mail/outbox/sendJob.d.ts +5 -1
  292. package/dist/apps/shared/mail/outbox/sendJob.js +14 -1
  293. package/dist/apps/shared/mail/outbox/sendOutcomes.js +13 -4
  294. package/dist/apps/shared/mail/primaryMailbox.d.ts +29 -0
  295. package/dist/apps/shared/mail/primaryMailbox.js +61 -0
  296. package/dist/apps/shared/mail/useCollapsedSections.d.ts +32 -0
  297. package/dist/apps/shared/mail/useCollapsedSections.js +110 -0
  298. package/dist/apps/shared/mail/useMailConnection.js +3 -1
  299. package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
  300. package/dist/apps/shared/notifications/preferences.d.ts +13 -0
  301. package/dist/apps/shared/notifications/preferences.js +36 -0
  302. package/dist/apps/shared/notifications/store.d.ts +1 -0
  303. package/dist/apps/shared/notifications/store.js +7 -23
  304. package/dist/apps/shared/search/crossMailboxSearch.d.ts +72 -0
  305. package/dist/apps/shared/search/crossMailboxSearch.js +136 -0
  306. package/dist/apps/shared/styles/app.css +396 -386
  307. package/dist/apps/www/_routes.js +1 -0
  308. package/dist/apps/www/calendar/index.js +78 -7
  309. package/dist/apps/www/contacts/index.js +3 -1
  310. package/dist/apps/www/index.js +548 -210
  311. package/dist/apps/www/messages/[uid].js +13 -5
  312. package/dist/apps/www/settings/profile/index.d.ts +4 -0
  313. package/dist/apps/www/settings/profile/index.js +124 -0
  314. package/package.json +2 -2
@@ -1,173 +1,185 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React from "react";
6
- import { useDraggable, useDroppable } from "@dnd-kit/core";
7
- import { eachDayOfInterval, endOfMonth, endOfWeek, format, isSameMonth, isToday, startOfMonth, startOfWeek } from "date-fns";
8
- import { dayDropId, eventDragId } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
9
- import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
10
- import { occursOnDay, startsOnDay } from "./allDay.js";
11
-
12
- const MAX_CHIPS_PER_DAY = 3;
13
-
14
- export interface MonthViewProps {
15
- /** Any date within the month to display. */
16
- viewDate: Date;
17
- /** Already expanded for the visible grid range — see `recurrence.ts`'s `expandAllOccurrences`. */
18
- occurrences: CalendarOccurrence[];
19
- /** Each occurrence's own calendar color, keyed by `folderUid` — see `calendarColors.ts`. */
20
- folderColors: Record<string, string>;
21
- onSelectDay: (date: Date) => void;
22
- onSelectEvent: (occurrence: CalendarOccurrence) => void;
23
- }
24
-
25
- /** A real 6-week month grid (Mon-start), matching Outlook/Gmail's month view. Multi-day and all-day
26
- * events are shown on every day they cover (see `allDay.ts`'s `occursOnDay`). */
27
- export default function MonthView({ viewDate, occurrences, folderColors, onSelectDay, onSelectEvent }: MonthViewProps) {
28
- const gridStart = startOfWeek(startOfMonth(viewDate), { weekStartsOn: 1 });
29
- const gridEnd = endOfWeek(endOfMonth(viewDate), { weekStartsOn: 1 });
30
- const days = eachDayOfInterval({ start: gridStart, end: gridEnd });
31
-
32
- return (
33
- <div className="flex-1 grid grid-cols-7 grid-rows-6 min-h-0" role="grid" aria-label="Month">
34
- {days.map((day) => (
35
- <DayCell
36
- key={day.toISOString()}
37
- day={day}
38
- inCurrentMonth={isSameMonth(day, viewDate)}
39
- occurrences={occurrences.filter((occ) => occursOnDay(occ, day))}
40
- folderColors={folderColors}
41
- onSelectDay={onSelectDay}
42
- onSelectEvent={onSelectEvent}
43
- />
44
- ))}
45
- </div>
46
- );
47
- }
48
-
49
- interface DayCellProps {
50
- day: Date;
51
- inCurrentMonth: boolean;
52
- occurrences: CalendarOccurrence[];
53
- folderColors: Record<string, string>;
54
- onSelectDay: (date: Date) => void;
55
- onSelectEvent: (occurrence: CalendarOccurrence) => void;
56
- }
57
-
58
- function DayCell({ day, inCurrentMonth, occurrences, folderColors, onSelectDay, onSelectEvent }: DayCellProps) {
59
- const { setNodeRef, isOver } = useDroppable({ id: dayDropId(day) });
60
- const visible = occurrences.slice(0, MAX_CHIPS_PER_DAY);
61
- const overflowCount = occurrences.length - visible.length;
62
-
63
- return (
64
- <div
65
- ref={setNodeRef}
66
- className={[
67
- "border-b border-r border-border p-1 flex flex-col gap-0.5 min-h-0 overflow-hidden",
68
- isOver ? "bg-primary/5" : "",
69
- inCurrentMonth ? "bg-surface" : "bg-surface-alt",
70
- ].join(" ")}
71
- >
72
- <button
73
- type="button"
74
- onClick={() => onSelectDay(day)}
75
- className={[
76
- "self-start text-xs font-semibold w-6 h-6 rounded-full flex items-center justify-center shrink-0",
77
- isToday(day) ? "bg-primary text-white" : inCurrentMonth ? "text-text" : "text-text-muted",
78
- ].join(" ")}
79
- >
80
- {format(day, "d")}
81
- </button>
82
- <div className="flex-1 flex flex-col gap-0.5 min-h-0 overflow-hidden">
83
- {visible.map((occurrence) =>
84
- startsOnDay(occurrence, day) ? (
85
- <EventChip
86
- key={occurrence.occurrenceKey}
87
- occurrence={occurrence}
88
- color={folderColors[occurrence.folderUid]}
89
- onSelect={onSelectEvent}
90
- />
91
- ) : (
92
- <ContinuationChip
93
- key={occurrence.occurrenceKey}
94
- occurrence={occurrence}
95
- color={folderColors[occurrence.folderUid]}
96
- onSelect={onSelectEvent}
97
- />
98
- ),
99
- )}
100
- {overflowCount > 0 && (
101
- <button
102
- type="button"
103
- onClick={() => onSelectDay(day)}
104
- className="text-xs text-text-muted text-left hover:underline shrink-0"
105
- >
106
- +{overflowCount} more
107
- </button>
108
- )}
109
- </div>
110
- </div>
111
- );
112
- }
113
-
114
- function EventChip({
115
- occurrence,
116
- color,
117
- onSelect,
118
- }: {
119
- occurrence: CalendarOccurrence;
120
- /** This occurrence's calendar color — see `calendarColors.ts`. Only applied for a "busy" chip; a
121
- * "free" one keeps Outlook's own muted/outline treatment regardless of which calendar it's on. */
122
- color: string;
123
- onSelect: (occurrence: CalendarOccurrence) => void;
124
- }) {
125
- const { setNodeRef, listeners, attributes, transform, isDragging } = useDraggable({ id: eventDragId(occurrence) });
126
- const isFree = occurrence.busyStatus === "free";
127
-
128
- return (
129
- <button
130
- ref={setNodeRef}
131
- type="button"
132
- onClick={() => onSelect(occurrence)}
133
- style={{
134
- ...(transform ? { transform: `translate(${transform.x}px, ${transform.y}px)`, zIndex: 10 } : undefined),
135
- ...(isFree ? undefined : { backgroundColor: color, color: "#fff" }),
136
- }}
137
- className={[
138
- "text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0",
139
- isFree ? "bg-surface-alt text-text-muted" : "",
140
- isDragging ? "opacity-50" : "",
141
- ].join(" ")}
142
- {...listeners}
143
- {...attributes}
144
- >
145
- {occurrence.allDay ? "" : `${format(new Date(occurrence.startDate), "h:mma")} `}
146
- {occurrence.title}
147
- </button>
148
- );
149
- }
150
-
151
- /** A multi-day event's chip on a day after its first: clickable, but not draggable (its drag id is
152
- * already taken by the start day's chip) and without a start-time prefix. */
153
- function ContinuationChip({
154
- occurrence,
155
- color,
156
- onSelect,
157
- }: {
158
- occurrence: CalendarOccurrence;
159
- color: string;
160
- onSelect: (occurrence: CalendarOccurrence) => void;
161
- }) {
162
- const isFree = occurrence.busyStatus === "free";
163
- return (
164
- <button
165
- type="button"
166
- onClick={() => onSelect(occurrence)}
167
- style={isFree ? undefined : { backgroundColor: color, color: "#fff" }}
168
- className={["text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0", isFree ? "bg-surface-alt text-text-muted" : ""].join(" ")}
169
- >
170
- {occurrence.title}
171
- </button>
172
- );
173
- }
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React from "react";
6
+ import { useDraggable, useDroppable } from "@dnd-kit/core";
7
+ import { addDays, eachDayOfInterval, endOfMonth, endOfWeek, format, isSameMonth, isToday, startOfMonth, startOfWeek } from "date-fns";
8
+ import { dayDropId, eventDragId } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
9
+ import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
10
+ import { occursOnDay, startsOnDay } from "./allDay.js";
11
+ import { EventAnchor, anchorOf } from "./EventShell.js";
12
+
13
+ const MAX_CHIPS_PER_DAY = 3;
14
+
15
+ export interface MonthViewProps {
16
+ /** Any date within the month to display. */
17
+ viewDate: Date;
18
+ /** Already expanded for the visible grid range — see `recurrence.ts`'s `expandAllOccurrences`. */
19
+ occurrences: CalendarOccurrence[];
20
+ /** Each occurrence's own calendar color, keyed by `folderUid` — see `calendarColors.ts`. */
21
+ folderColors: Record<string, string>;
22
+ onSelectDay: (date: Date) => void;
23
+ onSelectEvent: (occurrence: CalendarOccurrence) => void;
24
+ /** Click on the empty part of a day: the day (from midnight to the next midnight) and the cell, for a new all-day event's quick-create
25
+ * popover to open beside. */
26
+ onSelectSlot?: (start: Date, end: Date, anchor: EventAnchor) => void;
27
+ }
28
+
29
+ /** A real 6-week month grid (Mon-start), matching Outlook/Gmail's month view. Multi-day and all-day
30
+ * events are shown on every day they cover (see `allDay.ts`'s `occursOnDay`). */
31
+ export default function MonthView({ viewDate, occurrences, folderColors, onSelectDay, onSelectEvent, onSelectSlot }: MonthViewProps) {
32
+ const gridStart = startOfWeek(startOfMonth(viewDate), { weekStartsOn: 1 });
33
+ const gridEnd = endOfWeek(endOfMonth(viewDate), { weekStartsOn: 1 });
34
+ const days = eachDayOfInterval({ start: gridStart, end: gridEnd });
35
+
36
+ return (
37
+ <div className="flex-1 grid grid-cols-7 grid-rows-6 min-h-0" role="grid" aria-label="Month">
38
+ {days.map((day) => (
39
+ <DayCell
40
+ key={day.toISOString()}
41
+ day={day}
42
+ inCurrentMonth={isSameMonth(day, viewDate)}
43
+ occurrences={occurrences.filter((occ) => occursOnDay(occ, day))}
44
+ folderColors={folderColors}
45
+ onSelectDay={onSelectDay}
46
+ onSelectEvent={onSelectEvent}
47
+ onSelectSlot={onSelectSlot}
48
+ />
49
+ ))}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ interface DayCellProps {
55
+ day: Date;
56
+ inCurrentMonth: boolean;
57
+ occurrences: CalendarOccurrence[];
58
+ folderColors: Record<string, string>;
59
+ onSelectDay: (date: Date) => void;
60
+ onSelectEvent: (occurrence: CalendarOccurrence) => void;
61
+ onSelectSlot?: (start: Date, end: Date, anchor: EventAnchor) => void;
62
+ }
63
+
64
+ function DayCell({ day, inCurrentMonth, occurrences, folderColors, onSelectDay, onSelectEvent, onSelectSlot }: DayCellProps) {
65
+ const { setNodeRef, isOver } = useDroppable({ id: dayDropId(day) });
66
+ const visible = occurrences.slice(0, MAX_CHIPS_PER_DAY);
67
+ const overflowCount = occurrences.length - visible.length;
68
+
69
+ return (
70
+ <div
71
+ ref={setNodeRef}
72
+ onClick={(e) => {
73
+ // Only the empty part of the cell: the day number, the chips and "+N more" have their own clicks.
74
+ if (onSelectSlot && !(e.target as Element).closest("button")) {
75
+ onSelectSlot(day, addDays(day, 1), anchorOf(e.currentTarget));
76
+ }
77
+ }}
78
+ className={[
79
+ "border-b border-r border-border p-1 flex flex-col gap-0.5 min-h-0 overflow-hidden",
80
+ isOver ? "bg-primary/5" : "",
81
+ inCurrentMonth ? "bg-surface" : "bg-surface-alt",
82
+ ].join(" ")}
83
+ >
84
+ <button
85
+ type="button"
86
+ onClick={() => onSelectDay(day)}
87
+ className={[
88
+ "self-start text-xs font-semibold w-6 h-6 rounded-full flex items-center justify-center shrink-0",
89
+ isToday(day) ? "bg-primary text-white" : inCurrentMonth ? "text-text" : "text-text-muted",
90
+ ].join(" ")}
91
+ >
92
+ {format(day, "d")}
93
+ </button>
94
+ <div className="flex-1 flex flex-col gap-0.5 min-h-0 overflow-hidden">
95
+ {visible.map((occurrence) =>
96
+ startsOnDay(occurrence, day) ? (
97
+ <EventChip
98
+ key={occurrence.occurrenceKey}
99
+ occurrence={occurrence}
100
+ color={folderColors[occurrence.folderUid]}
101
+ onSelect={onSelectEvent}
102
+ />
103
+ ) : (
104
+ <ContinuationChip
105
+ key={occurrence.occurrenceKey}
106
+ occurrence={occurrence}
107
+ color={folderColors[occurrence.folderUid]}
108
+ onSelect={onSelectEvent}
109
+ />
110
+ ),
111
+ )}
112
+ {overflowCount > 0 && (
113
+ <button
114
+ type="button"
115
+ onClick={() => onSelectDay(day)}
116
+ className="text-xs text-text-muted text-left hover:underline shrink-0"
117
+ >
118
+ +{overflowCount} more
119
+ </button>
120
+ )}
121
+ </div>
122
+ </div>
123
+ );
124
+ }
125
+
126
+ function EventChip({
127
+ occurrence,
128
+ color,
129
+ onSelect,
130
+ }: {
131
+ occurrence: CalendarOccurrence;
132
+ /** This occurrence's calendar color — see `calendarColors.ts`. Only applied for a "busy" chip; a
133
+ * "free" one keeps Outlook's own muted/outline treatment regardless of which calendar it's on. */
134
+ color: string;
135
+ onSelect: (occurrence: CalendarOccurrence) => void;
136
+ }) {
137
+ const { setNodeRef, listeners, attributes, transform, isDragging } = useDraggable({ id: eventDragId(occurrence) });
138
+ const isFree = occurrence.busyStatus === "free";
139
+
140
+ return (
141
+ <button
142
+ ref={setNodeRef}
143
+ type="button"
144
+ onClick={() => onSelect(occurrence)}
145
+ style={{
146
+ ...(transform ? { transform: `translate(${transform.x}px, ${transform.y}px)`, zIndex: 10 } : undefined),
147
+ ...(isFree ? undefined : { backgroundColor: color, color: "#fff" }),
148
+ }}
149
+ className={[
150
+ "text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0",
151
+ isFree ? "bg-surface-alt text-text-muted" : "",
152
+ isDragging ? "opacity-50" : "",
153
+ ].join(" ")}
154
+ {...listeners}
155
+ {...attributes}
156
+ >
157
+ {occurrence.allDay ? "" : `${format(new Date(occurrence.startDate), "h:mma")} `}
158
+ {occurrence.title}
159
+ </button>
160
+ );
161
+ }
162
+
163
+ /** A multi-day event's chip on a day after its first: clickable, but not draggable (its drag id is
164
+ * already taken by the start day's chip) and without a start-time prefix. */
165
+ function ContinuationChip({
166
+ occurrence,
167
+ color,
168
+ onSelect,
169
+ }: {
170
+ occurrence: CalendarOccurrence;
171
+ color: string;
172
+ onSelect: (occurrence: CalendarOccurrence) => void;
173
+ }) {
174
+ const isFree = occurrence.busyStatus === "free";
175
+ return (
176
+ <button
177
+ type="button"
178
+ onClick={() => onSelect(occurrence)}
179
+ style={isFree ? undefined : { backgroundColor: color, color: "#fff" }}
180
+ className={["text-xs text-left truncate rounded-sm px-1.5 py-0.5 shrink-0", isFree ? "bg-surface-alt text-text-muted" : ""].join(" ")}
181
+ >
182
+ {occurrence.title}
183
+ </button>
184
+ );
185
+ }
@@ -0,0 +1,87 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { MutableRefObject, useEffect, useRef, useState } from "react";
6
+ import { deviceTimeZone } from "@rapidmx/react-shared/util/timeZone.js";
7
+ import AppointmentQuickForm, { initialAppointmentDraft } from "./AppointmentQuickForm.js";
8
+ import EventExpandedForm from "./EventExpandedForm.js";
9
+ import EventQuickForm from "./EventQuickForm.js";
10
+ import { EventFormController } from "./eventForm.js";
11
+ import QuickCreateTabs, { QuickTab, quickPanelProps, quickTabId } from "./QuickCreateTabs.js";
12
+ import TaskQuickForm, { initialTaskDraft } from "./TaskQuickForm.js";
13
+
14
+ /** What `EventModal` tells the editor about the tabs of a new event's popover. */
15
+ export interface QuickCreateConfig {
16
+ tab: QuickTab;
17
+ onTabChange: (tab: QuickTab) => void;
18
+ /** Where the booking plugin's Settings pages are, or `undefined` when the plugin is not running (and there is no Appointment schedule tab). */
19
+ bookingHref?: string;
20
+ }
21
+
22
+ export interface QuickCreateFacesProps {
23
+ /** The Event tab's form, which owns the title and the mailbox and calendar the other tabs share. */
24
+ c: EventFormController;
25
+ layout: "quick" | "expanded";
26
+ onExpand: () => void;
27
+ config: QuickCreateConfig;
28
+ dirtyRef: MutableRefObject<boolean>;
29
+ /** The signed-in mailbox's address: the host's name on booking pages when its display name is not known. */
30
+ organizerAddress: string;
31
+ }
32
+
33
+ /**
34
+ * The faces of a new event, one for each tab: Event (the quick popover or, after More options, the full card - unchanged), Task and
35
+ * Appointment schedule. Stays mounted while the tab changes, so it is what keeps the other tabs' fields (the Event tab's live in
36
+ * `EventEditor`) when the user looks at another one and comes back. The title, the mailbox and the calendar are the Event tab's, so they
37
+ * are shared by all three.
38
+ *
39
+ * The tab strip is redrawn by whichever face is showing, so a tab change moves keyboard focus back to the selected tab here - what an
40
+ * arrow-key walk along the strip needs.
41
+ */
42
+ export default function QuickCreateFaces({ c, layout, onExpand, config, dirtyRef, organizerAddress }: QuickCreateFacesProps) {
43
+ const { tab, onTabChange, bookingHref } = config;
44
+ const [task, setTask] = useState(() => initialTaskDraft(c.values.start, c.values.allDay));
45
+ const [appointment, setAppointment] = useState(initialAppointmentDraft);
46
+
47
+ const shownTab = useRef(tab);
48
+ useEffect(() => {
49
+ if (shownTab.current !== tab) {
50
+ shownTab.current = tab;
51
+ document.getElementById(quickTabId(tab))?.focus();
52
+ }
53
+ }, [tab]);
54
+
55
+ // Notes typed on the Task tab are worth a second thought before a stray click closes the popover, like a title.
56
+ dirtyRef.current = dirtyRef.current || task.body.trim() !== "";
57
+
58
+ const tabs = <QuickCreateTabs tab={tab} onChange={onTabChange} showAppointment={!!bookingHref} />;
59
+
60
+ if (tab === "task") {
61
+ return (
62
+ <TaskQuickForm
63
+ c={c}
64
+ draft={task}
65
+ onDraftChange={(patch) => setTask((prev) => ({ ...prev, ...patch }))}
66
+ tabs={tabs}
67
+ expanded={layout === "expanded"}
68
+ onExpand={onExpand}
69
+ />
70
+ );
71
+ }
72
+ if (tab === "appointment") {
73
+ const hostMailbox = c.mailboxOptions?.find((option) => option.mailbox.uid === c.values.targetMailboxUid)?.mailbox;
74
+ return (
75
+ <AppointmentQuickForm
76
+ c={c}
77
+ draft={appointment}
78
+ onDraftChange={(patch) => setAppointment((prev) => ({ ...prev, ...patch }))}
79
+ tabs={tabs}
80
+ settingsHref={bookingHref!}
81
+ hostName={hostMailbox?.displayName ?? organizerAddress}
82
+ timeZone={hostMailbox?.timezone ?? deviceTimeZone()}
83
+ />
84
+ );
85
+ }
86
+ return layout === "quick" ? <EventQuickForm c={c} onExpand={onExpand} tabs={tabs} tabPanelProps={quickPanelProps("event")} /> : <EventExpandedForm c={c} />;
87
+ }
@@ -0,0 +1,81 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { KeyboardEvent } from "react";
6
+
7
+ /** What the New event popover can create. */
8
+ export type QuickTab = "event" | "task" | "appointment";
9
+
10
+ const TAB_LABELS: Record<QuickTab, string> = { event: "Event", task: "Task", appointment: "Appointment schedule" };
11
+
12
+ /** The DOM ids that tie each tab to its panel (`aria-controls` / `aria-labelledby`). */
13
+ export function quickTabId(tab: QuickTab): string {
14
+ return `quick-create-tab-${tab}`;
15
+ }
16
+ export function quickPanelId(tab: QuickTab): string {
17
+ return `quick-create-panel-${tab}`;
18
+ }
19
+
20
+ /** The attributes of the element holding the selected tab's content. */
21
+ export function quickPanelProps(tab: QuickTab) {
22
+ return { role: "tabpanel" as const, id: quickPanelId(tab), "aria-labelledby": quickTabId(tab) };
23
+ }
24
+
25
+ export interface QuickCreateTabsProps {
26
+ tab: QuickTab;
27
+ onChange: (tab: QuickTab) => void;
28
+ /** Whether the booking plugin is running: without it there is no Appointment schedule tab. */
29
+ showAppointment: boolean;
30
+ }
31
+
32
+ /**
33
+ * The segmented control across the top of the New event popover: Event, Task and (when the booking plugin is running) Appointment schedule.
34
+ * A WAI-ARIA tab list with automatic activation: only the selected tab is in the Tab order, and the arrow keys (wrapping), Home and End
35
+ * move to and select another.
36
+ */
37
+ export default function QuickCreateTabs({ tab, onChange, showAppointment }: QuickCreateTabsProps) {
38
+ const tabs: QuickTab[] = showAppointment ? ["event", "task", "appointment"] : ["event", "task"];
39
+
40
+ function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
41
+ const index = tabs.indexOf(tab);
42
+ let next: QuickTab;
43
+ if (event.key === "ArrowRight") {
44
+ next = tabs[(index + 1) % tabs.length];
45
+ } else if (event.key === "ArrowLeft") {
46
+ next = tabs[(index + tabs.length - 1) % tabs.length];
47
+ } else if (event.key === "Home") {
48
+ next = tabs[0];
49
+ } else if (event.key === "End") {
50
+ next = tabs[tabs.length - 1];
51
+ } else {
52
+ return;
53
+ }
54
+ event.preventDefault();
55
+ onChange(next);
56
+ }
57
+
58
+ return (
59
+ <div role="tablist" aria-label="What to create" className="mx-5 mb-3 flex gap-1 rounded-lg bg-surface-alt p-1">
60
+ {tabs.map((id) => (
61
+ <button
62
+ key={id}
63
+ type="button"
64
+ role="tab"
65
+ id={quickTabId(id)}
66
+ aria-selected={id === tab}
67
+ aria-controls={quickPanelId(id)}
68
+ tabIndex={id === tab ? 0 : -1}
69
+ onClick={() => onChange(id)}
70
+ onKeyDown={handleKeyDown}
71
+ className={[
72
+ "flex-1 min-w-0 rounded-md px-2 py-1.5 text-sm font-medium truncate focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
73
+ id === tab ? "bg-surface text-text shadow-sm" : "text-text-muted hover:text-text",
74
+ ].join(" ")}
75
+ >
76
+ {TAB_LABELS[id]}
77
+ </button>
78
+ ))}
79
+ </div>
80
+ );
81
+ }
@@ -0,0 +1,81 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { FormEvent, ReactNode, useContext } from "react";
6
+ import { HiOutlineBars2, HiOutlineXMark } from "react-icons/hi2";
7
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
8
+ import { EventShellContext } from "./EventShell.js";
9
+ import { EventFormController } from "./eventForm.js";
10
+ import { QuickTab, quickPanelProps } from "./QuickCreateTabs.js";
11
+
12
+ export interface QuickFaceFrameProps {
13
+ c: EventFormController;
14
+ /** The `QuickCreateTabs` strip. */
15
+ tabs: ReactNode;
16
+ /** The tab this face is the panel of. */
17
+ tab: QuickTab;
18
+ error: string | null;
19
+ /** Leave the title out (the Appointment schedule tab once it has saved). */
20
+ hideTitle?: boolean;
21
+ /** Enter in a field, or the Save button. */
22
+ onSubmit: (event: FormEvent) => void;
23
+ /** What sits in the tab's panel under the title. */
24
+ children: ReactNode;
25
+ /** The buttons along the bottom (More options, Save). */
26
+ footer: ReactNode;
27
+ }
28
+
29
+ /**
30
+ * The frame the Task and Appointment schedule faces share, drawn the way `EventQuickForm` draws the Event face: a grip to drag the popover
31
+ * and a close button, the tab strip, then the panel - an error, the title (the one the Event tab holds, so switching tabs keeps it) and the
32
+ * face's own rows - and the buttons.
33
+ */
34
+ export default function QuickFaceFrame({ c, tabs, tab, error, hideTitle, onSubmit, children, footer }: QuickFaceFrameProps) {
35
+ const { dragHandleProps } = useContext(EventShellContext);
36
+ return (
37
+ <form onSubmit={onSubmit} className="flex flex-col">
38
+ <div className="flex items-center justify-between px-2 pt-2">
39
+ {dragHandleProps ? (
40
+ <div {...dragHandleProps} aria-hidden="true" className="flex-1 h-7 flex items-center cursor-move text-text-muted select-none touch-none">
41
+ <HiOutlineBars2 size={20} />
42
+ </div>
43
+ ) : (
44
+ <div className="flex-1" />
45
+ )}
46
+ <button
47
+ type="button"
48
+ aria-label="Close"
49
+ onClick={c.onCancel}
50
+ className="w-8 h-8 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text"
51
+ >
52
+ <HiOutlineXMark size={20} aria-hidden="true" />
53
+ </button>
54
+ </div>
55
+
56
+ {tabs}
57
+
58
+ <div {...quickPanelProps(tab)} className="flex flex-col gap-3 px-5 pb-3">
59
+ {error && <Alert>{error}</Alert>}
60
+
61
+ {!hideTitle && (
62
+ <div className="pl-8">
63
+ <input
64
+ type="text"
65
+ aria-label="Title"
66
+ placeholder="Add title"
67
+ data-autofocus
68
+ className="w-full text-xl bg-transparent text-text border-0 border-b-2 border-primary/50 focus:border-primary focus:outline-none pb-1 placeholder:text-text-muted"
69
+ value={c.values.title}
70
+ onChange={(e) => c.update({ title: e.target.value })}
71
+ />
72
+ </div>
73
+ )}
74
+
75
+ {children}
76
+ </div>
77
+
78
+ <div className="flex items-center justify-end gap-2 px-4 py-3">{footer}</div>
79
+ </form>
80
+ );
81
+ }