@q9labsai/chalk-react-native 4.1.17 → 5.0.1

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 (544) hide show
  1. package/README.md +134 -29
  2. package/android/build.gradle +35 -0
  3. package/android/compatibility/ModuleApiCompatibility.java +215 -0
  4. package/android/src/main/AndroidManifest.xml +2 -1
  5. package/android/src/main/java/ai/q9labs/chalk/reactnative/ChalkSpaceForegroundService.java +105 -31
  6. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkAndroidConnectionServicePackage.kt +2 -0
  7. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkAudioRouteModule.kt +137 -0
  8. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkCallServiceModule.kt +32 -0
  9. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/CommunicationDeviceListener.kt +23 -0
  10. package/android/src/test/kotlin/ai/q9labs/chalk/compatibility/UnsafeSignatures.kt +29 -0
  11. package/dist/android/call-service.d.ts +4 -0
  12. package/dist/android/call-service.d.ts.map +1 -0
  13. package/dist/avatar/FaceAvatar.d.ts +19 -0
  14. package/dist/avatar/FaceAvatar.d.ts.map +1 -0
  15. package/dist/avatar/tile-avatar-size.d.ts +3 -0
  16. package/dist/avatar/tile-avatar-size.d.ts.map +1 -0
  17. package/dist/chunk-GYVZSOGQ.js +30 -0
  18. package/dist/chunk-IHE6EA3T.js +25 -0
  19. package/dist/chunk-OIBT2FNM.js +1479 -0
  20. package/dist/client.d.ts +1 -5
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +185 -2
  23. package/dist/components/EmbeddedWhiteboard.d.ts.map +1 -1
  24. package/dist/components/native-media-visibility.d.ts +3 -2
  25. package/dist/components/native-media-visibility.d.ts.map +1 -1
  26. package/dist/components/useAndroidCallService.d.ts +8 -0
  27. package/dist/components/useAndroidCallService.d.ts.map +1 -0
  28. package/dist/dev.d.ts +10 -0
  29. package/dist/dev.d.ts.map +1 -0
  30. package/dist/dev.js +10 -0
  31. package/dist/dock/Dock.d.ts +23 -0
  32. package/dist/dock/Dock.d.ts.map +1 -0
  33. package/dist/dock/LeaveMenu.d.ts +12 -0
  34. package/dist/dock/LeaveMenu.d.ts.map +1 -0
  35. package/dist/dock/MoreSheet.d.ts +17 -0
  36. package/dist/dock/MoreSheet.d.ts.map +1 -0
  37. package/dist/dock/ReactionBurst.d.ts +8 -0
  38. package/dist/dock/ReactionBurst.d.ts.map +1 -0
  39. package/dist/dock/RoutePicker.d.ts +16 -0
  40. package/dist/dock/RoutePicker.d.ts.map +1 -0
  41. package/dist/dock/audio-route.d.ts +25 -0
  42. package/dist/dock/audio-route.d.ts.map +1 -0
  43. package/dist/error-YOPTEH5N.wav +0 -0
  44. package/dist/frame/CallFrame.d.ts +17 -0
  45. package/dist/frame/CallFrame.d.ts.map +1 -0
  46. package/dist/frame/Chalk.d.ts +40 -0
  47. package/dist/frame/Chalk.d.ts.map +1 -0
  48. package/dist/frame/back-handler.d.ts +6 -0
  49. package/dist/frame/back-handler.d.ts.map +1 -0
  50. package/dist/frame/dev-scenario.d.ts +28 -0
  51. package/dist/frame/dev-scenario.d.ts.map +1 -0
  52. package/dist/frame/device-layout.d.ts +16 -0
  53. package/dist/frame/device-layout.d.ts.map +1 -0
  54. package/dist/frame/measurement.d.ts +28 -0
  55. package/dist/frame/measurement.d.ts.map +1 -0
  56. package/dist/frame/portal.d.ts +24 -0
  57. package/dist/frame/portal.d.ts.map +1 -0
  58. package/dist/frame/preferences.d.ts +21 -0
  59. package/dist/frame/preferences.d.ts.map +1 -0
  60. package/dist/frame/regions.d.ts +25 -0
  61. package/dist/frame/regions.d.ts.map +1 -0
  62. package/dist/hand-DFWVXPH2.wav +0 -0
  63. package/dist/haptics/haptics.d.ts +12 -0
  64. package/dist/haptics/haptics.d.ts.map +1 -0
  65. package/dist/header/CallDetailsSheet.d.ts +18 -0
  66. package/dist/header/CallDetailsSheet.d.ts.map +1 -0
  67. package/dist/header/Header.d.ts +17 -0
  68. package/dist/header/Header.d.ts.map +1 -0
  69. package/dist/header/QrCode.d.ts +6 -0
  70. package/dist/header/QrCode.d.ts.map +1 -0
  71. package/dist/icons/Icon.d.ts +12 -0
  72. package/dist/icons/Icon.d.ts.map +1 -0
  73. package/dist/icons/icon-element.d.ts +24 -0
  74. package/dist/icons/icon-element.d.ts.map +1 -0
  75. package/dist/icons/icon-paths.d.ts +530 -0
  76. package/dist/icons/icon-paths.d.ts.map +1 -0
  77. package/dist/index.d.ts +6 -9
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +2956 -38
  80. package/dist/join-Z6RL5AHE.wav +0 -0
  81. package/dist/knock-4VBQSHK4.wav +0 -0
  82. package/dist/leave-MQLZOWQD.wav +0 -0
  83. package/dist/limbo/Limbo.d.ts +25 -0
  84. package/dist/limbo/Limbo.d.ts.map +1 -0
  85. package/dist/limbo/Veil.d.ts +10 -0
  86. package/dist/limbo/Veil.d.ts.map +1 -0
  87. package/dist/media/native-capture-error.d.ts +7 -0
  88. package/dist/media/native-capture-error.d.ts.map +1 -0
  89. package/dist/message-6Y6WJGTB.wav +0 -0
  90. package/dist/motion/MovingFrame.d.ts +36 -0
  91. package/dist/motion/MovingFrame.d.ts.map +1 -0
  92. package/dist/motion/motion.d.ts +82 -0
  93. package/dist/motion/motion.d.ts.map +1 -0
  94. package/dist/motion/presence.d.ts +6 -0
  95. package/dist/motion/presence.d.ts.map +1 -0
  96. package/dist/motion/press.d.ts +15 -0
  97. package/dist/motion/press.d.ts.map +1 -0
  98. package/dist/mute-H5SJFXVI.wav +0 -0
  99. package/dist/primitives/BottomSheet.d.ts +36 -0
  100. package/dist/primitives/BottomSheet.d.ts.map +1 -0
  101. package/dist/primitives/ChalkText.d.ts +13 -0
  102. package/dist/primitives/ChalkText.d.ts.map +1 -0
  103. package/dist/primitives/CopyButton.d.ts +11 -0
  104. package/dist/primitives/CopyButton.d.ts.map +1 -0
  105. package/dist/primitives/IconButton.d.ts +26 -0
  106. package/dist/primitives/IconButton.d.ts.map +1 -0
  107. package/dist/primitives/Menu.d.ts +39 -0
  108. package/dist/primitives/Menu.d.ts.map +1 -0
  109. package/dist/primitives/NameTag.d.ts +19 -0
  110. package/dist/primitives/NameTag.d.ts.map +1 -0
  111. package/dist/primitives/PillButton.d.ts +19 -0
  112. package/dist/primitives/PillButton.d.ts.map +1 -0
  113. package/dist/primitives/SegmentedControl.d.ts +17 -0
  114. package/dist/primitives/SegmentedControl.d.ts.map +1 -0
  115. package/dist/primitives/SheetGroup.d.ts +24 -0
  116. package/dist/primitives/SheetGroup.d.ts.map +1 -0
  117. package/dist/primitives/Switch.d.ts +9 -0
  118. package/dist/primitives/Switch.d.ts.map +1 -0
  119. package/dist/primitives/TileShell.d.ts +18 -0
  120. package/dist/primitives/TileShell.d.ts.map +1 -0
  121. package/dist/primitives/announce.d.ts +3 -0
  122. package/dist/primitives/announce.d.ts.map +1 -0
  123. package/dist/primitives/menu-placement.d.ts +32 -0
  124. package/dist/primitives/menu-placement.d.ts.map +1 -0
  125. package/dist/primitives/toast/Toast.d.ts +8 -0
  126. package/dist/primitives/toast/Toast.d.ts.map +1 -0
  127. package/dist/primitives/toast/ToastStack.d.ts +18 -0
  128. package/dist/primitives/toast/ToastStack.d.ts.map +1 -0
  129. package/dist/primitives/toast/toast-store.d.ts +39 -0
  130. package/dist/primitives/toast/toast-store.d.ts.map +1 -0
  131. package/dist/recording-Q4GB77Y4.wav +0 -0
  132. package/dist/sheets/people-chat/ChatNotices.d.ts +14 -0
  133. package/dist/sheets/people-chat/ChatNotices.d.ts.map +1 -0
  134. package/dist/sheets/people-chat/ChatTab.d.ts +6 -0
  135. package/dist/sheets/people-chat/ChatTab.d.ts.map +1 -0
  136. package/dist/sheets/people-chat/Composer.d.ts +7 -0
  137. package/dist/sheets/people-chat/Composer.d.ts.map +1 -0
  138. package/dist/sheets/people-chat/PeopleChatSheet.d.ts +17 -0
  139. package/dist/sheets/people-chat/PeopleChatSheet.d.ts.map +1 -0
  140. package/dist/sheets/people-chat/PeopleTab.d.ts +3 -0
  141. package/dist/sheets/people-chat/PeopleTab.d.ts.map +1 -0
  142. package/dist/sheets/people-chat/PhotoViewer.d.ts +19 -0
  143. package/dist/sheets/people-chat/PhotoViewer.d.ts.map +1 -0
  144. package/dist/sheets/people-chat/TextField.d.ts +4 -0
  145. package/dist/sheets/people-chat/TextField.d.ts.map +1 -0
  146. package/dist/sheets/people-chat/attachments.d.ts +33 -0
  147. package/dist/sheets/people-chat/attachments.d.ts.map +1 -0
  148. package/dist/sheets/people-chat/chat-model.d.ts +64 -0
  149. package/dist/sheets/people-chat/chat-model.d.ts.map +1 -0
  150. package/dist/sheets/people-chat/people-model.d.ts +43 -0
  151. package/dist/sheets/people-chat/people-model.d.ts.map +1 -0
  152. package/dist/sounds/sounds.d.ts +33 -0
  153. package/dist/sounds/sounds.d.ts.map +1 -0
  154. package/dist/space-client/create-native-space-client.d.ts +0 -4
  155. package/dist/space-client/create-native-space-client.d.ts.map +1 -1
  156. package/dist/space-client/random-uuid.d.ts +7 -0
  157. package/dist/space-client/random-uuid.d.ts.map +1 -0
  158. package/dist/stage/ContentTile.d.ts +18 -0
  159. package/dist/stage/ContentTile.d.ts.map +1 -0
  160. package/dist/stage/ParticipantTile.d.ts +18 -0
  161. package/dist/stage/ParticipantTile.d.ts.map +1 -0
  162. package/dist/stage/Stage.d.ts +14 -0
  163. package/dist/stage/Stage.d.ts.map +1 -0
  164. package/dist/stage/TileSheet.d.ts +12 -0
  165. package/dist/stage/TileSheet.d.ts.map +1 -0
  166. package/dist/stage/camera.d.ts +7 -0
  167. package/dist/stage/camera.d.ts.map +1 -0
  168. package/dist/stage/layout.d.ts +57 -0
  169. package/dist/stage/layout.d.ts.map +1 -0
  170. package/dist/stage/space-selector.d.ts +10 -0
  171. package/dist/stage/space-selector.d.ts.map +1 -0
  172. package/dist/stage/stage-view.d.ts +23 -0
  173. package/dist/stage/stage-view.d.ts.map +1 -0
  174. package/dist/status/CallOverlays.d.ts +17 -0
  175. package/dist/status/CallOverlays.d.ts.map +1 -0
  176. package/dist/status/ChalkArrival.d.ts +24 -0
  177. package/dist/status/ChalkArrival.d.ts.map +1 -0
  178. package/dist/status/StatusPage.d.ts +14 -0
  179. package/dist/status/StatusPage.d.ts.map +1 -0
  180. package/dist/status/call-screen.d.ts +20 -0
  181. package/dist/status/call-screen.d.ts.map +1 -0
  182. package/dist/test-VD2KAM4D.wav +0 -0
  183. package/dist/theme/theme.d.ts +15 -0
  184. package/dist/theme/theme.d.ts.map +1 -0
  185. package/dist/theme/tokens.d.ts +248 -0
  186. package/dist/theme/tokens.d.ts.map +1 -0
  187. package/dist/toasts/CallEventToasts.d.ts +21 -0
  188. package/dist/toasts/CallEventToasts.d.ts.map +1 -0
  189. package/dist/toasts/notices.d.ts +34 -0
  190. package/dist/toasts/notices.d.ts.map +1 -0
  191. package/dist/ui/ChalkUI.d.ts +12 -0
  192. package/dist/ui/ChalkUI.d.ts.map +1 -0
  193. package/dist/ui.d.ts +25 -0
  194. package/dist/ui.d.ts.map +1 -0
  195. package/dist/ui.js +77 -0
  196. package/dist/unmute-2XR2LICU.wav +0 -0
  197. package/embedded/chalk-whiteboard/asset-manifest.json +2 -2
  198. package/embedded/chalk-whiteboard/renderer.js +510 -510
  199. package/ios/ChalkAudioRouteModule.swift +93 -0
  200. package/ios/ChalkAudioRouteModuleBridge.m +9 -0
  201. package/package.json +46 -76
  202. package/src/android/call-service.ts +19 -0
  203. package/src/avatar/FaceAvatar.tsx +145 -0
  204. package/src/avatar/tile-avatar-size.ts +4 -0
  205. package/src/client.ts +1 -5
  206. package/src/components/EmbeddedWhiteboard.tsx +14 -71
  207. package/src/components/native-media-visibility.ts +1 -2
  208. package/src/components/useAndroidCallService.ts +41 -0
  209. package/src/dev.ts +9 -0
  210. package/src/dock/Dock.tsx +95 -0
  211. package/src/dock/LeaveMenu.tsx +21 -0
  212. package/src/dock/MoreSheet.tsx +233 -0
  213. package/src/dock/ReactionBurst.tsx +55 -0
  214. package/src/dock/RoutePicker.tsx +42 -0
  215. package/src/dock/audio-route.ts +69 -0
  216. package/src/frame/CallFrame.tsx +209 -0
  217. package/src/frame/Chalk.tsx +83 -0
  218. package/src/frame/back-handler.ts +19 -0
  219. package/src/frame/dev-scenario.ts +48 -0
  220. package/src/frame/device-layout.tsx +31 -0
  221. package/src/frame/measurement.ts +44 -0
  222. package/src/frame/portal.tsx +107 -0
  223. package/src/frame/preferences.tsx +48 -0
  224. package/src/frame/regions.test.ts +38 -0
  225. package/src/frame/regions.ts +60 -0
  226. package/src/haptics/haptics.ts +19 -0
  227. package/src/header/CallDetailsSheet.tsx +139 -0
  228. package/src/header/Header.tsx +107 -0
  229. package/src/header/QrCode.tsx +34 -0
  230. package/src/icons/Icon.tsx +35 -0
  231. package/src/icons/icon-element.ts +11 -0
  232. package/src/icons/icon-paths.ts +199 -0
  233. package/src/index.ts +6 -9
  234. package/src/limbo/Limbo.tsx +159 -0
  235. package/src/limbo/Veil.tsx +36 -0
  236. package/src/media/native-capture-error.test.ts +19 -0
  237. package/src/media/native-capture-error.ts +13 -0
  238. package/src/motion/MovingFrame.tsx +101 -0
  239. package/src/motion/motion.ts +33 -0
  240. package/src/motion/presence.ts +20 -0
  241. package/src/motion/press.ts +35 -0
  242. package/src/primitives/BottomSheet.tsx +211 -0
  243. package/src/primitives/ChalkText.tsx +23 -0
  244. package/src/primitives/CopyButton.tsx +78 -0
  245. package/src/primitives/IconButton.tsx +90 -0
  246. package/src/primitives/Menu.tsx +212 -0
  247. package/src/primitives/NameTag.tsx +68 -0
  248. package/src/primitives/PillButton.tsx +56 -0
  249. package/src/primitives/SegmentedControl.tsx +55 -0
  250. package/src/primitives/SheetGroup.tsx +72 -0
  251. package/src/primitives/Switch.tsx +39 -0
  252. package/src/primitives/TileShell.tsx +53 -0
  253. package/src/primitives/announce.ts +7 -0
  254. package/src/primitives/menu-placement.test.ts +50 -0
  255. package/src/primitives/menu-placement.ts +43 -0
  256. package/src/primitives/toast/Toast.tsx +50 -0
  257. package/src/primitives/toast/ToastStack.tsx +89 -0
  258. package/src/primitives/toast/toast-store.ts +86 -0
  259. package/src/sheets/people-chat/ChatNotices.tsx +69 -0
  260. package/src/sheets/people-chat/ChatTab.tsx +186 -0
  261. package/src/sheets/people-chat/Composer.tsx +182 -0
  262. package/src/sheets/people-chat/PeopleChatSheet.tsx +100 -0
  263. package/src/sheets/people-chat/PeopleTab.tsx +326 -0
  264. package/src/sheets/people-chat/PhotoViewer.tsx +132 -0
  265. package/src/sheets/people-chat/TextField.tsx +14 -0
  266. package/src/sheets/people-chat/attachments.tsx +183 -0
  267. package/src/sheets/people-chat/chat-model.ts +157 -0
  268. package/src/sheets/people-chat/people-chat-model.test.ts +98 -0
  269. package/src/sheets/people-chat/people-model.ts +68 -0
  270. package/src/sounds/assets/error.wav +0 -0
  271. package/src/sounds/assets/hand.wav +0 -0
  272. package/src/sounds/assets/join.wav +0 -0
  273. package/src/sounds/assets/knock.wav +0 -0
  274. package/src/sounds/assets/leave.wav +0 -0
  275. package/src/sounds/assets/message.wav +0 -0
  276. package/src/sounds/assets/mute.wav +0 -0
  277. package/src/sounds/assets/recording.wav +0 -0
  278. package/src/sounds/assets/test.wav +0 -0
  279. package/src/sounds/assets/unmute.wav +0 -0
  280. package/src/sounds/assets.d.ts +5 -0
  281. package/src/sounds/sounds.ts +50 -0
  282. package/src/space-client/create-native-space-client.ts +20 -181
  283. package/src/space-client/random-uuid.ts +17 -0
  284. package/src/space-client/whiteboard-watchdog.test.ts +23 -0
  285. package/src/stage/ContentTile.tsx +100 -0
  286. package/src/stage/ParticipantTile.tsx +140 -0
  287. package/src/stage/Stage.tsx +152 -0
  288. package/src/stage/TileSheet.tsx +116 -0
  289. package/src/stage/camera.ts +20 -0
  290. package/src/stage/layout.test.ts +77 -0
  291. package/src/stage/layout.ts +226 -0
  292. package/src/stage/space-selector.ts +31 -0
  293. package/src/stage/stage-view.tsx +68 -0
  294. package/src/status/CallOverlays.tsx +47 -0
  295. package/src/status/ChalkArrival.tsx +53 -0
  296. package/src/status/StatusPage.tsx +123 -0
  297. package/src/status/call-screen.test.ts +42 -0
  298. package/src/status/call-screen.ts +29 -0
  299. package/src/theme/theme.tsx +28 -0
  300. package/src/theme/tokens.ts +247 -0
  301. package/src/toasts/CallEventToasts.tsx +192 -0
  302. package/src/toasts/notices.test.ts +92 -0
  303. package/src/toasts/notices.ts +62 -0
  304. package/src/ui/ChalkUI.tsx +49 -0
  305. package/src/ui.ts +24 -0
  306. package/dist/android.d.ts +0 -2
  307. package/dist/android.d.ts.map +0 -1
  308. package/dist/android.js +0 -62
  309. package/dist/chunk-2OS7SSXE.js +0 -552
  310. package/dist/chunk-5DQ4KBRH.js +0 -690
  311. package/dist/chunk-ITLUHTVS.js +0 -5271
  312. package/dist/chunk-JB4LNWVP.js +0 -247
  313. package/dist/components/Chalk.d.ts +0 -79
  314. package/dist/components/Chalk.d.ts.map +0 -1
  315. package/dist/components/ChatMessageList.d.ts +0 -11
  316. package/dist/components/ChatMessageList.d.ts.map +0 -1
  317. package/dist/components/Entrance.d.ts +0 -22
  318. package/dist/components/Entrance.d.ts.map +0 -1
  319. package/dist/components/EntranceView.android.d.ts +0 -4
  320. package/dist/components/EntranceView.android.d.ts.map +0 -1
  321. package/dist/components/EntranceView.d.ts +0 -18
  322. package/dist/components/EntranceView.d.ts.map +0 -1
  323. package/dist/components/EntranceView.ios-pad.d.ts +0 -3
  324. package/dist/components/EntranceView.ios-pad.d.ts.map +0 -1
  325. package/dist/components/EntranceView.ios-phone.d.ts +0 -3
  326. package/dist/components/EntranceView.ios-phone.d.ts.map +0 -1
  327. package/dist/components/EntranceView.macos.d.ts +0 -4
  328. package/dist/components/EntranceView.macos.d.ts.map +0 -1
  329. package/dist/components/FaceAvatar.d.ts +0 -16
  330. package/dist/components/FaceAvatar.d.ts.map +0 -1
  331. package/dist/components/GradientSurface.d.ts +0 -14
  332. package/dist/components/GradientSurface.d.ts.map +0 -1
  333. package/dist/components/MediaView.d.ts +0 -14
  334. package/dist/components/MediaView.d.ts.map +0 -1
  335. package/dist/components/ReactionPicker.d.ts +0 -10
  336. package/dist/components/ReactionPicker.d.ts.map +0 -1
  337. package/dist/components/SpaceView.android.d.ts +0 -2
  338. package/dist/components/SpaceView.android.d.ts.map +0 -1
  339. package/dist/components/SpaceView.d.ts +0 -33
  340. package/dist/components/SpaceView.d.ts.map +0 -1
  341. package/dist/components/SpaceView.ios-pad.d.ts +0 -2
  342. package/dist/components/SpaceView.ios-pad.d.ts.map +0 -1
  343. package/dist/components/SpaceView.ios-phone.d.ts +0 -2
  344. package/dist/components/SpaceView.ios-phone.d.ts.map +0 -1
  345. package/dist/components/SpaceView.macos.d.ts +0 -2
  346. package/dist/components/SpaceView.macos.d.ts.map +0 -1
  347. package/dist/components/SpaceView.shared.d.ts +0 -3
  348. package/dist/components/SpaceView.shared.d.ts.map +0 -1
  349. package/dist/components/episode-diagnostic-render-observer.d.ts +0 -2
  350. package/dist/components/episode-diagnostic-render-observer.d.ts.map +0 -1
  351. package/dist/components/logo/Logo.d.ts +0 -11
  352. package/dist/components/logo/Logo.d.ts.map +0 -1
  353. package/dist/components/native-animation-controller.d.ts +0 -7
  354. package/dist/components/native-animation-controller.d.ts.map +0 -1
  355. package/dist/components/native-chat.d.ts +0 -8
  356. package/dist/components/native-chat.d.ts.map +0 -1
  357. package/dist/components/native-entrance/entrance-controller-store.d.ts +0 -34
  358. package/dist/components/native-entrance/entrance-controller-store.d.ts.map +0 -1
  359. package/dist/components/native-entrance/useEntranceController.d.ts +0 -19
  360. package/dist/components/native-entrance/useEntranceController.d.ts.map +0 -1
  361. package/dist/components/native-joining-loading-animation.d.ts +0 -9
  362. package/dist/components/native-joining-loading-animation.d.ts.map +0 -1
  363. package/dist/components/native-space-view/AppearanceSettings.d.ts +0 -2
  364. package/dist/components/native-space-view/AppearanceSettings.d.ts.map +0 -1
  365. package/dist/components/native-space-view/FeedbackSheet.d.ts +0 -12
  366. package/dist/components/native-space-view/FeedbackSheet.d.ts.map +0 -1
  367. package/dist/components/native-space-view/ParticipantActionSheet.d.ts +0 -9
  368. package/dist/components/native-space-view/ParticipantActionSheet.d.ts.map +0 -1
  369. package/dist/components/native-space-view/SettingsSheet.d.ts +0 -7
  370. package/dist/components/native-space-view/SettingsSheet.d.ts.map +0 -1
  371. package/dist/components/native-space-view/SpaceActionMenu.d.ts +0 -10
  372. package/dist/components/native-space-view/SpaceActionMenu.d.ts.map +0 -1
  373. package/dist/components/native-space-view/SpaceBottomDock.android.d.ts +0 -4
  374. package/dist/components/native-space-view/SpaceBottomDock.android.d.ts.map +0 -1
  375. package/dist/components/native-space-view/SpaceChatAttachment.d.ts +0 -8
  376. package/dist/components/native-space-view/SpaceChatAttachment.d.ts.map +0 -1
  377. package/dist/components/native-space-view/SpaceChatSheet.d.ts +0 -5
  378. package/dist/components/native-space-view/SpaceChatSheet.d.ts.map +0 -1
  379. package/dist/components/native-space-view/SpaceGrid.android.d.ts +0 -8
  380. package/dist/components/native-space-view/SpaceGrid.android.d.ts.map +0 -1
  381. package/dist/components/native-space-view/SpaceMoreSheet.d.ts +0 -7
  382. package/dist/components/native-space-view/SpaceMoreSheet.d.ts.map +0 -1
  383. package/dist/components/native-space-view/SpacePanelSheet.d.ts +0 -5
  384. package/dist/components/native-space-view/SpacePanelSheet.d.ts.map +0 -1
  385. package/dist/components/native-space-view/SpacePeopleSheet.d.ts +0 -5
  386. package/dist/components/native-space-view/SpacePeopleSheet.d.ts.map +0 -1
  387. package/dist/components/native-space-view/SpaceStage.android.d.ts +0 -29
  388. package/dist/components/native-space-view/SpaceStage.android.d.ts.map +0 -1
  389. package/dist/components/native-space-view/SpaceSurfacePrimitives.d.ts +0 -30
  390. package/dist/components/native-space-view/SpaceSurfacePrimitives.d.ts.map +0 -1
  391. package/dist/components/native-space-view/SpaceTopBar.android.d.ts +0 -11
  392. package/dist/components/native-space-view/SpaceTopBar.android.d.ts.map +0 -1
  393. package/dist/components/native-space-view/SpaceWhiteboardSurface.d.ts +0 -15
  394. package/dist/components/native-space-view/SpaceWhiteboardSurface.d.ts.map +0 -1
  395. package/dist/components/native-space-view/diagnostics.d.ts +0 -48
  396. package/dist/components/native-space-view/diagnostics.d.ts.map +0 -1
  397. package/dist/components/native-space-view/format-space-duration.d.ts +0 -2
  398. package/dist/components/native-space-view/format-space-duration.d.ts.map +0 -1
  399. package/dist/components/native-space-view/native-space-grid-keys.d.ts +0 -3
  400. package/dist/components/native-space-view/native-space-grid-keys.d.ts.map +0 -1
  401. package/dist/components/native-space-view/native-space-page-store.d.ts +0 -8
  402. package/dist/components/native-space-view/native-space-page-store.d.ts.map +0 -1
  403. package/dist/components/native-space-view/space-chat-attachments.d.ts +0 -15
  404. package/dist/components/native-space-view/space-chat-attachments.d.ts.map +0 -1
  405. package/dist/components/native-space-view/space-panel-layout.d.ts +0 -10
  406. package/dist/components/native-space-view/space-panel-layout.d.ts.map +0 -1
  407. package/dist/components/native-space-view/space-progressive-surface-helpers.d.ts +0 -16
  408. package/dist/components/native-space-view/space-progressive-surface-helpers.d.ts.map +0 -1
  409. package/dist/components/native-space-view/space-progressive-surface-types.d.ts +0 -3
  410. package/dist/components/native-space-view/space-progressive-surface-types.d.ts.map +0 -1
  411. package/dist/components/native-space-view/types.d.ts +0 -24
  412. package/dist/components/native-space-view/types.d.ts.map +0 -1
  413. package/dist/components/native-space-view/useSpaceViewController.contract.d.ts +0 -41
  414. package/dist/components/native-space-view/useSpaceViewController.contract.d.ts.map +0 -1
  415. package/dist/components/native-space-view/useSpaceViewController.d.ts +0 -156
  416. package/dist/components/native-space-view/useSpaceViewController.d.ts.map +0 -1
  417. package/dist/components/native-space-view/useSpaceViewDerived.d.ts +0 -12
  418. package/dist/components/native-space-view/useSpaceViewDerived.d.ts.map +0 -1
  419. package/dist/components/native-space-view/useSpaceViewPanels.d.ts +0 -70
  420. package/dist/components/native-space-view/useSpaceViewPanels.d.ts.map +0 -1
  421. package/dist/feedback/native-capture.d.ts +0 -9
  422. package/dist/feedback/native-capture.d.ts.map +0 -1
  423. package/dist/feedback/native-evidence.d.ts +0 -3
  424. package/dist/feedback/native-evidence.d.ts.map +0 -1
  425. package/dist/hooks/entrance-preview-store.d.ts +0 -16
  426. package/dist/hooks/entrance-preview-store.d.ts.map +0 -1
  427. package/dist/hooks/useEntrancePreview.d.ts +0 -7
  428. package/dist/hooks/useEntrancePreview.d.ts.map +0 -1
  429. package/dist/hooks/useLayout.d.ts +0 -16
  430. package/dist/hooks/useLayout.d.ts.map +0 -1
  431. package/dist/hooks/usePanels.d.ts +0 -12
  432. package/dist/hooks/usePanels.d.ts.map +0 -1
  433. package/dist/ios-pad.d.ts +0 -2
  434. package/dist/ios-pad.d.ts.map +0 -1
  435. package/dist/ios-pad.js +0 -62
  436. package/dist/ios-phone.d.ts +0 -2
  437. package/dist/ios-phone.d.ts.map +0 -1
  438. package/dist/ios-phone.js +0 -62
  439. package/dist/ios.d.ts +0 -2
  440. package/dist/ios.d.ts.map +0 -1
  441. package/dist/ios.js +0 -62
  442. package/dist/macos.d.ts +0 -2
  443. package/dist/macos.d.ts.map +0 -1
  444. package/dist/macos.js +0 -62
  445. package/dist/space-client/cloudflare-rtk-native.d.ts +0 -3
  446. package/dist/space-client/cloudflare-rtk-native.d.ts.map +0 -1
  447. package/dist/telemetry.d.ts +0 -63
  448. package/dist/telemetry.d.ts.map +0 -1
  449. package/dist/ui/appearance.d.ts +0 -246
  450. package/dist/ui/appearance.d.ts.map +0 -1
  451. package/dist/ui/appearance.js +0 -14
  452. package/dist/ui/native-appearance-context.d.ts +0 -15
  453. package/dist/ui/native-appearance-context.d.ts.map +0 -1
  454. package/dist/ui/native-theme.d.ts +0 -17
  455. package/dist/ui/native-theme.d.ts.map +0 -1
  456. package/dist/ui/native-types.d.ts +0 -38
  457. package/dist/ui/native-types.d.ts.map +0 -1
  458. package/dist/ui/participant-avatar.d.ts +0 -21
  459. package/dist/ui/participant-avatar.d.ts.map +0 -1
  460. package/dist/ui/theme-tokens.d.ts +0 -25
  461. package/dist/ui/theme-tokens.d.ts.map +0 -1
  462. package/dist/ui/theme.d.ts +0 -404
  463. package/dist/ui/theme.d.ts.map +0 -1
  464. package/dist/ui/theme.js +0 -8
  465. package/dist/utils/native-space-layout.d.ts +0 -42
  466. package/dist/utils/native-space-layout.d.ts.map +0 -1
  467. package/src/android.ts +0 -1
  468. package/src/components/Chalk.tsx +0 -363
  469. package/src/components/ChatMessageList.tsx +0 -209
  470. package/src/components/Entrance.tsx +0 -42
  471. package/src/components/EntranceView.android.tsx +0 -274
  472. package/src/components/EntranceView.ios-pad.tsx +0 -326
  473. package/src/components/EntranceView.ios-phone.tsx +0 -271
  474. package/src/components/EntranceView.macos.tsx +0 -278
  475. package/src/components/EntranceView.tsx +0 -38
  476. package/src/components/FaceAvatar.tsx +0 -120
  477. package/src/components/GradientSurface.tsx +0 -23
  478. package/src/components/MediaView.tsx +0 -76
  479. package/src/components/ReactionPicker.tsx +0 -98
  480. package/src/components/SpaceView.android.tsx +0 -1
  481. package/src/components/SpaceView.ios-pad.tsx +0 -1
  482. package/src/components/SpaceView.ios-phone.tsx +0 -1
  483. package/src/components/SpaceView.macos.tsx +0 -1
  484. package/src/components/SpaceView.shared.tsx +0 -98
  485. package/src/components/SpaceView.tsx +0 -54
  486. package/src/components/episode-diagnostic-render-observer.ts +0 -12
  487. package/src/components/logo/Logo.tsx +0 -309
  488. package/src/components/native-animation-controller.ts +0 -36
  489. package/src/components/native-chat.ts +0 -23
  490. package/src/components/native-entrance/entrance-controller-store.ts +0 -112
  491. package/src/components/native-entrance/useEntranceController.ts +0 -60
  492. package/src/components/native-joining-loading-animation.ts +0 -26
  493. package/src/components/native-space-view/AppearanceSettings.tsx +0 -114
  494. package/src/components/native-space-view/FeedbackSheet.tsx +0 -296
  495. package/src/components/native-space-view/ParticipantActionSheet.tsx +0 -99
  496. package/src/components/native-space-view/SettingsSheet.tsx +0 -164
  497. package/src/components/native-space-view/SpaceActionMenu.tsx +0 -109
  498. package/src/components/native-space-view/SpaceBottomDock.android.tsx +0 -115
  499. package/src/components/native-space-view/SpaceChatAttachment.tsx +0 -105
  500. package/src/components/native-space-view/SpaceChatSheet.tsx +0 -184
  501. package/src/components/native-space-view/SpaceGrid.android.tsx +0 -323
  502. package/src/components/native-space-view/SpaceMoreSheet.tsx +0 -190
  503. package/src/components/native-space-view/SpacePanelSheet.tsx +0 -45
  504. package/src/components/native-space-view/SpacePeopleSheet.tsx +0 -116
  505. package/src/components/native-space-view/SpaceStage.android.tsx +0 -372
  506. package/src/components/native-space-view/SpaceSurfacePrimitives.tsx +0 -75
  507. package/src/components/native-space-view/SpaceTopBar.android.tsx +0 -113
  508. package/src/components/native-space-view/SpaceWhiteboardSurface.tsx +0 -69
  509. package/src/components/native-space-view/diagnostics.ts +0 -84
  510. package/src/components/native-space-view/format-space-duration.ts +0 -3
  511. package/src/components/native-space-view/native-space-grid-keys.ts +0 -6
  512. package/src/components/native-space-view/native-space-page-store.ts +0 -41
  513. package/src/components/native-space-view/space-chat-attachments.test.ts +0 -26
  514. package/src/components/native-space-view/space-chat-attachments.ts +0 -37
  515. package/src/components/native-space-view/space-panel-layout.ts +0 -10
  516. package/src/components/native-space-view/space-progressive-surface-helpers.ts +0 -33
  517. package/src/components/native-space-view/space-progressive-surface-types.ts +0 -3
  518. package/src/components/native-space-view/types.ts +0 -25
  519. package/src/components/native-space-view/useSpaceViewController.contract.ts +0 -34
  520. package/src/components/native-space-view/useSpaceViewController.ts +0 -416
  521. package/src/components/native-space-view/useSpaceViewDerived.ts +0 -29
  522. package/src/components/native-space-view/useSpaceViewPanels.ts +0 -170
  523. package/src/feedback/native-capture.ts +0 -62
  524. package/src/feedback/native-evidence.ts +0 -37
  525. package/src/hooks/entrance-preview-store.ts +0 -99
  526. package/src/hooks/useEntrancePreview.ts +0 -26
  527. package/src/hooks/useLayout.ts +0 -37
  528. package/src/hooks/usePanels.ts +0 -30
  529. package/src/ios-pad.ts +0 -1
  530. package/src/ios-phone.ts +0 -1
  531. package/src/ios.ts +0 -1
  532. package/src/macos.ts +0 -1
  533. package/src/space-client/cloudflare-rtk-native.ts +0 -104
  534. package/src/telemetry.ts +0 -234
  535. package/src/types/hugeicons-direct.d.ts +0 -4
  536. package/src/types/react-native-safe-area-context.d.ts +0 -8
  537. package/src/ui/appearance.ts +0 -808
  538. package/src/ui/native-appearance-context.tsx +0 -74
  539. package/src/ui/native-theme.tsx +0 -54
  540. package/src/ui/native-types.ts +0 -40
  541. package/src/ui/participant-avatar.ts +0 -47
  542. package/src/ui/theme-tokens.ts +0 -41
  543. package/src/ui/theme.ts +0 -214
  544. package/src/utils/native-space-layout.ts +0 -135
@@ -0,0 +1,226 @@
1
+ import { STAGE_GAP as G } from "../frame/regions";
2
+ import type { Frame } from "../motion/MovingFrame";
3
+
4
+ /** What a tile shows: one person, the shared screen or board, or the "+N" tile that opens People. */
5
+ export type Slot = { readonly kind: "person"; readonly id: string } | { readonly kind: "content" };
6
+
7
+ export type StageItem =
8
+ | { readonly kind: "person"; readonly id: string; readonly frame: Frame; readonly big: boolean; readonly float: boolean }
9
+ /** `inner` is the shared content's box inside the tile, letterboxed to its aspect. */
10
+ | { readonly kind: "content"; readonly frame: Frame; readonly inner: Frame; readonly mini: boolean }
11
+ | { readonly kind: "more"; readonly frame: Frame; readonly count: number };
12
+
13
+ export type StageContent = { readonly kind: "screen" | "board"; /** Width over height; null fills the tile. */ readonly aspect: number | null };
14
+
15
+ export type StageInput = {
16
+ readonly stage: Frame;
17
+ /** Portrait or landscape phone. The iPad uses the phone layout today. */
18
+ readonly portrait: boolean;
19
+ readonly layout: "spotlight" | "grid";
20
+ /** Everyone in stage order, you included. */
21
+ readonly order: readonly string[];
22
+ /** The person Spotlight shows big when nobody is pinned. */
23
+ readonly spot: string | null;
24
+ readonly pinned: string | null;
25
+ readonly content: StageContent | null;
26
+ };
27
+
28
+ type Placed = { readonly key: Slot | "more"; readonly frame: Frame; readonly more?: number };
29
+ type Rows = { readonly placed: readonly Placed[] };
30
+ type JustifyOptions = { readonly t: number; readonly minW: number; readonly minH: number };
31
+
32
+ const SOFT = [0.8, 2] as const;
33
+ const HARD = [0.6, 2.4] as const;
34
+ const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value));
35
+ const area = (frame: Frame) => frame.width * frame.height;
36
+
37
+ /** The mockup's `contain`: the largest box of `aspect` centred in `box`. */
38
+ export function contain(box: Frame, aspect: number | null): Frame {
39
+ if (!aspect) return box;
40
+ let width = box.width;
41
+ let height = width / aspect;
42
+ if (height > box.height) {
43
+ height = box.height;
44
+ width = height * aspect;
45
+ }
46
+ return { x: box.x + (box.width - width) / 2, y: box.y + (box.height - height) / 2, width, height };
47
+ }
48
+
49
+ /** The mockup's `jo()`: the target aspect and smallest tile per arrangement. */
50
+ function justifyOptions(portrait: boolean): JustifyOptions {
51
+ return portrait ? { t: 1, minW: 88, minH: 72 } : { t: 1.5, minW: 100, minH: 64 };
52
+ }
53
+
54
+ /**
55
+ * The mockup's `jplan`: pick the row count, spread `units` so rows differ by at most one, and
56
+ * score each plan by how far its tiles stray from the aspect bounds and the target aspect.
57
+ */
58
+ function justifyPlan(box: Frame, units: number, options: JustifyOptions, firstRowTwo: boolean) {
59
+ let best: { counts: number[]; height: number; cost: number; minWidth: number } | null = null;
60
+ for (let rows = 1; rows <= units; rows++) {
61
+ const base = Math.floor(units / rows);
62
+ const extra = units % rows;
63
+ if (!base) break;
64
+ const counts = Array.from({ length: rows }, (_, index) => base + (index < extra ? 1 : 0));
65
+ if (firstRowTwo && (counts[0] ?? 0) < 2) continue;
66
+ const height = (box.height - (rows - 1) * G) / rows;
67
+ if (height <= 0) break;
68
+ let cost = 0;
69
+ let minWidth = Infinity;
70
+ for (const count of counts) {
71
+ const width = (box.width - (count - 1) * G) / count;
72
+ const aspect = width / height;
73
+ minWidth = Math.min(minWidth, width);
74
+ const hard = aspect < HARD[0] ? Math.log(HARD[0] / aspect) : aspect > HARD[1] ? Math.log(aspect / HARD[1]) : 0;
75
+ const soft = aspect < SOFT[0] ? Math.log(SOFT[0] / aspect) : aspect > SOFT[1] ? Math.log(aspect / SOFT[1]) : 0;
76
+ cost += count * (hard * 60 + soft * 3 + Math.abs(Math.log(aspect / options.t)));
77
+ }
78
+ cost /= units;
79
+ if (!best || cost < best.cost) best = { counts, height, cost, minWidth };
80
+ }
81
+ return best;
82
+ }
83
+
84
+ /**
85
+ * The mockup's `jrows`: justified rows that fill the box with no gaps. Shared content takes a
86
+ * double tile. When tiles would get smaller than the minimum, the last one becomes "+N".
87
+ */
88
+ function justifiedRows(box: Frame, list: readonly Slot[], options: JustifyOptions): Rows {
89
+ const weight = (key: Slot | "more") => (key !== "more" && key.kind === "content" ? 2 : 1);
90
+ const contentFirst = list[0]?.kind === "content";
91
+ const total = list.length;
92
+ let keys: readonly (Slot | "more")[] = list;
93
+ let plan: ReturnType<typeof justifyPlan> = null;
94
+ for (let kept = total; kept >= 1; kept--) {
95
+ keys = kept === total ? list : [...list.slice(0, kept - 1), "more"];
96
+ plan = justifyPlan(
97
+ box,
98
+ keys.reduce((sum, key) => sum + weight(key), 0),
99
+ options,
100
+ contentFirst,
101
+ );
102
+ if ((plan && plan.minWidth >= options.minW && plan.height >= options.minH) || kept <= (contentFirst ? 2 : 1)) break;
103
+ }
104
+ if (!plan) return { placed: [] };
105
+ let height = plan.height;
106
+ for (const count of plan.counts) {
107
+ const width = (box.width - (count - 1) * G) / count;
108
+ if (width / height < HARD[0]) height = Math.min(height, width / HARD[0]);
109
+ }
110
+ const top = box.y + (box.height - plan.counts.length * height - (plan.counts.length - 1) * G) / 2;
111
+ const placed: Placed[] = [];
112
+ let index = 0;
113
+ plan.counts.forEach((count, row) => {
114
+ let unit = (box.width - (count - 1) * G) / count;
115
+ if (unit / height > HARD[1]) unit = height * HARD[1];
116
+ let x = box.x + (box.width - count * unit - (count - 1) * G) / 2;
117
+ let used = 0;
118
+ while (used < count && index < keys.length) {
119
+ const key = keys[index];
120
+ if (key === undefined) break;
121
+ const units = weight(key);
122
+ const frame = { x, y: top + row * (height + G), width: unit * units + G * (units - 1), height };
123
+ placed.push(key === "more" ? { key, frame, more: total - keys.length + 1 } : { key, frame });
124
+ x += frame.width + G;
125
+ used += units;
126
+ index++;
127
+ }
128
+ });
129
+ return { placed };
130
+ }
131
+
132
+ /** The mockup's `fitGrid` and `fill` for a single tile: the largest box within the aspect bounds, centred. */
133
+ function fillOne(box: Frame, aspect: { readonly min: number; readonly max: number; readonly cap: number }): Frame {
134
+ let width = box.width;
135
+ let height = box.height;
136
+ if (width / height > aspect.max) width = height * aspect.max;
137
+ else if (width / height < aspect.min) height = width / aspect.min;
138
+ if (aspect.cap && width > aspect.cap) {
139
+ height *= aspect.cap / width;
140
+ width = aspect.cap;
141
+ }
142
+ return { x: box.x + (box.width - width) / 2, y: box.y + (box.height - height) / 2, width, height };
143
+ }
144
+
145
+ /** Lays out the stage as the mockup's `layout()` does for a phone call. */
146
+ export function stageLayout(input: StageInput): readonly StageItem[] {
147
+ const { stage, portrait, content } = input;
148
+ if (input.layout === "grid") {
149
+ const rows = justifiedRows(stage, content ? [{ kind: "content" }, ...people(input.order)] : people(input.order), justifyOptions(portrait));
150
+ return toItems(rows.placed, content, { miniContent: true });
151
+ }
152
+ if (content && input.pinned !== null) return spotlightPeople(input, [{ kind: "content" }, ...people(input.order)]);
153
+ return content ? spotlightContent(input, content) : spotlightPeople(input, people(input.order));
154
+ }
155
+
156
+ function people(order: readonly string[]): Slot[] {
157
+ return order.map((id) => ({ kind: "person", id }));
158
+ }
159
+
160
+ /** The mockup's `spotContent`: the share gets the larger of below or beside, people fill the rest. */
161
+ function spotlightContent(input: StageInput, content: StageContent): readonly StageItem[] {
162
+ const { stage, portrait } = input;
163
+ const peopleMin = portrait ? (content.kind === "board" ? clamp(Math.round(stage.height * 0.17), 96, 120) : Math.round(stage.height * 0.3)) : clamp(stage.height * 0.17, 104, 150);
164
+ const contentMin = portrait ? 120 : clamp(stage.width * 0.15, 180, 260);
165
+ const belowBox = contain({ ...stage, height: stage.height - peopleMin - G }, content.aspect);
166
+ const below = { tile: { ...stage, height: belowBox.height }, rest: { x: stage.x, y: stage.y + belowBox.height + G, width: stage.width, height: stage.height - belowBox.height - G }, area: area(belowBox) };
167
+ const besideBox = contain({ ...stage, width: stage.width - contentMin - G }, content.aspect);
168
+ const beside = { tile: { ...stage, width: besideBox.width }, rest: { x: stage.x + besideBox.width + G, y: stage.y, width: stage.width - besideBox.width - G, height: stage.height }, area: area(besideBox) };
169
+ const best = beside.area > below.area * 1.005 ? beside : below;
170
+ const rows = justifiedRows(best.rest, people(input.order), justifyOptions(portrait));
171
+ const tile: StageItem = { kind: "content", frame: best.tile, inner: contain({ x: 0, y: 0, width: best.tile.width, height: best.tile.height }, content.aspect), mini: false };
172
+ return [tile, ...toItems(rows.placed, content, { miniContent: true })];
173
+ }
174
+
175
+ /** The mockup's `spotPeople`: one person big, everyone else in rows below (or beside, in landscape). */
176
+ function spotlightPeople(input: StageInput, list: readonly Slot[]): readonly StageItem[] {
177
+ const { stage, portrait, content, pinned, spot } = input;
178
+ const ids = list.flatMap((slot) => (slot.kind === "person" ? [slot.id] : []));
179
+ const main = content ? pinned : pinned !== null && ids.includes(pinned) ? pinned : spot !== null && ids.includes(spot) ? spot : (ids[0] ?? null);
180
+ if (main === null) return [];
181
+ const rest = list.filter((slot) => slot.kind !== "person" || slot.id !== main);
182
+ const first = rest[0];
183
+ if (!first) return [{ kind: "person", id: main, frame: fillOne(stage, { min: portrait ? 0.4 : 4 / 3, max: 16 / 9, cap: portrait ? 0 : stage.width * 0.56 }), big: false, float: false }];
184
+ if (rest.length === 1) {
185
+ const size = portrait ? { width: 96, height: 128 } : { width: 132, height: 96 };
186
+ const y = portrait ? stage.y + 12 : stage.y + stage.height - size.height - 12;
187
+ const floating = { x: stage.x + stage.width - size.width - 12, y, ...size };
188
+ return [{ kind: "person", id: main, frame: stage, big: false, float: false }, ...toItems([{ key: first, frame: floating }], content, { miniContent: true, float: true })];
189
+ }
190
+ const peopleMin = portrait ? Math.round(stage.height * 0.3) : clamp(stage.height * 0.17, 104, 150);
191
+ const sideMin = clamp(stage.width * 0.15, 180, 260);
192
+ const off = (frame: Frame) => Math.abs(Math.log(frame.width / frame.height / (16 / 9)));
193
+ const below = { main: { ...stage, height: stage.height - peopleMin - G }, rest: { x: stage.x, y: stage.y + stage.height - peopleMin, width: stage.width, height: peopleMin } };
194
+ const beside = { main: { ...stage, width: stage.width - sideMin - G }, rest: { x: stage.x + stage.width - sideMin, y: stage.y, width: sideMin, height: stage.height } };
195
+ const chosen = portrait || off(below.main) <= off(beside.main) ? below : beside;
196
+ return [{ kind: "person", id: main, frame: chosen.main, big: true, float: false }, ...toItems(justifiedRows(chosen.rest, rest, justifyOptions(portrait)).placed, content, { miniContent: true })];
197
+ }
198
+
199
+ function toItems(placed: readonly Placed[], content: StageContent | null, options: { readonly miniContent: boolean; readonly float?: boolean }): StageItem[] {
200
+ return placed.map(({ key, frame, more }): StageItem => {
201
+ if (key === "more") return { kind: "more", frame, count: more ?? 0 };
202
+ if (key.kind === "person") return { kind: "person", id: key.id, frame, big: false, float: options.float ?? false };
203
+ return { kind: "content", frame, inner: contain({ x: 0, y: 0, width: frame.width, height: frame.height }, content?.aspect ?? null), mini: options.miniContent };
204
+ });
205
+ }
206
+
207
+ export type StageOrder = { readonly order: readonly string[]; readonly spot: string | null };
208
+
209
+ /**
210
+ * Keeps the stage order stable as people come and go, as the mockup's `syncOrder` and
211
+ * `nextSpeaker` do: leavers drop out, newcomers join the end, and when someone else starts
212
+ * speaking they swap places with the person in the spotlight, so only those two tiles move.
213
+ */
214
+ export function nextStageOrder(previous: StageOrder, present: readonly string[], selfId: string | null, speaker: string | null): StageOrder {
215
+ const order = previous.order.filter((id) => present.includes(id));
216
+ for (const id of present) if (!order.includes(id)) order.push(id);
217
+ let spot = previous.spot !== null && present.includes(previous.spot) ? previous.spot : (present.find((id) => id !== selfId) ?? present[0] ?? null);
218
+ if (speaker !== null && speaker !== selfId && speaker !== spot && present.includes(speaker)) {
219
+ const from = spot === null ? -1 : order.indexOf(spot);
220
+ const to = order.indexOf(speaker);
221
+ if (from >= 0 && to >= 0) [order[from], order[to]] = [speaker, order[from] ?? speaker];
222
+ spot = speaker;
223
+ }
224
+ const unchanged = spot === previous.spot && order.length === previous.order.length && order.every((id, index) => id === previous.order[index]);
225
+ return unchanged ? previous : { order, spot };
226
+ }
@@ -0,0 +1,31 @@
1
+ import type { SpaceSnapshot } from "@q9labsai/chalk-client";
2
+ import { useCallback, useRef, useSyncExternalStore } from "react";
3
+ import { useSpaceClient } from "../context/space-client-context";
4
+
5
+ /**
6
+ * Reads one value from the call's snapshot and re-renders only when that value changes by
7
+ * `isEqual`, so one person's change re-renders only their own tile. `select` must be stable
8
+ * (wrap it in `useCallback`).
9
+ */
10
+ export function useSpaceSelector<T>(select: (snapshot: SpaceSnapshot) => T, isEqual: (a: T, b: T) => boolean = Object.is): T {
11
+ const client = useSpaceClient();
12
+ const cache = useRef<{ readonly snapshot: SpaceSnapshot; readonly select: (snapshot: SpaceSnapshot) => T; readonly value: T } | null>(null);
13
+ const subscribe = useCallback((listener: () => void) => client.subscribe(listener), [client]);
14
+ const getSnapshot = useCallback(() => {
15
+ const snapshot = client.getSnapshot();
16
+ const cached = cache.current;
17
+ if (cached && cached.snapshot === snapshot && cached.select === select) return cached.value;
18
+ const next = select(snapshot);
19
+ const value = cached && cached.select === select && isEqual(cached.value, next) ? cached.value : next;
20
+ cache.current = { snapshot, select, value };
21
+ return value;
22
+ }, [client, isEqual, select]);
23
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
24
+ }
25
+
26
+ /** Equal when both objects hold the same values under the same keys, compared with `Object.is`. */
27
+ export function shallowEqual<T extends object>(a: T, b: T): boolean {
28
+ const keys = Object.keys(a);
29
+ if (keys.length !== Object.keys(b).length) return false;
30
+ return keys.every((key) => Object.is(Reflect.get(a, key), Reflect.get(b, key)));
31
+ }
@@ -0,0 +1,68 @@
1
+ import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
2
+ import { useDevScenario } from "../frame/dev-scenario";
3
+
4
+ export type StageLayoutChoice = "spotlight" | "grid";
5
+
6
+ export type StageView = {
7
+ readonly layout: StageLayoutChoice;
8
+ readonly pinned: string | null;
9
+ /** Whether your own front-camera video is mirrored, as in a mirror. On by default. */
10
+ readonly mirror: boolean;
11
+ readonly setLayout: (layout: StageLayoutChoice) => void;
12
+ /** Pins a person, or unpins them if they're pinned. In Grid it switches to Spotlight first. */
13
+ readonly togglePin: (participantId: string) => void;
14
+ /** Spotlight with nobody pinned, so the shared screen or board takes the stage. */
15
+ readonly showContent: () => void;
16
+ readonly setMirror: (mirror: boolean) => void;
17
+ };
18
+
19
+ const StageViewContext = createContext<StageView | null>(null);
20
+
21
+ /**
22
+ * The person's choices about the stage: Spotlight or Grid, who's pinned, and the mirror. They
23
+ * live above the frame so More (the layout switch) and the stage share them.
24
+ */
25
+ export function StageViewProvider({ children }: { readonly children: ReactNode }): React.JSX.Element {
26
+ const [layout, setLayout] = useState<StageLayoutChoice>("spotlight");
27
+ const [pinned, setPinned] = useState<string | null>(null);
28
+ const [mirror, setMirror] = useState(true);
29
+
30
+ const dev = useDevScenario();
31
+ useEffect(() => {
32
+ if (dev.layout) setLayout(dev.layout);
33
+ if (dev.pinned) setPinned(dev.pinned);
34
+ }, [dev]);
35
+
36
+ const value = useMemo<StageView>(
37
+ () => ({
38
+ layout,
39
+ pinned,
40
+ mirror,
41
+ setLayout: (next) => {
42
+ setLayout(next);
43
+ if (next === "grid") setPinned(null);
44
+ },
45
+ togglePin: (participantId) => {
46
+ if (layout === "grid") {
47
+ setLayout("spotlight");
48
+ setPinned(participantId);
49
+ return;
50
+ }
51
+ setPinned((current) => (current === participantId ? null : participantId));
52
+ },
53
+ showContent: () => {
54
+ setLayout("spotlight");
55
+ setPinned(null);
56
+ },
57
+ setMirror,
58
+ }),
59
+ [layout, mirror, pinned],
60
+ );
61
+ return <StageViewContext.Provider value={value}>{children}</StageViewContext.Provider>;
62
+ }
63
+
64
+ export function useStageView(): StageView {
65
+ const view = useContext(StageViewContext);
66
+ if (!view) throw new Error("useStageView must be used inside <Chalk>.");
67
+ return view;
68
+ }
@@ -0,0 +1,47 @@
1
+ import { useEffect } from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import { useBackHandler } from "../frame/back-handler";
4
+ import { useChat, useRecording } from "../hooks/space-hooks";
5
+ import { Limbo, type LimboState } from "../limbo/Limbo";
6
+ import { Veil, type VeilLevel } from "../limbo/Veil";
7
+ import { useToasts } from "../primitives/toast/ToastStack";
8
+ import type { CallScreen } from "./call-screen";
9
+ import { StatusPage } from "./StatusPage";
10
+
11
+ export type CallOverlaysProps = {
12
+ readonly screen: CallScreen;
13
+ readonly callName: string;
14
+ /** The app restarted to rejoin, so the card says "Rejoining" instead of "Joining". */
15
+ readonly rejoining: boolean;
16
+ /** A phone call has the microphone (T6). */
17
+ readonly onHold: boolean;
18
+ readonly onRejoin: () => void;
19
+ readonly onHome: () => void;
20
+ };
21
+
22
+ /**
23
+ * The overlays layer: the veil and limbo card while the call joins, reconnects or waits on a phone
24
+ * call, and the status page once it's over. Android back on a status page goes Home.
25
+ */
26
+ export function CallOverlays({ screen, callName, rejoining, onHold, onRejoin, onHome }: CallOverlaysProps): React.JSX.Element {
27
+ const recording = useRecording().current?.status === "recording";
28
+ const queuedMessages = useChat().pendingSends.filter((send) => send.status === "queued").length;
29
+ useBackHandler(screen.kind === "status", onHome);
30
+ // Notices belong to the live call; a status page or a fresh join starts without them.
31
+ const { clear } = useToasts();
32
+ const clearsNotices = screen.kind === "status" || screen.kind === "joining";
33
+ useEffect(() => {
34
+ if (clearsNotices) clear();
35
+ }, [clear, clearsNotices]);
36
+
37
+ const limbo: LimboState | null = screen.kind === "joining" ? { kind: "joining", rejoining } : screen.kind === "reconnecting" ? { kind: "reconnecting", queuedMessages } : screen.kind === "call" && onHold ? { kind: "hold" } : null;
38
+ const veil: VeilLevel = limbo === null ? "none" : limbo.kind === "joining" ? "limbo" : "paused";
39
+
40
+ return (
41
+ <View pointerEvents="box-none" style={StyleSheet.absoluteFill}>
42
+ <Veil level={screen.kind === "status" ? "none" : veil} />
43
+ {limbo ? <Limbo callName={callName} recording={recording} state={limbo} /> : null}
44
+ {screen.kind === "status" ? <StatusPage callName={callName} onHome={onHome} onRejoin={onRejoin} outcome={screen.outcome} /> : null}
45
+ </View>
46
+ );
47
+ }
@@ -0,0 +1,53 @@
1
+ import { StyleSheet, View } from "react-native";
2
+ import { SafeAreaProvider } from "react-native-safe-area-context";
3
+ import { useBackHandler } from "../frame/back-handler";
4
+ import { Limbo } from "../limbo/Limbo";
5
+ import { ThemeProvider, useTheme } from "../theme/theme";
6
+ import type { ColorScheme } from "../theme/tokens";
7
+ import { StatusPage } from "./StatusPage";
8
+
9
+ /** Where a person stands before the call lets them in: waiting for the host, turned away, or the call is full. */
10
+ export type ArrivalState = "knocking" | "refused" | "full";
11
+
12
+ export type ChalkArrivalProps = {
13
+ readonly state: ArrivalState;
14
+ /** The call's name, such as "Q9 team". */
15
+ readonly callName: string;
16
+ /** Dark by default, like `Chalk`. */
17
+ readonly colorScheme?: ColorScheme;
18
+ /** Stops waiting (Cancel while knocking, Android back). */
19
+ readonly onCancel: () => void;
20
+ /** Knocks again after a refusal, or tries again once a full call has room. */
21
+ readonly onRetry: () => void;
22
+ /** Recording consent shows while knocking on a recorded call. */
23
+ readonly recording?: boolean;
24
+ };
25
+
26
+ /**
27
+ * The screens before `Chalk` joins (T3, T4, A7), for an app that lets a person ask to join a call
28
+ * through an invite link: "Asking to join" with Cancel, "The host didn’t let you in" with Ask again,
29
+ * and "This call is full" with Try again and Home.
30
+ */
31
+ export function ChalkArrival({ colorScheme = "dark", ...props }: ChalkArrivalProps): React.JSX.Element {
32
+ return (
33
+ <SafeAreaProvider>
34
+ <ThemeProvider colorScheme={colorScheme}>
35
+ <ArrivalScreen {...props} />
36
+ </ThemeProvider>
37
+ </SafeAreaProvider>
38
+ );
39
+ }
40
+
41
+ export function ArrivalScreen({ state, callName, onCancel, onRetry, recording = false }: Omit<ChalkArrivalProps, "colorScheme">): React.JSX.Element {
42
+ const { colors } = useTheme();
43
+ useBackHandler(true, onCancel);
44
+ return (
45
+ <View style={[styles.screen, { backgroundColor: colors.canvas }]} testID="arrival.screen">
46
+ {state === "full" ? <StatusPage callName={callName} onHome={onCancel} onRejoin={onRetry} outcome="full" /> : <Limbo callName={callName} recording={recording} state={state === "knocking" ? { kind: "knocking", onCancel } : { kind: "refused", onAskAgain: onRetry }} />}
47
+ </View>
48
+ );
49
+ }
50
+
51
+ const styles = StyleSheet.create({
52
+ screen: { flex: 1 },
53
+ });
@@ -0,0 +1,123 @@
1
+ import { forwardRef, memo, useEffect, useRef } from "react";
2
+ import { AccessibilityInfo, findNodeHandle, Pressable, StyleSheet, View } from "react-native";
3
+ import Animated, { useAnimatedStyle } from "react-native-reanimated";
4
+ import { useMeasuredRef } from "../frame/measurement";
5
+ import { Icon, type IconName } from "../icons/Icon";
6
+ import { usePressAnimation } from "../motion/press";
7
+ import { ChalkText } from "../primitives/ChalkText";
8
+ import { useTheme } from "../theme/theme";
9
+ import { fontFamilies } from "../theme/tokens";
10
+
11
+ /** The ways a call ends for you (A3 to A7). */
12
+ export type CallOutcome = "left" | "ended" | "failed" | "removed" | "exited" | "full";
13
+
14
+ export type StatusPageProps = {
15
+ readonly outcome: CallOutcome;
16
+ readonly callName: string;
17
+ readonly onRejoin: () => void;
18
+ readonly onHome: () => void;
19
+ };
20
+
21
+ type Action = "rejoin" | "home";
22
+
23
+ type Page = { readonly title: string; readonly detail?: string; readonly actions: readonly Action[]; readonly rejoinLabel?: string };
24
+
25
+ /** The mockup's copy for each outcome (`SP` in renderLimbo). */
26
+ function pageFor(outcome: CallOutcome, callName: string): Page {
27
+ switch (outcome) {
28
+ case "left":
29
+ return { title: `You left ${callName}`, actions: ["rejoin", "home"] };
30
+ case "ended":
31
+ return { title: "The call ended", actions: ["rejoin", "home"] };
32
+ case "failed":
33
+ return { title: `Couldn’t connect to ${callName}`, detail: "Check your internet connection, then try again.", actions: ["rejoin", "home"], rejoinLabel: "Try again" };
34
+ case "removed":
35
+ return { title: `You were removed from ${callName}`, detail: "The host removed you from this call.", actions: ["home"] };
36
+ case "exited":
37
+ return { title: `You’re no longer in ${callName}`, actions: ["rejoin", "home"] };
38
+ case "full":
39
+ return { title: "This call is full", detail: `${callName} has reached 100 people. Ask the host to make room, then try again.`, actions: ["rejoin", "home"], rejoinLabel: "Try again" };
40
+ }
41
+ }
42
+
43
+ /**
44
+ * A full-screen status page on the canvas (`.status`): a title, an optional line saying what to do,
45
+ * and the ways forward as round actions with their names under them.
46
+ */
47
+ export function StatusPage({ outcome, callName, onRejoin, onHome }: StatusPageProps): React.JSX.Element {
48
+ const { colors } = useTheme();
49
+ const page = pageFor(outcome, callName);
50
+ const pageStyle = [styles.page, { backgroundColor: colors.canvas }];
51
+ const ref = useMeasuredRef<View>("status.page", pageStyle);
52
+ const firstAction = useRef<View>(null);
53
+ // The mockup moves focus to the first action when the page appears.
54
+ useEffect(() => {
55
+ const node = firstAction.current && findNodeHandle(firstAction.current);
56
+ if (node) AccessibilityInfo.setAccessibilityFocus(node);
57
+ }, [outcome]);
58
+ return (
59
+ <View accessibilityViewIsModal ref={ref} style={pageStyle} testID="status.page">
60
+ <ChalkText accessibilityRole="header" style={styles.center} testID="status.title" variant="statusTitle">
61
+ {page.title}
62
+ </ChalkText>
63
+ {page.detail ? (
64
+ <ChalkText style={styles.center} testID="status.detail" tone="textMuted">
65
+ {page.detail}
66
+ </ChalkText>
67
+ ) : null}
68
+ <View style={styles.actions}>
69
+ {page.actions.map((action, index) =>
70
+ action === "rejoin" ? (
71
+ <StatusAction icon="retry" key={action} label={page.rejoinLabel ?? "Rejoin"} onPress={onRejoin} ref={index === 0 ? firstAction : undefined} testID="status.rejoin" />
72
+ ) : (
73
+ <StatusAction icon="home" key={action} label="Home" onPress={onHome} ref={index === 0 ? firstAction : undefined} testID="status.home" />
74
+ ),
75
+ )}
76
+ </View>
77
+ </View>
78
+ );
79
+ }
80
+
81
+ type StatusActionProps = {
82
+ readonly icon: IconName;
83
+ readonly label: string;
84
+ readonly onPress: () => void;
85
+ readonly testID: string;
86
+ };
87
+
88
+ /** `.act`: a 56 pt round button with its name under it; the round part presses like `.rb`. */
89
+ const StatusAction = memo(
90
+ forwardRef<View, StatusActionProps>(function StatusAction({ icon, label, onPress, testID }, ref) {
91
+ const { colors } = useTheme();
92
+ const press = usePressAnimation("round");
93
+ const { down } = press;
94
+ const roundStyle = [styles.round, { backgroundColor: colors.raised }];
95
+ const roundRef = useMeasuredRef<View>(`${testID}.button`, roundStyle);
96
+ const raised = colors.raised;
97
+ const pressed = colors.pressed;
98
+ const background = useAnimatedStyle(() => {
99
+ "worklet";
100
+ return { backgroundColor: down.value ? pressed : raised };
101
+ });
102
+ return (
103
+ <Pressable accessibilityLabel={label} accessibilityRole="button" onPress={onPress} onPressIn={press.onPressIn} onPressOut={press.onPressOut} ref={ref} style={styles.action} testID={testID}>
104
+ <Animated.View ref={roundRef} style={[roundStyle, background, press.scaleStyle]}>
105
+ <Icon color={colors.text} name={icon} />
106
+ </Animated.View>
107
+ <ChalkText style={styles.actionLabel} testID={`${testID}.label`} tone="textSoft" variant="meta">
108
+ {label}
109
+ </ChalkText>
110
+ </Pressable>
111
+ );
112
+ }),
113
+ );
114
+
115
+ const styles = StyleSheet.create({
116
+ page: { ...StyleSheet.absoluteFillObject, alignItems: "center", justifyContent: "center", gap: 12, padding: 24 },
117
+ center: { textAlign: "center" },
118
+ actions: { flexDirection: "row", gap: 28, marginTop: 20 },
119
+ action: { alignItems: "center", gap: 8 },
120
+ round: { width: 56, height: 56, borderRadius: 28, alignItems: "center", justifyContent: "center" },
121
+ // `.act` is 13 pt at weight 500; the type scale has 13 pt only at 400 and 600.
122
+ actionLabel: { fontFamily: fontFamilies[500] },
123
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { callScreen } from "./call-screen";
3
+
4
+ type ConnectionOptions = { readonly code?: "episode.ended" | "access.invalid"; readonly episodeEnded?: boolean; readonly removedByHost?: boolean };
5
+
6
+ const connection = (status: Parameters<typeof callScreen>[0]["status"], { code, episodeEnded = false, removedByHost = false }: ConnectionOptions = {}) => ({
7
+ status,
8
+ episode: null,
9
+ lastError: code ? { code, recoverable: false, message: "" } : null,
10
+ episodeEnded,
11
+ removedByHost,
12
+ });
13
+
14
+ describe("callScreen", () => {
15
+ it("covers the call while it joins or reconnects", () => {
16
+ expect(callScreen(connection("joining"), false)).toEqual({ kind: "joining" });
17
+ expect(callScreen(connection("reconnecting"), false)).toEqual({ kind: "reconnecting" });
18
+ expect(callScreen(connection("live"), false)).toEqual({ kind: "call" });
19
+ });
20
+
21
+ it("shows the person's own leave as left, even when the leave call failed", () => {
22
+ expect(callScreen(connection("left"), true)).toEqual({ kind: "status", outcome: "left" });
23
+ expect(callScreen(connection("left", { removedByHost: true }), true)).toEqual({ kind: "status", outcome: "left" });
24
+ });
25
+
26
+ it("shows removed only when the server reported a host removal", () => {
27
+ expect(callScreen(connection("left", { removedByHost: true }), false)).toEqual({ kind: "status", outcome: "removed" });
28
+ });
29
+
30
+ it("shows a neutral exit for any other exit nobody asked for", () => {
31
+ expect(callScreen(connection("left"), false)).toEqual({ kind: "status", outcome: "exited" });
32
+ });
33
+
34
+ it("shows the call ended when the Episode ends while the person is in it", () => {
35
+ expect(callScreen(connection("left", { episodeEnded: true }), false)).toEqual({ kind: "status", outcome: "ended" });
36
+ expect(callScreen(connection("left", { code: "episode.ended" }), false)).toEqual({ kind: "status", outcome: "ended" });
37
+ });
38
+
39
+ it("shows a failure as failed", () => {
40
+ expect(callScreen(connection("failed", { code: "access.invalid" }), false)).toEqual({ kind: "status", outcome: "failed" });
41
+ });
42
+ });
@@ -0,0 +1,29 @@
1
+ import type { ConnectionSlice } from "@q9labsai/chalk-client";
2
+ import type { CallOutcome } from "./StatusPage";
3
+
4
+ /** What covers the call: nothing, a limbo card over the veiled call, or a status page. */
5
+ export type CallScreen = { readonly kind: "call" } | { readonly kind: "joining" } | { readonly kind: "reconnecting" } | { readonly kind: "status"; readonly outcome: CallOutcome };
6
+
7
+ /**
8
+ * Reads the screen from the connection. `leftByChoice` is true from the moment the person presses
9
+ * Leave or End until they join again, so a leave whose call failed still reads as their choice.
10
+ * Only the server's own removal signal shows "removed"; any other exit nobody chose is `exited`.
11
+ */
12
+ export function callScreen(connection: ConnectionSlice, leftByChoice: boolean): CallScreen {
13
+ if (connection.episodeEnded || connection.lastError?.code === "episode.ended") return { kind: "status", outcome: "ended" };
14
+ switch (connection.status) {
15
+ case "idle":
16
+ case "joining":
17
+ return { kind: "joining" };
18
+ case "reconnecting":
19
+ return { kind: "reconnecting" };
20
+ case "live":
21
+ case "leaving":
22
+ return { kind: "call" };
23
+ case "left":
24
+ if (leftByChoice) return { kind: "status", outcome: "left" };
25
+ return { kind: "status", outcome: connection.removedByHost ? "removed" : "exited" };
26
+ case "failed":
27
+ return { kind: "status", outcome: "failed" };
28
+ }
29
+ }
@@ -0,0 +1,28 @@
1
+ import { createContext, useContext, type ReactNode } from "react";
2
+ import { elevations, palettes, type ColorScheme, type Elevation, type Palette } from "./tokens";
3
+
4
+ export type Theme = {
5
+ readonly scheme: ColorScheme;
6
+ readonly colors: Palette;
7
+ readonly elevation: Elevation;
8
+ };
9
+
10
+ const themes: Readonly<Record<ColorScheme, Theme>> = {
11
+ dark: { scheme: "dark", colors: palettes.dark, elevation: elevations.dark },
12
+ light: { scheme: "light", colors: palettes.light, elevation: elevations.light },
13
+ };
14
+
15
+ const ThemeContext = createContext<Theme>(themes.dark);
16
+
17
+ /** Supplies one of two frozen theme objects, so consumers re-render only when the scheme changes. */
18
+ export function ThemeProvider({ colorScheme, children }: { readonly colorScheme: ColorScheme; readonly children: ReactNode }): React.JSX.Element {
19
+ return <ThemeContext.Provider value={themes[colorScheme]}>{children}</ThemeContext.Provider>;
20
+ }
21
+
22
+ export function useTheme(): Theme {
23
+ return useContext(ThemeContext);
24
+ }
25
+
26
+ export function themeFor(colorScheme: ColorScheme): Theme {
27
+ return themes[colorScheme];
28
+ }