@rapidmx/web-client 0.9.0 → 0.11.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 (443) hide show
  1. package/README.md +373 -88
  2. package/apps/admin/_layout.tsx +8 -2
  3. package/apps/admin/index.tsx +7 -1
  4. package/apps/admin/ingest-queue/index.tsx +2 -1
  5. package/apps/admin/mailboxes/[uid].tsx +23 -10
  6. package/apps/admin/quarantine/index.tsx +2 -1
  7. package/apps/admin/signing-certificates/index.tsx +343 -0
  8. package/apps/escrow/_layout.tsx +8 -2
  9. package/apps/shared/appearance/AppearanceForm.tsx +392 -0
  10. package/apps/shared/appearance/AppearanceHead.tsx +49 -0
  11. package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
  12. package/apps/shared/appearance/ColorField.tsx +107 -0
  13. package/apps/shared/appearance/appearanceCache.ts +106 -0
  14. package/apps/shared/appearance/appearanceContext.ts +77 -0
  15. package/apps/shared/appearance/bootScript.ts +35 -0
  16. package/apps/shared/appearance/color.ts +169 -0
  17. package/apps/shared/appearance/photo.ts +88 -0
  18. package/apps/shared/appearance/resolvedTheme.ts +67 -0
  19. package/apps/shared/appearance/theme.ts +355 -0
  20. package/apps/shared/auth/accountUrl.ts +9 -0
  21. package/apps/shared/auth/adminAccess.ts +99 -0
  22. package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
  23. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
  24. package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
  25. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +2 -1
  26. package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
  27. package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
  28. package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
  29. package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
  30. package/apps/shared/components/contacts/ContactForm.tsx +11 -5
  31. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
  32. package/apps/shared/components/contacts/ContactsToolbar.tsx +98 -115
  33. package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
  34. package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
  35. package/apps/shared/components/layout/AppShell.tsx +477 -288
  36. package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
  37. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
  38. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
  39. package/apps/shared/components/layout/RailIcon.tsx +100 -0
  40. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
  41. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
  42. package/apps/shared/components/layout/UserMenu.tsx +372 -185
  43. package/apps/shared/components/mail/ConversationList.tsx +271 -262
  44. package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
  45. package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
  46. package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
  47. package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
  48. package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
  49. package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
  50. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -160
  51. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
  52. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
  53. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
  54. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
  55. package/apps/shared/components/mail/compose/composePerf.ts +46 -0
  56. package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
  57. package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
  58. package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
  59. package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
  60. package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
  61. package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
  62. package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
  63. package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
  64. package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
  65. package/apps/shared/components/mail/reading/color.ts +245 -0
  66. package/apps/shared/components/mail/reading/frameControl.ts +142 -0
  67. package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
  68. package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
  69. package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
  70. package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
  71. package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
  72. package/apps/shared/components/mail/unreadStyle.tsx +75 -0
  73. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
  74. package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
  75. package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
  76. package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
  77. package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
  78. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
  79. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
  80. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
  81. package/apps/shared/keyboard/dispatch.ts +124 -0
  82. package/apps/shared/keyboard/format.ts +89 -0
  83. package/apps/shared/keyboard/keymap.ts +114 -0
  84. package/apps/shared/keyboard/match.ts +44 -0
  85. package/apps/shared/keyboard/parse.ts +136 -0
  86. package/apps/shared/keyboard/platform.ts +34 -0
  87. package/apps/shared/keyboard/registry.ts +65 -0
  88. package/apps/shared/keyboard/targets.ts +79 -0
  89. package/apps/shared/keyboard/useShortcut.ts +50 -0
  90. package/apps/shared/keyboard/useShortcutProps.ts +17 -0
  91. package/apps/shared/mail/folderCounts.ts +308 -0
  92. package/apps/shared/mail/folderTree.ts +143 -0
  93. package/apps/shared/mail/listSnapshots.ts +87 -0
  94. package/apps/shared/mail/messageReadState.ts +85 -0
  95. package/apps/shared/mail/newMailNotifications.ts +183 -0
  96. package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
  97. package/apps/shared/mail/outbox/composeBridge.ts +69 -0
  98. package/apps/shared/mail/outbox/outboxState.ts +88 -0
  99. package/apps/shared/mail/outbox/pendingSends.ts +146 -0
  100. package/apps/shared/mail/outbox/sendDecision.ts +157 -0
  101. package/apps/shared/mail/outbox/sendJob.ts +427 -0
  102. package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
  103. package/apps/shared/mail/outbox/sendState.ts +35 -0
  104. package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
  105. package/apps/shared/mail/useMailConnection.ts +203 -0
  106. package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
  107. package/apps/shared/mail/useMarkMessageRead.ts +47 -0
  108. package/apps/shared/mail/useNewMailNotifications.ts +178 -0
  109. package/apps/shared/mail/useUnreadTitle.ts +42 -0
  110. package/apps/shared/navigation/AppRouter.tsx +300 -0
  111. package/apps/shared/navigation/appHrefs.ts +23 -0
  112. package/apps/shared/navigation/frameContext.tsx +35 -0
  113. package/apps/shared/navigation/idle.ts +45 -0
  114. package/apps/shared/navigation/routerContext.tsx +83 -0
  115. package/apps/shared/navigation/routes.ts +72 -0
  116. package/apps/shared/notifications/NotificationCenter.tsx +215 -0
  117. package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
  118. package/apps/shared/notifications/apiErrors.ts +98 -0
  119. package/apps/shared/notifications/headerOffset.ts +35 -0
  120. package/apps/shared/notifications/pushStatus.ts +56 -0
  121. package/apps/shared/notifications/store.ts +549 -0
  122. package/apps/shared/notifications/systemErrors.ts +63 -0
  123. package/apps/shared/notifications/useNotifications.ts +41 -0
  124. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
  125. package/apps/shared/signing/enrollmentStorage.ts +33 -0
  126. package/apps/shared/signing/enrollmentTracker.ts +385 -0
  127. package/apps/shared/signing/enrollmentView.ts +251 -0
  128. package/apps/shared/signing/signingInfo.ts +58 -0
  129. package/apps/shared/signing/useNow.ts +19 -0
  130. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
  131. package/apps/shared/styles/app.css +108 -10
  132. package/apps/www/_layout.tsx +8 -2
  133. package/apps/www/_routedPage.tsx +24 -0
  134. package/apps/www/_routes.ts +36 -0
  135. package/apps/www/calendar/index.tsx +64 -33
  136. package/apps/www/contacts/[uid].tsx +109 -107
  137. package/apps/www/contacts/index.tsx +62 -42
  138. package/apps/www/index.tsx +2291 -1917
  139. package/apps/www/messages/[uid].tsx +108 -101
  140. package/apps/www/settings/appearance/index.tsx +21 -0
  141. package/apps/www/settings/auto-reply/index.tsx +7 -8
  142. package/apps/www/settings/encryption/index.tsx +1287 -1249
  143. package/apps/www/settings/filters/[uid].tsx +7 -2
  144. package/apps/www/settings/filters/index.tsx +102 -99
  145. package/apps/www/settings/filters/new/index.tsx +140 -133
  146. package/apps/www/settings/labels/index.tsx +204 -201
  147. package/apps/www/settings/privacy/index.tsx +7 -9
  148. package/apps/www/settings/read-receipts/index.tsx +7 -8
  149. package/apps/www/settings/sharing/index.tsx +256 -274
  150. package/apps/www/settings/signatures/[uid].tsx +172 -167
  151. package/apps/www/settings/signatures/index.tsx +88 -85
  152. package/apps/www/settings/signatures/new/index.tsx +135 -129
  153. package/apps/www/tasks/index.tsx +29 -18
  154. package/dist/apps/admin/_layout.d.ts +5 -1
  155. package/dist/apps/admin/_layout.js +3 -2
  156. package/dist/apps/admin/index.js +3 -2
  157. package/dist/apps/admin/ingest-queue/index.js +2 -1
  158. package/dist/apps/admin/mailboxes/[uid].js +9 -6
  159. package/dist/apps/admin/quarantine/index.js +2 -1
  160. package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
  161. package/dist/apps/admin/signing-certificates/index.js +161 -0
  162. package/dist/apps/escrow/_layout.d.ts +5 -1
  163. package/dist/apps/escrow/_layout.js +3 -2
  164. package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
  165. package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
  166. package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
  167. package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
  168. package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
  169. package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
  170. package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
  171. package/dist/apps/shared/appearance/ColorField.js +40 -0
  172. package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
  173. package/dist/apps/shared/appearance/appearanceCache.js +80 -0
  174. package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
  175. package/dist/apps/shared/appearance/appearanceContext.js +29 -0
  176. package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
  177. package/dist/apps/shared/appearance/bootScript.js +33 -0
  178. package/dist/apps/shared/appearance/color.d.ts +65 -0
  179. package/dist/apps/shared/appearance/color.js +132 -0
  180. package/dist/apps/shared/appearance/photo.d.ts +17 -0
  181. package/dist/apps/shared/appearance/photo.js +79 -0
  182. package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
  183. package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
  184. package/dist/apps/shared/appearance/theme.d.ts +111 -0
  185. package/dist/apps/shared/appearance/theme.js +251 -0
  186. package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
  187. package/dist/apps/shared/auth/accountUrl.js +8 -0
  188. package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
  189. package/dist/apps/shared/auth/adminAccess.js +89 -0
  190. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  191. package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
  192. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
  193. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
  194. package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
  195. package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
  196. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +1 -1
  197. package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
  198. package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
  199. package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
  201. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
  202. package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
  203. package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
  204. package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +6 -2
  205. package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
  206. package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
  207. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
  208. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
  209. package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
  210. package/dist/apps/shared/components/layout/AppShell.js +122 -17
  211. package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
  212. package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
  213. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
  214. package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
  215. package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
  216. package/dist/apps/shared/components/layout/RailIcon.js +85 -0
  217. package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
  218. package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
  219. package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
  220. package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
  221. package/dist/apps/shared/components/layout/UserMenu.d.ts +25 -3
  222. package/dist/apps/shared/components/layout/UserMenu.js +88 -7
  223. package/dist/apps/shared/components/mail/ConversationList.js +5 -11
  224. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
  225. package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
  226. package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
  227. package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
  228. package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
  229. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
  230. package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
  231. package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
  232. package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
  233. package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
  234. package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
  235. package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
  236. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +38 -0
  237. package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
  238. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
  239. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
  240. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
  241. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
  242. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
  243. package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
  244. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
  245. package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
  246. package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
  247. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
  248. package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
  249. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
  250. package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
  251. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
  252. package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
  253. package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
  254. package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
  255. package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
  256. package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
  257. package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
  258. package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
  259. package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
  260. package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
  261. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
  262. package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
  263. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
  264. package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
  265. package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
  266. package/dist/apps/shared/components/mail/reading/color.js +201 -0
  267. package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
  268. package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
  269. package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
  270. package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
  271. package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
  272. package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
  273. package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
  274. package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
  275. package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
  276. package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
  277. package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
  278. package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
  279. package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
  280. package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
  281. package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
  282. package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
  283. package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
  284. package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
  285. package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
  286. package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
  287. package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
  288. package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
  289. package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
  290. package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
  291. package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
  292. package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
  293. package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
  294. package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
  295. package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
  296. package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
  297. package/dist/apps/shared/keyboard/dispatch.js +109 -0
  298. package/dist/apps/shared/keyboard/format.d.ts +12 -0
  299. package/dist/apps/shared/keyboard/format.js +74 -0
  300. package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
  301. package/dist/apps/shared/keyboard/keymap.js +84 -0
  302. package/dist/apps/shared/keyboard/match.d.ts +20 -0
  303. package/dist/apps/shared/keyboard/match.js +31 -0
  304. package/dist/apps/shared/keyboard/parse.d.ts +31 -0
  305. package/dist/apps/shared/keyboard/parse.js +107 -0
  306. package/dist/apps/shared/keyboard/platform.d.ts +15 -0
  307. package/dist/apps/shared/keyboard/platform.js +22 -0
  308. package/dist/apps/shared/keyboard/registry.d.ts +39 -0
  309. package/dist/apps/shared/keyboard/registry.js +33 -0
  310. package/dist/apps/shared/keyboard/targets.d.ts +14 -0
  311. package/dist/apps/shared/keyboard/targets.js +67 -0
  312. package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
  313. package/dist/apps/shared/keyboard/useShortcut.js +35 -0
  314. package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
  315. package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
  316. package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
  317. package/dist/apps/shared/mail/folderCounts.js +218 -0
  318. package/dist/apps/shared/mail/folderTree.d.ts +55 -0
  319. package/dist/apps/shared/mail/folderTree.js +116 -0
  320. package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
  321. package/dist/apps/shared/mail/listSnapshots.js +43 -0
  322. package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
  323. package/dist/apps/shared/mail/messageReadState.js +63 -0
  324. package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
  325. package/dist/apps/shared/mail/newMailNotifications.js +138 -0
  326. package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
  327. package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
  328. package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
  329. package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
  330. package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
  331. package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
  332. package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
  333. package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
  334. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
  335. package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
  336. package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
  337. package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
  338. package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
  339. package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
  340. package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
  341. package/dist/apps/shared/mail/outbox/sendState.js +8 -0
  342. package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
  343. package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
  344. package/dist/apps/shared/mail/useMailConnection.d.ts +56 -0
  345. package/dist/apps/shared/mail/useMailConnection.js +138 -0
  346. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
  347. package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
  348. package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
  349. package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
  350. package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
  351. package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
  352. package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
  353. package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
  354. package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
  355. package/dist/apps/shared/navigation/AppRouter.js +242 -0
  356. package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
  357. package/dist/apps/shared/navigation/appHrefs.js +20 -0
  358. package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
  359. package/dist/apps/shared/navigation/frameContext.js +24 -0
  360. package/dist/apps/shared/navigation/idle.d.ts +14 -0
  361. package/dist/apps/shared/navigation/idle.js +43 -0
  362. package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
  363. package/dist/apps/shared/navigation/routerContext.js +56 -0
  364. package/dist/apps/shared/navigation/routes.d.ts +32 -0
  365. package/dist/apps/shared/navigation/routes.js +37 -0
  366. package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
  367. package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
  368. package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
  369. package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
  370. package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
  371. package/dist/apps/shared/notifications/apiErrors.js +86 -0
  372. package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
  373. package/dist/apps/shared/notifications/headerOffset.js +33 -0
  374. package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
  375. package/dist/apps/shared/notifications/pushStatus.js +54 -0
  376. package/dist/apps/shared/notifications/store.d.ts +136 -0
  377. package/dist/apps/shared/notifications/store.js +416 -0
  378. package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
  379. package/dist/apps/shared/notifications/systemErrors.js +58 -0
  380. package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
  381. package/dist/apps/shared/notifications/useNotifications.js +18 -0
  382. package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
  383. package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
  384. package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
  385. package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
  386. package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
  387. package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
  388. package/dist/apps/shared/signing/enrollmentView.js +214 -0
  389. package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
  390. package/dist/apps/shared/signing/signingInfo.js +48 -0
  391. package/dist/apps/shared/signing/useNow.d.ts +2 -0
  392. package/dist/apps/shared/signing/useNow.js +18 -0
  393. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
  394. package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
  395. package/dist/apps/shared/styles/app.css +108 -10
  396. package/dist/apps/www/_layout.d.ts +5 -1
  397. package/dist/apps/www/_layout.js +3 -2
  398. package/dist/apps/www/_routedPage.d.ts +12 -0
  399. package/dist/apps/www/_routedPage.js +19 -0
  400. package/dist/apps/www/_routes.d.ts +11 -0
  401. package/dist/apps/www/_routes.js +30 -0
  402. package/dist/apps/www/calendar/index.d.ts +2 -2
  403. package/dist/apps/www/calendar/index.js +41 -11
  404. package/dist/apps/www/contacts/[uid].d.ts +3 -9
  405. package/dist/apps/www/contacts/[uid].js +11 -9
  406. package/dist/apps/www/contacts/index.d.ts +2 -2
  407. package/dist/apps/www/contacts/index.js +30 -24
  408. package/dist/apps/www/index.d.ts +2 -2
  409. package/dist/apps/www/index.js +332 -48
  410. package/dist/apps/www/messages/[uid].d.ts +3 -7
  411. package/dist/apps/www/messages/[uid].js +10 -5
  412. package/dist/apps/www/settings/appearance/index.d.ts +4 -0
  413. package/dist/apps/www/settings/appearance/index.js +13 -0
  414. package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
  415. package/dist/apps/www/settings/auto-reply/index.js +6 -7
  416. package/dist/apps/www/settings/encryption/index.d.ts +2 -2
  417. package/dist/apps/www/settings/encryption/index.js +106 -96
  418. package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
  419. package/dist/apps/www/settings/filters/[uid].js +6 -2
  420. package/dist/apps/www/settings/filters/index.d.ts +2 -2
  421. package/dist/apps/www/settings/filters/index.js +3 -1
  422. package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
  423. package/dist/apps/www/settings/filters/new/index.js +9 -3
  424. package/dist/apps/www/settings/labels/index.d.ts +2 -2
  425. package/dist/apps/www/settings/labels/index.js +3 -1
  426. package/dist/apps/www/settings/privacy/index.d.ts +2 -2
  427. package/dist/apps/www/settings/privacy/index.js +8 -9
  428. package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
  429. package/dist/apps/www/settings/read-receipts/index.js +6 -7
  430. package/dist/apps/www/settings/sharing/index.d.ts +2 -2
  431. package/dist/apps/www/settings/sharing/index.js +22 -34
  432. package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
  433. package/dist/apps/www/settings/signatures/[uid].js +6 -2
  434. package/dist/apps/www/settings/signatures/index.d.ts +2 -2
  435. package/dist/apps/www/settings/signatures/index.js +3 -1
  436. package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
  437. package/dist/apps/www/settings/signatures/new/index.js +9 -4
  438. package/dist/apps/www/tasks/index.d.ts +2 -2
  439. package/dist/apps/www/tasks/index.js +22 -16
  440. package/package.json +2 -2
  441. package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
  442. package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
  443. package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
@@ -0,0 +1,392 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React, { DragEvent, useEffect, useRef, useState } from "react";
6
+ import {
7
+ APPEARANCE_MODES,
8
+ AppearanceMode,
9
+ BACKGROUND_BLUR_MAX,
10
+ BACKGROUND_DIM_MAX,
11
+ BACKGROUND_FITS,
12
+ BACKGROUND_IMAGE_TYPES,
13
+ BACKGROUND_MAX_BYTES,
14
+ BackgroundFit,
15
+ BackgroundKind,
16
+ ColorKey,
17
+ DEFAULT_BACKGROUND,
18
+ validateBackgroundFile,
19
+ } from "@rapidmx/react-shared/appearance/preferencesApi.js";
20
+ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
21
+ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
22
+ import ColorField from "./ColorField.js";
23
+ import { normalizeHex } from "./color.js";
24
+ import { useAppearance } from "./appearanceContext.js";
25
+ import { DEFAULT_PALETTE, contrastWarnings } from "./theme.js";
26
+
27
+ const MODE_LABELS: Record<AppearanceMode, { label: string; hint: string }> = {
28
+ system: { label: "System", hint: "Follow this device" },
29
+ light: { label: "Light", hint: "Always light" },
30
+ dark: { label: "Dark", hint: "Always dark" },
31
+ };
32
+
33
+ const FIT_LABELS: Record<BackgroundFit, string> = {
34
+ cover: "Cover (fill the window)",
35
+ contain: "Contain (show all of it)",
36
+ tile: "Tile (repeat)",
37
+ };
38
+
39
+ const COLOR_FIELDS: { key: ColorKey; label: string; description: string; token: string }[] = [
40
+ { key: "primary", label: "Primary", description: "Links, the selected app and folder, and focus rings.", token: "--rr-color-primary" },
41
+ { key: "accent", label: "Accent", description: "Main buttons and highlights.", token: "--rr-color-accent" },
42
+ { key: "surface", label: "Surface", description: "Panels, lists and menus.", token: "--rr-color-surface" },
43
+ { key: "text", label: "Text", description: "Everything written on a surface.", token: "--rr-color-text" },
44
+ ];
45
+
46
+ /** The colour a token has right now on the page (the app's, the branding's, or the user's own), as `#rrggbb`; `fallback` where it can't be read (jsdom, or a value that isn't a colour). Only called from an effect, so there is always a document. */
47
+ function readToken(token: string, fallback: string): string {
48
+ return normalizeHex(getComputedStyle(document.documentElement).getPropertyValue(token)) ?? fallback;
49
+ }
50
+
51
+ const DEFAULT_TOKEN_COLORS: Record<ColorKey, { light: string; dark: string }> = {
52
+ primary: { light: "#0d9488", dark: "#2dd4bf" },
53
+ accent: { light: "#a3690a", dark: "#f2af0d" },
54
+ surface: { light: DEFAULT_PALETTE.light.surface, dark: DEFAULT_PALETTE.dark.surface },
55
+ text: { light: DEFAULT_PALETTE.light.text, dark: DEFAULT_PALETTE.dark.text },
56
+ };
57
+
58
+ /** The colour a new "Colour" background starts as. */
59
+ const STARTING_BACKGROUND_COLOR = { light: "#dbe4ee", dark: "#1e293b" };
60
+
61
+ function formatBytes(bytes: number): string {
62
+ return `${bytes / (1024 * 1024)} MB`;
63
+ }
64
+
65
+ /**
66
+ * Settings > Appearance: the colour scheme, the theme colours and the background. Every control changes the app itself at once - the
67
+ * page you are on is the preview - and saves in the background (`useAppearance()`); a save that fails puts the change back and says so
68
+ * here. Nothing waits on the server, and nothing is disabled while it works.
69
+ */
70
+ export default function AppearanceForm() {
71
+ const appearance = useAppearance();
72
+ const { prefs, resolved, setPrefs, uploadBackground, removeBackground, reset, error, clearError, saving, isDefault, backgroundUrl } = appearance;
73
+ const background = prefs.background;
74
+ const colors = prefs.colors;
75
+
76
+ // The kind the radio shows: chosen before it can take effect ("Image" is chosen before there is one).
77
+ const [kind, setKind] = useState<BackgroundKind>(background?.kind ?? "none");
78
+ useEffect(() => setKind(background?.kind ?? "none"), [background?.kind]);
79
+ const [fileError, setFileError] = useState<string | null>(null);
80
+ const [dragging, setDragging] = useState(false);
81
+ const [uploading, setUploading] = useState<string | null>(null);
82
+ const inputRef = useRef<HTMLInputElement>(null);
83
+
84
+ // What the colour fields show while unset: the tokens as they are on the page (a deployment's branding may have its own).
85
+ const [current, setCurrent] = useState<Record<ColorKey, string>>(() => ({
86
+ primary: DEFAULT_TOKEN_COLORS.primary[resolved],
87
+ accent: DEFAULT_TOKEN_COLORS.accent[resolved],
88
+ surface: DEFAULT_TOKEN_COLORS.surface[resolved],
89
+ text: DEFAULT_TOKEN_COLORS.text[resolved],
90
+ }));
91
+ useEffect(() => {
92
+ setCurrent(
93
+ Object.fromEntries(COLOR_FIELDS.map((field) => [field.key, readToken(field.token, DEFAULT_TOKEN_COLORS[field.key][resolved])])) as Record<
94
+ ColorKey,
95
+ string
96
+ >,
97
+ );
98
+ }, [resolved, colors]);
99
+
100
+ const warnings = contrastWarnings(colors, resolved);
101
+ const hasImage = background?.imageVersion !== undefined;
102
+
103
+ function chooseKind(next: BackgroundKind) {
104
+ setKind(next);
105
+ if (next === "none") {
106
+ setPrefs({ background: { kind: "none" } });
107
+ } else if (next === "color") {
108
+ setPrefs({ background: { kind: "color", color: background?.color ?? STARTING_BACKGROUND_COLOR[resolved] } });
109
+ } else if (hasImage) {
110
+ setPrefs({ background: { kind: "image" } });
111
+ }
112
+ }
113
+
114
+ function handleFile(file: File | undefined) {
115
+ if (!file) {
116
+ return;
117
+ }
118
+ const problem = validateBackgroundFile(file);
119
+ if (problem) {
120
+ setFileError(problem);
121
+ return;
122
+ }
123
+ setFileError(null);
124
+ setUploading(file.name);
125
+ void uploadBackground(file).finally(() => setUploading(null));
126
+ }
127
+
128
+ function handleDrop(event: DragEvent<HTMLDivElement>) {
129
+ event.preventDefault();
130
+ setDragging(false);
131
+ handleFile(event.dataTransfer.files[0]);
132
+ }
133
+
134
+ const showImageControls = kind === "image";
135
+ const dim = background?.dim ?? DEFAULT_BACKGROUND.dim;
136
+ const blur = background?.blur ?? DEFAULT_BACKGROUND.blur;
137
+ const fit = background?.fit ?? DEFAULT_BACKGROUND.fit;
138
+
139
+ return (
140
+ <div className="flex-1 min-w-0 overflow-y-auto p-6">
141
+ <div className="max-w-2xl flex flex-col gap-8">
142
+ <div>
143
+ <h1 className="text-lg font-bold tracking-tight mb-1">Appearance</h1>
144
+ <p className="text-sm text-text-muted">
145
+ Choose how RapidMX looks for you. Every change shows straight away, across the whole app, and is saved to your account
146
+ for your other devices.
147
+ </p>
148
+ <p role="status" aria-live="polite" className="mt-1 h-4 text-xs text-text-muted">
149
+ {saving ? "Saving…" : ""}
150
+ </p>
151
+ </div>
152
+
153
+ {error && (
154
+ <Alert>
155
+ <span className="flex-1">{error}</span>
156
+ <button type="button" className="font-semibold underline hover:no-underline" onClick={clearError}>
157
+ Dismiss
158
+ </button>
159
+ </Alert>
160
+ )}
161
+
162
+ <fieldset className="flex flex-col gap-2">
163
+ <legend className="text-sm font-semibold mb-1">Colour scheme</legend>
164
+ <div className="flex flex-wrap gap-2">
165
+ {APPEARANCE_MODES.map((mode) => (
166
+ <label
167
+ key={mode}
168
+ className={[
169
+ "flex cursor-pointer items-center gap-2 rounded-sm border px-3 py-2 text-sm",
170
+ prefs.mode === mode ? "border-primary bg-primary/10" : "border-border hover:bg-surface-alt",
171
+ ].join(" ")}
172
+ >
173
+ <input
174
+ type="radio"
175
+ name="appearance-mode"
176
+ value={mode}
177
+ checked={prefs.mode === mode}
178
+ onChange={() => setPrefs({ mode })}
179
+ />
180
+ <span>
181
+ <span className="font-semibold">{MODE_LABELS[mode].label}</span>
182
+ <span className="ml-1.5 text-xs text-text-muted">{MODE_LABELS[mode].hint}</span>
183
+ </span>
184
+ </label>
185
+ ))}
186
+ </div>
187
+ </fieldset>
188
+
189
+ <fieldset className="flex flex-col gap-4">
190
+ <legend className="text-sm font-semibold mb-1">Theme colours</legend>
191
+ <p className="text-xs text-text-muted -mt-2">
192
+ These apply in both the light and the dark scheme. A colour you have not chosen stays as the app (or your organization)
193
+ has it.
194
+ </p>
195
+ {COLOR_FIELDS.map((field) => (
196
+ <ColorField
197
+ key={field.key}
198
+ id={`appearance-${field.key}`}
199
+ label={field.label}
200
+ description={field.description}
201
+ value={colors?.[field.key]}
202
+ current={current[field.key]}
203
+ onChange={(hex) => setPrefs({ colors: { [field.key]: hex } })}
204
+ onReset={() => setPrefs({ colors: { [field.key]: null } })}
205
+ />
206
+ ))}
207
+ {warnings.map((warning) => (
208
+ // The warning's own colours, not the theme's: it is about a theme that may be unreadable.
209
+ <p key={warning.id} role="status" className="rounded-sm bg-warning px-3 py-2 text-sm font-medium text-warning-contrast">
210
+ {warning.message}
211
+ </p>
212
+ ))}
213
+ </fieldset>
214
+
215
+ <fieldset className="flex flex-col gap-4">
216
+ <legend className="text-sm font-semibold mb-1">Background</legend>
217
+ <div className="flex flex-wrap gap-2">
218
+ {(["none", "color", "image"] as const).map((option) => (
219
+ <label
220
+ key={option}
221
+ className={[
222
+ "flex cursor-pointer items-center gap-2 rounded-sm border px-3 py-2 text-sm",
223
+ kind === option ? "border-primary bg-primary/10" : "border-border hover:bg-surface-alt",
224
+ ].join(" ")}
225
+ >
226
+ <input
227
+ type="radio"
228
+ name="appearance-background"
229
+ value={option}
230
+ checked={kind === option}
231
+ onChange={() => chooseKind(option)}
232
+ />
233
+ <span className="font-semibold">{option === "none" ? "None" : option === "color" ? "Colour" : "Image"}</span>
234
+ </label>
235
+ ))}
236
+ </div>
237
+
238
+ {kind === "color" && background?.color && (
239
+ <ColorField
240
+ id="appearance-background-color"
241
+ label="Background colour"
242
+ description="Shows behind every panel of the app."
243
+ value={background.color}
244
+ current={background.color}
245
+ onChange={(hex) => setPrefs({ background: { kind: "color", color: hex } })}
246
+ />
247
+ )}
248
+
249
+ {showImageControls && (
250
+ <div className="flex flex-col gap-4">
251
+ <div
252
+ role="group"
253
+ aria-label="Background image"
254
+ onDragEnter={(event) => {
255
+ event.preventDefault();
256
+ setDragging(true);
257
+ }}
258
+ onDragOver={(event) => {
259
+ event.preventDefault();
260
+ setDragging(true);
261
+ }}
262
+ onDragLeave={() => setDragging(false)}
263
+ onDrop={handleDrop}
264
+ className={[
265
+ "flex flex-wrap items-center gap-4 rounded-md border-2 border-dashed p-4",
266
+ dragging ? "border-primary bg-primary/10" : "border-border",
267
+ ].join(" ")}
268
+ >
269
+ {hasImage && backgroundUrl && (
270
+ <img
271
+ src={backgroundUrl}
272
+ alt="Your background image"
273
+ className="h-20 w-32 shrink-0 rounded-sm border border-border object-cover"
274
+ />
275
+ )}
276
+ <div className="min-w-0 flex-1 basis-48 text-sm">
277
+ <p className="font-semibold">{hasImage ? "Replace the image" : "Add an image"}</p>
278
+ <p className="text-xs text-text-muted">
279
+ Drag a picture here, or choose one. PNG, JPEG, WebP or AVIF, up to {formatBytes(BACKGROUND_MAX_BYTES)}.
280
+ </p>
281
+ {uploading && (
282
+ <p role="status" className="mt-1 text-xs text-text-muted">
283
+ Uploading {uploading}&hellip;
284
+ </p>
285
+ )}
286
+ </div>
287
+ <input
288
+ ref={inputRef}
289
+ type="file"
290
+ accept={BACKGROUND_IMAGE_TYPES.join(",")}
291
+ aria-label="Choose a background image"
292
+ className="sr-only"
293
+ onChange={(event) => {
294
+ handleFile(event.target.files?.[0]);
295
+ // The same file can be chosen again.
296
+ event.target.value = "";
297
+ }}
298
+ />
299
+ <div className="flex shrink-0 gap-2">
300
+ <Button type="button" variant="secondary" className="!w-auto" onClick={() => inputRef.current?.click()}>
301
+ Choose image
302
+ </Button>
303
+ {hasImage && (
304
+ <Button
305
+ type="button"
306
+ variant="secondary"
307
+ className="!w-auto"
308
+ onClick={() => {
309
+ setFileError(null);
310
+ void removeBackground();
311
+ }}
312
+ >
313
+ Remove
314
+ </Button>
315
+ )}
316
+ </div>
317
+ </div>
318
+ {fileError && (
319
+ <p role="alert" className="text-sm text-danger">
320
+ {fileError}
321
+ </p>
322
+ )}
323
+
324
+ {hasImage && (
325
+ <div className="flex flex-col gap-4">
326
+ <div>
327
+ <label htmlFor="appearance-dim" className="flex justify-between text-sm font-semibold">
328
+ <span>Dim</span>
329
+ <span className="font-normal text-text-muted">{Math.round(dim * 100)}%</span>
330
+ </label>
331
+ <input
332
+ id="appearance-dim"
333
+ type="range"
334
+ min={0}
335
+ max={Math.round(BACKGROUND_DIM_MAX * 100)}
336
+ step={1}
337
+ value={Math.round(dim * 100)}
338
+ aria-valuetext={`${Math.round(dim * 100)} percent`}
339
+ onChange={(event) => setPrefs({ background: { dim: Number(event.target.value) / 100 } })}
340
+ className="w-full accent-primary"
341
+ />
342
+ <p className="text-xs text-text-muted">Lays the app&rsquo;s surface colour over the picture so text stays easy to read.</p>
343
+ </div>
344
+ <div>
345
+ <label htmlFor="appearance-blur" className="flex justify-between text-sm font-semibold">
346
+ <span>Blur</span>
347
+ <span className="font-normal text-text-muted">{blur} px</span>
348
+ </label>
349
+ <input
350
+ id="appearance-blur"
351
+ type="range"
352
+ min={0}
353
+ max={BACKGROUND_BLUR_MAX}
354
+ step={1}
355
+ value={blur}
356
+ aria-valuetext={`${blur} pixels`}
357
+ onChange={(event) => setPrefs({ background: { blur: Number(event.target.value) } })}
358
+ className="w-full accent-primary"
359
+ />
360
+ </div>
361
+ <div>
362
+ <label htmlFor="appearance-fit" className="block text-sm font-semibold mb-1">
363
+ Fit
364
+ </label>
365
+ <select
366
+ id="appearance-fit"
367
+ value={fit}
368
+ onChange={(event) => setPrefs({ background: { fit: event.target.value as BackgroundFit } })}
369
+ className="w-full max-w-xs rounded-sm border border-border bg-surface px-2 py-1.5 text-sm text-text"
370
+ >
371
+ {BACKGROUND_FITS.map((option) => (
372
+ <option key={option} value={option}>
373
+ {FIT_LABELS[option]}
374
+ </option>
375
+ ))}
376
+ </select>
377
+ </div>
378
+ </div>
379
+ )}
380
+ </div>
381
+ )}
382
+ </fieldset>
383
+
384
+ <div>
385
+ <Button type="button" variant="secondary" className="!w-auto" disabled={isDefault} onClick={() => void reset()}>
386
+ Reset all
387
+ </Button>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ );
392
+ }
@@ -0,0 +1,49 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import React from "react";
6
+ import { appearanceBackgroundUrl, normalizeAppearance } from "@rapidmx/react-shared/appearance/preferencesApi.js";
7
+ import { APPEARANCE_BOOT_SCRIPT } from "./bootScript.js";
8
+ import { cacheKeyOf, timeOf } from "./appearanceCache.js";
9
+ import { APPEARANCE_STYLE_ID, appearanceCss } from "./theme.js";
10
+
11
+ /** What a page layout puts on `<html>` for the server-rendered appearance: the explicit scheme (never for "System", which the stylesheet's own media query follows) and the signed-in user. */
12
+ export function appearanceHtmlAttributes(appearance: unknown, userUid?: string): { "data-theme"?: "light" | "dark"; "data-uid"?: string } {
13
+ const prefs = normalizeAppearance(appearance);
14
+ const attributes: { "data-theme"?: "light" | "dark"; "data-uid"?: string } = {};
15
+ if (prefs && prefs.mode !== "system") {
16
+ attributes["data-theme"] = prefs.mode;
17
+ }
18
+ if (userUid) {
19
+ attributes["data-uid"] = userUid;
20
+ }
21
+ return attributes;
22
+ }
23
+
24
+ /**
25
+ * The `<head>` part of the user's appearance, for every page layout (`_layout.tsx` of www, admin and escrow): the stylesheet the server
26
+ * built from the preferences it was given (`appearance`, a page prop - only the www pages get one), so the first byte of the page is
27
+ * already themed, and the boot script (`APPEARANCE_BOOT_SCRIPT`), which applies the browser's cached copy before the first paint on pages
28
+ * that weren't given any. Nothing user-controlled reaches the CSS but validated colours, numbers and an image version made of safe characters.
29
+ */
30
+ export function AppearanceHead({ appearance }: { appearance?: unknown }) {
31
+ const prefs = normalizeAppearance(appearance);
32
+ const version = prefs?.background?.imageVersion;
33
+ const css = appearanceCss(prefs, { imageUrl: version === undefined ? undefined : appearanceBackgroundUrl(version) });
34
+ return (
35
+ <>
36
+ {prefs && (
37
+ <style
38
+ id={APPEARANCE_STYLE_ID}
39
+ data-key={cacheKeyOf(prefs)}
40
+ // When the server stored these: the boot script keeps a browser copy that is newer (this page can be a minute old).
41
+ data-t={timeOf(prefs.updatedAt)}
42
+ // Present even when it is empty, for its time: the boot script keeps a browser copy that is newer than this page's idea of nothing. Cannot contain a closing tag: nothing in the CSS is free text.
43
+ dangerouslySetInnerHTML={{ __html: css.replace(/</g, "\\3c ") }}
44
+ />
45
+ )}
46
+ <script dangerouslySetInnerHTML={{ __html: APPEARANCE_BOOT_SCRIPT }} />
47
+ </>
48
+ );
49
+ }