@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,182 @@
1
+ import { CHALK_CHAT_ATTACHMENT_MIME_TYPES, type ChatAttachment, type ChatUploadFile } from "@q9labsai/chalk-client";
2
+ import { getDocumentAsync } from "expo-document-picker";
3
+ import { File } from "expo-file-system";
4
+ import { useCallback, useRef, useState } from "react";
5
+ import { ActivityIndicator, Image, Pressable, ScrollView, StyleSheet, View } from "react-native";
6
+ import Animated, { useAnimatedKeyboard, useAnimatedStyle } from "react-native-reanimated";
7
+ import { useSpaceClient } from "../../context/space-client-context";
8
+ import { useDeviceLayout } from "../../frame/device-layout";
9
+ import { Icon } from "../../icons/Icon";
10
+ import { usePressAnimation } from "../../motion/press";
11
+ import { announce } from "../../primitives/announce";
12
+ import { ChalkText } from "../../primitives/ChalkText";
13
+ import { IconButton } from "../../primitives/IconButton";
14
+ import { useToasts } from "../../primitives/toast/ToastStack";
15
+ import { useTheme } from "../../theme/theme";
16
+ import { chatOverlays, fontFamilies } from "../../theme/tokens";
17
+ import { FileBadge } from "./attachments";
18
+ import { fileKind, formatSize, mimeTypeOf, stagingError, type StagedFile } from "./chat-model";
19
+ import { TextField } from "./TextField";
20
+
21
+ /**
22
+ * The composer (`.comp`): files waiting to be sent, then the field with Attach and Send. It pads
23
+ * itself for the home indicator, and only by 8 pt while the keyboard is up, since the sheet then
24
+ * rides on the keyboard.
25
+ */
26
+ export function Composer(): React.JSX.Element {
27
+ const client = useSpaceClient();
28
+ const toasts = useToasts();
29
+ const { colors } = useTheme();
30
+ const layout = useDeviceLayout();
31
+ const keyboard = useAnimatedKeyboard();
32
+ const [text, setText] = useState("");
33
+ const [focused, setFocused] = useState(false);
34
+ const [staged, setStaged] = useState<readonly StagedFile[]>([]);
35
+ const nextId = useRef(0);
36
+
37
+ const update = useCallback((id: number, change: Partial<StagedFile>) => setStaged((files) => files.map((file) => (file.id === id ? { ...file, ...change } : file))), []);
38
+
39
+ const upload = useCallback(
40
+ (file: StagedFile) => {
41
+ client.chat.files
42
+ .upload(file.source)
43
+ .then((attachment) => update(file.id, { state: "ready", attachment }))
44
+ .catch((cause: unknown) => update(file.id, { state: "error", error: cause instanceof Error ? cause.message : "Couldn’t upload" }));
45
+ },
46
+ [client, update],
47
+ );
48
+
49
+ const attach = useCallback(async () => {
50
+ const picked = await getDocumentAsync({ multiple: true, copyToCacheDirectory: true, type: [...CHALK_CHAT_ATTACHMENT_MIME_TYPES] });
51
+ if (picked.canceled) return;
52
+ let accepted = staged.filter((file) => file.state !== "error").length;
53
+ const added: StagedFile[] = [];
54
+ for (const asset of picked.assets) {
55
+ const mimeType = mimeTypeOf(asset.name, asset.mimeType);
56
+ const size = asset.size ?? 0;
57
+ const source: ChatUploadFile = { name: asset.name, type: mimeType, size, arrayBuffer: () => new File(asset.uri).arrayBuffer() };
58
+ const error = stagingError({ name: asset.name, mimeType, size }, accepted);
59
+ if (!error) accepted += 1;
60
+ nextId.current += 1;
61
+ added.push({ id: nextId.current, name: asset.name, size, mimeType, source, previewUri: fileKind(mimeType) === "image" ? asset.uri : null, state: error ? "error" : "uploading", attachment: null, error });
62
+ }
63
+ setStaged((files) => [...files, ...added]);
64
+ added.filter((file) => file.state === "uploading").forEach(upload);
65
+ const refused = added.filter((file) => file.state === "error").length;
66
+ const ok = added.length - refused;
67
+ announce(`${ok} file${ok === 1 ? "" : "s"} added${refused ? `, ${refused} can’t be sent` : ""}`);
68
+ }, [staged, upload]);
69
+
70
+ const pick = useCallback(() => {
71
+ attach().catch(() => toasts.show({ kind: "error", dismissible: true, message: "Couldn't open your files." }));
72
+ }, [attach, toasts]);
73
+
74
+ const ready = staged.flatMap((file) => (file.state === "ready" && file.attachment ? [file.attachment] : []));
75
+ const uploading = staged.some((file) => file.state === "uploading");
76
+ const message = text.trim();
77
+ const canSend = !uploading && (message.length > 0 || ready.length > 0);
78
+
79
+ const send = useCallback(() => {
80
+ if (!canSend) return;
81
+ const attachments: readonly ChatAttachment[] = ready;
82
+ setText("");
83
+ setStaged([]);
84
+ // A send that fails stays in the list as "Couldn’t send" with Retry, so nothing more is needed here.
85
+ client.chat.send({ text: message, attachments }).catch(() => undefined);
86
+ }, [canSend, client, message, ready]);
87
+
88
+ const padStyle = useAnimatedStyle(() => {
89
+ "worklet";
90
+ return { paddingBottom: keyboard.height.value > 0 ? 8 : layout.insets.bottom + 8 };
91
+ });
92
+
93
+ return (
94
+ <Animated.View style={[styles.composer, padStyle]} testID="chat.composer">
95
+ {staged.length > 0 ? (
96
+ <ScrollView accessibilityLabel="Files to send" contentContainerStyle={styles.stagedList} style={styles.staged} testID="chat.staged">
97
+ {staged.map((file) => (
98
+ <StagedChip file={file} key={file.id} onRemove={() => setStaged((files) => files.filter((other) => other.id !== file.id))} />
99
+ ))}
100
+ </ScrollView>
101
+ ) : null}
102
+ <View style={[styles.field, { backgroundColor: colors.raised, borderColor: focused ? colors.text : colors.line }]}>
103
+ <View style={styles.clip}>
104
+ <IconButton accessibilityLabel="Attach files" icon="clip" onPress={pick} size={36} testID="chat.attach" tone="ghost" />
105
+ </View>
106
+ <TextField
107
+ accessibilityLabel="Message everyone"
108
+ blurOnSubmit={false}
109
+ onBlur={() => setFocused(false)}
110
+ onChangeText={setText}
111
+ onFocus={() => setFocused(true)}
112
+ onSubmitEditing={send}
113
+ placeholder="Message everyone"
114
+ returnKeyType="send"
115
+ submitBehavior="submit"
116
+ testID="chat.input"
117
+ value={text}
118
+ />
119
+ <SendButton disabled={!canSend} onPress={send} />
120
+ </View>
121
+ </Animated.View>
122
+ );
123
+ }
124
+
125
+ function SendButton({ disabled, onPress }: { readonly disabled: boolean; readonly onPress: () => void }): React.JSX.Element {
126
+ const { colors } = useTheme();
127
+ const press = usePressAnimation("round");
128
+ return (
129
+ <Pressable accessibilityLabel="Send message" accessibilityRole="button" accessibilityState={{ disabled }} disabled={disabled} onPress={onPress} onPressIn={press.onPressIn} onPressOut={press.onPressOut} testID="chat.send">
130
+ <Animated.View style={[styles.send, { backgroundColor: disabled ? colors.hover : colors.text }, disabled ? null : press.scaleStyle]}>
131
+ <Icon color={disabled ? colors.textMuted : colors.canvas} name="up" size={18} />
132
+ </Animated.View>
133
+ </Pressable>
134
+ );
135
+ }
136
+
137
+ /** A file waiting to be sent (`.chip`): its preview, name and size or what's wrong, and Remove. */
138
+ function StagedChip({ file, onRemove }: { readonly file: StagedFile; readonly onRemove: () => void }): React.JSX.Element {
139
+ const { colors } = useTheme();
140
+ const failed = file.state === "error";
141
+ const uploading = file.state === "uploading";
142
+ return (
143
+ <View accessible={false} style={[styles.chip, { backgroundColor: colors.raised, borderColor: failed ? colors.danger : colors.line }]} testID={`chat.staged.${file.id}`}>
144
+ <View style={[styles.chipPreview, failed ? styles.faded : null]}>
145
+ {file.previewUri && !failed ? <Image source={{ uri: file.previewUri }} style={styles.chipImage} /> : <FileBadge mimeType={file.mimeType} size={32} />}
146
+ {uploading ? (
147
+ <View style={[StyleSheet.absoluteFill, styles.chipShade]}>
148
+ <ActivityIndicator color={chatOverlays.ring} size="small" />
149
+ </View>
150
+ ) : null}
151
+ </View>
152
+ <View style={styles.chipText}>
153
+ <ChalkText numberOfLines={1} variant="button">
154
+ {file.name}
155
+ </ChalkText>
156
+ <ChalkText color={failed ? colors.dangerText : undefined} style={styles.chipDetail} tone="textMuted" variant="label">
157
+ {failed ? file.error : uploading ? `Uploading, ${formatSize(file.size)}` : formatSize(file.size)}
158
+ </ChalkText>
159
+ </View>
160
+ <Pressable accessibilityLabel={`Remove ${file.name}`} accessibilityRole="button" hitSlop={8} onPress={onRemove} style={({ pressed }) => [styles.remove, pressed ? { backgroundColor: colors.hover } : null]} testID={`chat.staged.remove.${file.id}`}>
161
+ <Icon color={colors.textMuted} name="x" size={16} />
162
+ </Pressable>
163
+ </View>
164
+ );
165
+ }
166
+
167
+ const styles = StyleSheet.create({
168
+ composer: { paddingTop: 8, paddingHorizontal: 12 },
169
+ staged: { maxHeight: 196 },
170
+ stagedList: { gap: 4, paddingBottom: 8 },
171
+ field: { flexDirection: "row", alignItems: "center", height: 44, paddingHorizontal: 4, borderRadius: 999, borderWidth: 1 },
172
+ clip: { marginRight: 2 },
173
+ send: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center" },
174
+ chip: { minHeight: 44, flexDirection: "row", alignItems: "center", gap: 10, paddingTop: 4, paddingRight: 4, paddingBottom: 4, paddingLeft: 6, borderRadius: 12, borderWidth: 1 },
175
+ chipPreview: { width: 32, height: 32 },
176
+ chipImage: { width: 32, height: 32, borderRadius: 7 },
177
+ chipShade: { borderRadius: 7, backgroundColor: chatOverlays.uploading, alignItems: "center", justifyContent: "center" },
178
+ faded: { opacity: 0.5 },
179
+ chipText: { flex: 1, minWidth: 0, gap: 1 },
180
+ chipDetail: { fontFamily: fontFamilies[400], lineHeight: 15 },
181
+ remove: { width: 28, height: 28, borderRadius: 14, alignItems: "center", justifyContent: "center" },
182
+ });
@@ -0,0 +1,100 @@
1
+ import { useEffect, type RefObject } from "react";
2
+ import { Pressable, StyleSheet, View } from "react-native";
3
+ import { useSpaceClient } from "../../context/space-client-context";
4
+ import { useChat, useParticipants } from "../../hooks/space-hooks";
5
+ import { BottomSheet, SheetDragRegion } from "../../primitives/BottomSheet";
6
+ import { ChalkText } from "../../primitives/ChalkText";
7
+ import { IconButton } from "../../primitives/IconButton";
8
+ import { useTheme } from "../../theme/theme";
9
+ import { ChatTab } from "./ChatTab";
10
+ import { PeopleTab } from "./PeopleTab";
11
+
12
+ export type PeopleChatTab = "people" | "chat";
13
+
14
+ export type PeopleChatSheetProps = {
15
+ readonly open: boolean;
16
+ readonly tab: PeopleChatTab;
17
+ readonly onTabChange: (tab: PeopleChatTab) => void;
18
+ readonly onClose: () => void;
19
+ /** Where accessibility focus returns when the sheet closes. */
20
+ readonly returnFocusRef?: RefObject<View | null>;
21
+ };
22
+
23
+ /**
24
+ * People and Chat share one card (`.card`): a 78 % sheet whose tab row switches between who is in
25
+ * the call and the call's chat. The tab row drags the sheet like the grabber does.
26
+ */
27
+ export function PeopleChatSheet({ open, tab, onTabChange, onClose, returnFocusRef }: PeopleChatSheetProps): React.JSX.Element {
28
+ const { colors } = useTheme();
29
+ const { roster } = useParticipants();
30
+ const chat = useChat();
31
+ useMarkChatRead(open && tab === "chat");
32
+ const unread = chat.unreadCount > 0 && tab !== "chat";
33
+ return (
34
+ <BottomSheet accessibilityLabel="People and chat" heightRatio={0.78} onClose={onClose} open={open} returnFocusRef={returnFocusRef} testID="sheet.people" variant="card">
35
+ <SheetDragRegion>
36
+ <View accessibilityRole="tablist" style={[styles.tabs, { borderBottomColor: colors.line }]}>
37
+ <Tab label="People" count={roster.length} onPress={() => onTabChange("people")} selected={tab === "people"} testID="sheet.tab.people" />
38
+ <Tab label="Chat" onPress={() => onTabChange("chat")} selected={tab === "chat"} testID="sheet.tab.chat" unread={unread} />
39
+ <View style={styles.close}>
40
+ <IconButton accessibilityLabel="Close" icon="x" onPress={onClose} size={44} testID="sheet.people.close" tone="ghost" />
41
+ </View>
42
+ </View>
43
+ </SheetDragRegion>
44
+ {tab === "people" ? <PeopleTab /> : <ChatTab />}
45
+ </BottomSheet>
46
+ );
47
+ }
48
+
49
+ type TabProps = {
50
+ readonly label: string;
51
+ readonly selected: boolean;
52
+ readonly onPress: () => void;
53
+ readonly count?: number;
54
+ readonly unread?: boolean;
55
+ readonly testID: string;
56
+ };
57
+
58
+ function Tab({ label, selected, onPress, count, unread = false, testID }: TabProps): React.JSX.Element {
59
+ const { colors } = useTheme();
60
+ const accessibilityLabel = [count === undefined ? label : `${label}, ${count}`, unread ? "new messages" : null].filter(Boolean).join(", ");
61
+ return (
62
+ <Pressable accessibilityLabel={accessibilityLabel} accessibilityRole="tab" accessibilityState={{ selected }} onPress={onPress} style={styles.tab} testID={testID}>
63
+ <View style={styles.tabLabel}>
64
+ <ChalkText tone={selected ? "text" : "textMuted"} variant="bodyStrong">
65
+ {label}
66
+ </ChalkText>
67
+ {count === undefined ? null : (
68
+ <ChalkText style={styles.count} tabular tone="textMuted" variant="bodyMedium">
69
+ {count}
70
+ </ChalkText>
71
+ )}
72
+ </View>
73
+ {selected ? <View style={[styles.underline, { backgroundColor: colors.text }]} /> : null}
74
+ {unread ? <View style={[styles.unread, { backgroundColor: colors.info }]} testID={`${testID}.unread`} /> : null}
75
+ </Pressable>
76
+ );
77
+ }
78
+
79
+ /** While the Chat tab is in view, tells the server you've read up to the newest message. */
80
+ function useMarkChatRead(reading: boolean): void {
81
+ const client = useSpaceClient();
82
+ const { messages, unreadCount } = useChat();
83
+ const last = messages[messages.length - 1]?.messageId;
84
+ useEffect(() => {
85
+ if (!reading || !last || unreadCount === 0) return;
86
+ // A lost read receipt only leaves the unread dot up; the next message marks it again.
87
+ client.chat.markRead(last).catch(() => undefined);
88
+ }, [client, last, reading, unreadCount]);
89
+ }
90
+
91
+ const styles = StyleSheet.create({
92
+ // `.tabs`: 4 pt above, 8 right, 10 left, a hairline under it.
93
+ tabs: { flexDirection: "row", alignItems: "center", gap: 4, paddingTop: 4, paddingRight: 8, paddingLeft: 10, borderBottomWidth: 1 },
94
+ tab: { height: 46, justifyContent: "center", paddingHorizontal: 10 },
95
+ tabLabel: { flexDirection: "row", alignItems: "center" },
96
+ count: { marginLeft: 5 },
97
+ underline: { position: "absolute", left: 10, right: 10, bottom: -1, height: 2, borderRadius: 2 },
98
+ unread: { position: "absolute", right: 0, top: 13, width: 7, height: 7, borderRadius: 4 },
99
+ close: { marginLeft: "auto" },
100
+ });
@@ -0,0 +1,326 @@
1
+ import type { AdmissionRequest, Participant } from "@q9labsai/chalk-client";
2
+ import { memo, useCallback, useMemo, useRef, useState, type RefObject } from "react";
3
+ import { FlatList, Pressable, StyleSheet, View, type ListRenderItemInfo } from "react-native";
4
+ import Animated from "react-native-reanimated";
5
+ import { FaceAvatar } from "../../avatar/FaceAvatar";
6
+ import { useSpaceClient } from "../../context/space-client-context";
7
+ import { Icon } from "../../icons/Icon";
8
+ import { usePressAnimation } from "../../motion/press";
9
+ import { useCan, useParticipants, useSelf } from "../../hooks/space-hooks";
10
+ import { ChalkText } from "../../primitives/ChalkText";
11
+ import { IconButton } from "../../primitives/IconButton";
12
+ import { Menu, MenuItem } from "../../primitives/Menu";
13
+ import { PillButton } from "../../primitives/PillButton";
14
+ import { useToasts } from "../../primitives/toast/ToastStack";
15
+ import { useTheme } from "../../theme/theme";
16
+ import { fixed } from "../../theme/tokens";
17
+ import { nextHandOrder, peopleRows, type PeopleRow } from "./people-model";
18
+ import { TextField } from "./TextField";
19
+
20
+ /** Past this many people the list gets a search field. */
21
+ const SEARCH_FROM = 9;
22
+
23
+ /** The People tab: who is waiting, raised hands, and everyone in the call, with the host's actions. */
24
+ export function PeopleTab(): React.JSX.Element {
25
+ const client = useSpaceClient();
26
+ const toasts = useToasts();
27
+ const { colors } = useTheme();
28
+ const { roster, admissionQueue } = useParticipants();
29
+ const self = useSelf();
30
+ const canAdmit = useCan("manageAdmission");
31
+ const canMute = useCan("muteOthers");
32
+ const canRemove = useCan("removeParticipant");
33
+ const canRename = useCan("renameSelf");
34
+ const [query, setQuery] = useState("");
35
+ const [confirmMuteAll, setConfirmMuteAll] = useState(false);
36
+ const muteAllRef = useRef<View>(null);
37
+
38
+ const handOrder = useRef<readonly string[]>([]);
39
+ handOrder.current = useMemo(() => nextHandOrder(handOrder.current, roster), [roster]);
40
+ const waiting = canAdmit ? admissionQueue : NONE;
41
+ const rows = useMemo(() => peopleRows({ roster, waiting, handOrder: handOrder.current, query }), [roster, waiting, query]);
42
+
43
+ const failed = useCallback(
44
+ (what: string) => () => {
45
+ toasts.show({ kind: "error", dismissible: true, message: `Couldn't ${what}.` });
46
+ },
47
+ [toasts],
48
+ );
49
+ const admit = useCallback((request: AdmissionRequest) => client.participants.admit(request.requestId).catch(failed(`let ${request.displayName} in`)), [client, failed]);
50
+ const deny = useCallback((request: AdmissionRequest) => client.participants.deny(request.requestId).catch(failed(`deny ${request.displayName}`)), [client, failed]);
51
+ const mute = useCallback((participant: Participant) => client.participants.mute(participant.participantId).catch(failed(`mute ${participant.displayName}`)), [client, failed]);
52
+ const remove = useCallback((participant: Participant) => client.participants.remove(participant.participantId).catch(failed(`remove ${participant.displayName}`)), [client, failed]);
53
+ const rename = useCallback((name: string) => client.participants.renameSelf(name).catch(failed("change your name")), [client, failed]);
54
+
55
+ const loud = useMemo(() => roster.filter((participant) => participant.participantId !== self.participantId && participant.media.microphone === "active"), [roster, self.participantId]);
56
+ const muteAll = useCallback(() => {
57
+ setConfirmMuteAll(false);
58
+ Promise.all(loud.map((participant) => client.participants.mute(participant.participantId))).catch(failed("mute everyone"));
59
+ }, [client, failed, loud]);
60
+
61
+ const renderItem = useCallback(
62
+ ({ item }: ListRenderItemInfo<PeopleRow>) => {
63
+ switch (item.kind) {
64
+ case "section":
65
+ return <SectionLabel label={item.label} />;
66
+ case "waiting":
67
+ return <WaitingRow onAdmit={admit} onDeny={deny} request={item.request} />;
68
+ case "hand":
69
+ return <HandRow isSelf={item.participant.participantId === self.participantId} order={item.order} participant={item.participant} />;
70
+ case "none":
71
+ return (
72
+ <ChalkText style={styles.none} tone="textMuted" variant="meta">
73
+ {item.query ? `No one in the call matches “${item.query}”.` : "No one else is in the call."}
74
+ </ChalkText>
75
+ );
76
+ case "person": {
77
+ const isSelf = item.participant.participantId === self.participantId;
78
+ return <PersonRow canMute={canMute && !isSelf} canRemove={canRemove && !isSelf} canRename={canRename && isSelf} handNumber={item.handNumber} isSelf={isSelf} onMute={mute} onRemove={remove} onRename={rename} participant={item.participant} />;
79
+ }
80
+ }
81
+ },
82
+ [admit, canMute, canRemove, canRename, deny, mute, remove, rename, self.participantId],
83
+ );
84
+
85
+ const header = (
86
+ <View>
87
+ {canMute ? (
88
+ <View style={styles.controls}>
89
+ <HostChip disabled={loud.length === 0} expanded={confirmMuteAll} buttonRef={muteAllRef} onPress={() => setConfirmMuteAll(true)} />
90
+ </View>
91
+ ) : null}
92
+ {roster.length >= SEARCH_FROM ? (
93
+ <View style={[styles.search, { backgroundColor: colors.raised }]}>
94
+ <Icon color={colors.textMuted} name="search" size={16} />
95
+ <TextField accessibilityLabel="Search people" autoCapitalize="none" autoCorrect={false} onChangeText={setQuery} placeholder="Search people" returnKeyType="search" testID="people.search" value={query} />
96
+ </View>
97
+ ) : null}
98
+ </View>
99
+ );
100
+
101
+ return (
102
+ <>
103
+ <FlatList contentContainerStyle={styles.content} data={rows} initialNumToRender={12} keyboardShouldPersistTaps="handled" keyExtractor={(row) => row.key} ListHeaderComponent={header} maxToRenderPerBatch={12} renderItem={renderItem} style={styles.list} testID="people.list" windowSize={7} />
104
+ <Menu accessibilityLabel="Mute everyone" anchorRef={muteAllRef} minWidth={280} onClose={() => setConfirmMuteAll(false)} open={confirmMuteAll} testID="people.mute-all.confirm">
105
+ <View style={styles.confirm}>
106
+ <ChalkText variant="cardTitle">Mute everyone?</ChalkText>
107
+ <ChalkText style={styles.confirmText} tone="textSoft" variant="meta">
108
+ {`${loud.length} ${loud.length === 1 ? "person" : "people"} will be muted. They can unmute themselves.`}
109
+ </ChalkText>
110
+ <View style={styles.confirmButtons}>
111
+ <PillButton label="Cancel" onPress={() => setConfirmMuteAll(false)} size="sm" testID="people.mute-all.cancel" />
112
+ <PillButton label="Mute everyone" onPress={muteAll} size="sm" testID="people.mute-all.confirm-button" variant="primary" />
113
+ </View>
114
+ </View>
115
+ </Menu>
116
+ </>
117
+ );
118
+ }
119
+
120
+ const NONE: readonly AdmissionRequest[] = [];
121
+
122
+ type HostChipProps = { readonly disabled: boolean; readonly expanded: boolean; readonly onPress: () => void; readonly buttonRef: RefObject<View | null> };
123
+
124
+ /** `.hcb`: Mute everyone, dimmed once everyone else is muted. */
125
+ function HostChip({ disabled, expanded, onPress, buttonRef }: HostChipProps): React.JSX.Element {
126
+ const { colors } = useTheme();
127
+ const press = usePressAnimation("pill");
128
+ return (
129
+ <Pressable accessibilityHint="Asks you to confirm" accessibilityLabel="Mute everyone" accessibilityRole="button" accessibilityState={{ disabled, expanded }} disabled={disabled} onPress={onPress} onPressIn={press.onPressIn} onPressOut={press.onPressOut} ref={buttonRef} testID="people.mute-all">
130
+ {({ pressed }) => (
131
+ <Animated.View style={[styles.chip, { backgroundColor: pressed || expanded ? colors.hover : colors.raised }, disabled ? styles.disabled : press.scaleStyle]}>
132
+ <Icon color={colors.textMuted} name="micOff" size={16} />
133
+ <ChalkText variant="button">Mute everyone</ChalkText>
134
+ </Animated.View>
135
+ )}
136
+ </Pressable>
137
+ );
138
+ }
139
+
140
+ const SectionLabel = memo(function SectionLabel({ label }: { readonly label: string }): React.JSX.Element {
141
+ return (
142
+ <View accessibilityRole="header" accessible style={styles.section}>
143
+ <ChalkText tone="textMuted" variant="section">
144
+ {label}
145
+ </ChalkText>
146
+ </View>
147
+ );
148
+ });
149
+
150
+ function Face({ name }: { readonly name: string }): React.JSX.Element {
151
+ return (
152
+ <View accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
153
+ <FaceAvatar name={name} size={32} />
154
+ </View>
155
+ );
156
+ }
157
+
158
+ const WaitingRow = memo(function WaitingRow({ request, onAdmit, onDeny }: { readonly request: AdmissionRequest; readonly onAdmit: (request: AdmissionRequest) => void; readonly onDeny: (request: AdmissionRequest) => void }): React.JSX.Element {
159
+ return (
160
+ <View style={styles.row} testID={`people.waiting.${request.participantId}`}>
161
+ <Face name={request.displayName} />
162
+ <ChalkText numberOfLines={1} style={styles.name} variant="bodyMedium">
163
+ {request.displayName}
164
+ </ChalkText>
165
+ <PillButton accessibilityLabel={`Deny ${request.displayName}`} label="Deny" onPress={() => onDeny(request)} size="sm" testID={`people.deny.${request.participantId}`} />
166
+ <PillButton accessibilityLabel={`Let ${request.displayName} in`} label="Let in" onPress={() => onAdmit(request)} size="sm" testID={`people.admit.${request.participantId}`} variant="primary" />
167
+ </View>
168
+ );
169
+ });
170
+
171
+ const HandRow = memo(function HandRow({ participant, order, isSelf }: { readonly participant: Participant; readonly order: number; readonly isSelf: boolean }): React.JSX.Element {
172
+ return (
173
+ <View accessible accessibilityLabel={`${participant.displayName}${isSelf ? " (You)" : ""}, hand raised, number ${order}`} style={[styles.row, styles.handRow]} testID={`people.hand.${participant.participantId}`}>
174
+ <View style={styles.handNumber}>
175
+ <ChalkText color={fixed.handText} variant="section">
176
+ {order}
177
+ </ChalkText>
178
+ </View>
179
+ <Face name={participant.displayName} />
180
+ <ChalkText numberOfLines={1} style={styles.name} variant="bodyMedium">
181
+ {participant.displayName}
182
+ {isSelf ? (
183
+ <ChalkText tone="textMuted" variant="meta">
184
+ {" (You)"}
185
+ </ChalkText>
186
+ ) : null}
187
+ </ChalkText>
188
+ </View>
189
+ );
190
+ });
191
+
192
+ type PersonRowProps = {
193
+ readonly participant: Participant;
194
+ readonly isSelf: boolean;
195
+ readonly handNumber: number | null;
196
+ readonly canMute: boolean;
197
+ readonly canRemove: boolean;
198
+ readonly canRename: boolean;
199
+ readonly onMute: (participant: Participant) => void;
200
+ readonly onRemove: (participant: Participant) => void;
201
+ readonly onRename: (name: string) => void;
202
+ };
203
+
204
+ const PersonRow = memo(function PersonRow({ participant, isSelf, handNumber, canMute, canRemove, canRename, onMute, onRemove, onRename }: PersonRowProps): React.JSX.Element {
205
+ const { colors } = useTheme();
206
+ const [menuOpen, setMenuOpen] = useState(false);
207
+ const [renaming, setRenaming] = useState(false);
208
+ const [draft, setDraft] = useState(participant.displayName);
209
+ const optionsRef = useRef<View>(null);
210
+ const host = participant.capabilities.includes("endEpisode");
211
+ const micOff = participant.media.microphone === "inactive";
212
+ const canMuteNow = canMute && participant.media.microphone === "active";
213
+ const hasOptions = canRename || canMuteNow || canRemove;
214
+
215
+ const save = () => {
216
+ const name = draft.trim();
217
+ setRenaming(false);
218
+ if (name && name !== participant.displayName) onRename(name);
219
+ };
220
+
221
+ if (renaming) {
222
+ return (
223
+ <View style={styles.row} testID="people.rename">
224
+ <Face name={participant.displayName} />
225
+ <View style={[styles.rename, { backgroundColor: colors.raised, boxShadow: `0px 0px 0px 2px ${colors.text}` }]}>
226
+ <TextField accessibilityLabel="Your name" autoFocus maxLength={40} onChangeText={setDraft} onSubmitEditing={save} returnKeyType="done" selectTextOnFocus testID="people.rename.input" value={draft} />
227
+ </View>
228
+ <PillButton label="Save" onPress={save} size="sm" testID="people.rename.save" variant="primary" />
229
+ </View>
230
+ );
231
+ }
232
+
233
+ const status = [isSelf ? "You" : null, host ? "Host" : null, participant.handRaised ? `hand raised${handNumber ? `, number ${handNumber}` : ""}` : null, micOff ? "microphone off" : null].filter(Boolean).join(", ");
234
+ return (
235
+ <View style={styles.row} testID={`people.row.${participant.participantId}`}>
236
+ <Face name={participant.displayName} />
237
+ <ChalkText accessibilityLabel={status ? `${participant.displayName}, ${status}` : participant.displayName} numberOfLines={1} style={styles.name} variant="bodyMedium">
238
+ {participant.displayName}
239
+ {isSelf ? (
240
+ <ChalkText tone="textMuted" variant="meta">
241
+ {" (You)"}
242
+ </ChalkText>
243
+ ) : null}
244
+ {host ? (
245
+ <ChalkText tone="textMuted" variant="meta">
246
+ {" Host"}
247
+ </ChalkText>
248
+ ) : null}
249
+ </ChalkText>
250
+ {participant.handRaised ? (
251
+ <View accessibilityElementsHidden importantForAccessibility="no-hide-descendants" style={styles.status}>
252
+ <Icon color={colors.warning} name="hand" size={16} />
253
+ {handNumber ? (
254
+ <ChalkText color={colors.warning} style={styles.statusNumber} variant="caption">
255
+ {handNumber}
256
+ </ChalkText>
257
+ ) : null}
258
+ </View>
259
+ ) : null}
260
+ {micOff ? (
261
+ <View accessibilityElementsHidden importantForAccessibility="no-hide-descendants" style={styles.status}>
262
+ <Icon color={colors.dangerText} name="micOff" size={16} />
263
+ </View>
264
+ ) : null}
265
+ {hasOptions ? <IconButton accessibilityLabel={`Options for ${participant.displayName}`} expanded={menuOpen} icon="more" onPress={() => setMenuOpen(true)} ref={optionsRef} size={36} testID={`people.options.${participant.participantId}`} tone="ghost" /> : null}
266
+ <Menu accessibilityLabel="Person options" anchorRef={optionsRef} onClose={() => setMenuOpen(false)} open={menuOpen} testID="people.menu">
267
+ {canRename ? (
268
+ <MenuItem
269
+ icon="edit"
270
+ label="Edit name"
271
+ onPress={() => {
272
+ setMenuOpen(false);
273
+ setDraft(participant.displayName);
274
+ setRenaming(true);
275
+ }}
276
+ testID="people.menu.rename"
277
+ />
278
+ ) : null}
279
+ {canMuteNow ? (
280
+ <MenuItem
281
+ icon="micOff"
282
+ label="Mute"
283
+ onPress={() => {
284
+ setMenuOpen(false);
285
+ onMute(participant);
286
+ }}
287
+ testID="people.menu.mute"
288
+ />
289
+ ) : null}
290
+ {canRemove ? (
291
+ <MenuItem
292
+ danger
293
+ icon="x"
294
+ label="Remove from call"
295
+ onPress={() => {
296
+ setMenuOpen(false);
297
+ onRemove(participant);
298
+ }}
299
+ testID="people.menu.remove"
300
+ />
301
+ ) : null}
302
+ </Menu>
303
+ </View>
304
+ );
305
+ });
306
+
307
+ const styles = StyleSheet.create({
308
+ list: { flex: 1 },
309
+ content: { padding: 8, paddingBottom: 16 },
310
+ controls: { flexDirection: "row", flexWrap: "wrap", gap: 6, marginHorizontal: 2, marginTop: 2, marginBottom: 8 },
311
+ chip: { flexDirection: "row", alignItems: "center", gap: 6, height: 36, paddingLeft: 10, paddingRight: 12, borderRadius: 999 },
312
+ disabled: { opacity: 0.45 },
313
+ search: { flexDirection: "row", alignItems: "center", gap: 8, height: 40, marginHorizontal: 2, marginTop: 2, marginBottom: 8, paddingHorizontal: 14, borderRadius: 999 },
314
+ section: { paddingTop: 10, paddingHorizontal: 8, paddingBottom: 6 },
315
+ none: { paddingVertical: 16, paddingHorizontal: 8 },
316
+ row: { flexDirection: "row", alignItems: "center", gap: 10, minHeight: 52, paddingLeft: 8, paddingRight: 4, borderRadius: 12 },
317
+ handRow: { gap: 8 },
318
+ name: { flex: 1 },
319
+ handNumber: { width: 20, height: 20, borderRadius: 10, alignItems: "center", justifyContent: "center", backgroundColor: fixed.hand },
320
+ status: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
321
+ statusNumber: { marginLeft: 1, fontWeight: "700" },
322
+ rename: { flex: 1, flexDirection: "row", alignItems: "center", height: 36, paddingHorizontal: 12, borderRadius: 999 },
323
+ confirm: { paddingTop: 8, paddingHorizontal: 8, paddingBottom: 4 },
324
+ confirmText: { marginTop: 4, marginBottom: 10 },
325
+ confirmButtons: { flexDirection: "row", justifyContent: "flex-end", gap: 8, marginTop: 4, marginHorizontal: 6 },
326
+ });