@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.
- package/README.md +134 -29
- package/android/build.gradle +35 -0
- package/android/compatibility/ModuleApiCompatibility.java +215 -0
- package/android/src/main/AndroidManifest.xml +2 -1
- package/android/src/main/java/ai/q9labs/chalk/reactnative/ChalkSpaceForegroundService.java +105 -31
- package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkAndroidConnectionServicePackage.kt +2 -0
- package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkAudioRouteModule.kt +137 -0
- package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkCallServiceModule.kt +32 -0
- package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/CommunicationDeviceListener.kt +23 -0
- package/android/src/test/kotlin/ai/q9labs/chalk/compatibility/UnsafeSignatures.kt +29 -0
- package/dist/android/call-service.d.ts +4 -0
- package/dist/android/call-service.d.ts.map +1 -0
- package/dist/avatar/FaceAvatar.d.ts +19 -0
- package/dist/avatar/FaceAvatar.d.ts.map +1 -0
- package/dist/avatar/tile-avatar-size.d.ts +3 -0
- package/dist/avatar/tile-avatar-size.d.ts.map +1 -0
- package/dist/chunk-GYVZSOGQ.js +30 -0
- package/dist/chunk-IHE6EA3T.js +25 -0
- package/dist/chunk-OIBT2FNM.js +1479 -0
- package/dist/client.d.ts +1 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +185 -2
- package/dist/components/EmbeddedWhiteboard.d.ts.map +1 -1
- package/dist/components/native-media-visibility.d.ts +3 -2
- package/dist/components/native-media-visibility.d.ts.map +1 -1
- package/dist/components/useAndroidCallService.d.ts +8 -0
- package/dist/components/useAndroidCallService.d.ts.map +1 -0
- package/dist/dev.d.ts +10 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +10 -0
- package/dist/dock/Dock.d.ts +23 -0
- package/dist/dock/Dock.d.ts.map +1 -0
- package/dist/dock/LeaveMenu.d.ts +12 -0
- package/dist/dock/LeaveMenu.d.ts.map +1 -0
- package/dist/dock/MoreSheet.d.ts +17 -0
- package/dist/dock/MoreSheet.d.ts.map +1 -0
- package/dist/dock/ReactionBurst.d.ts +8 -0
- package/dist/dock/ReactionBurst.d.ts.map +1 -0
- package/dist/dock/RoutePicker.d.ts +16 -0
- package/dist/dock/RoutePicker.d.ts.map +1 -0
- package/dist/dock/audio-route.d.ts +25 -0
- package/dist/dock/audio-route.d.ts.map +1 -0
- package/dist/error-YOPTEH5N.wav +0 -0
- package/dist/frame/CallFrame.d.ts +17 -0
- package/dist/frame/CallFrame.d.ts.map +1 -0
- package/dist/frame/Chalk.d.ts +40 -0
- package/dist/frame/Chalk.d.ts.map +1 -0
- package/dist/frame/back-handler.d.ts +6 -0
- package/dist/frame/back-handler.d.ts.map +1 -0
- package/dist/frame/dev-scenario.d.ts +28 -0
- package/dist/frame/dev-scenario.d.ts.map +1 -0
- package/dist/frame/device-layout.d.ts +16 -0
- package/dist/frame/device-layout.d.ts.map +1 -0
- package/dist/frame/measurement.d.ts +28 -0
- package/dist/frame/measurement.d.ts.map +1 -0
- package/dist/frame/portal.d.ts +24 -0
- package/dist/frame/portal.d.ts.map +1 -0
- package/dist/frame/preferences.d.ts +21 -0
- package/dist/frame/preferences.d.ts.map +1 -0
- package/dist/frame/regions.d.ts +25 -0
- package/dist/frame/regions.d.ts.map +1 -0
- package/dist/hand-DFWVXPH2.wav +0 -0
- package/dist/haptics/haptics.d.ts +12 -0
- package/dist/haptics/haptics.d.ts.map +1 -0
- package/dist/header/CallDetailsSheet.d.ts +18 -0
- package/dist/header/CallDetailsSheet.d.ts.map +1 -0
- package/dist/header/Header.d.ts +17 -0
- package/dist/header/Header.d.ts.map +1 -0
- package/dist/header/QrCode.d.ts +6 -0
- package/dist/header/QrCode.d.ts.map +1 -0
- package/dist/icons/Icon.d.ts +12 -0
- package/dist/icons/Icon.d.ts.map +1 -0
- package/dist/icons/icon-element.d.ts +24 -0
- package/dist/icons/icon-element.d.ts.map +1 -0
- package/dist/icons/icon-paths.d.ts +530 -0
- package/dist/icons/icon-paths.d.ts.map +1 -0
- package/dist/index.d.ts +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2956 -38
- package/dist/join-Z6RL5AHE.wav +0 -0
- package/dist/knock-4VBQSHK4.wav +0 -0
- package/dist/leave-MQLZOWQD.wav +0 -0
- package/dist/limbo/Limbo.d.ts +25 -0
- package/dist/limbo/Limbo.d.ts.map +1 -0
- package/dist/limbo/Veil.d.ts +10 -0
- package/dist/limbo/Veil.d.ts.map +1 -0
- package/dist/media/native-capture-error.d.ts +7 -0
- package/dist/media/native-capture-error.d.ts.map +1 -0
- package/dist/message-6Y6WJGTB.wav +0 -0
- package/dist/motion/MovingFrame.d.ts +36 -0
- package/dist/motion/MovingFrame.d.ts.map +1 -0
- package/dist/motion/motion.d.ts +82 -0
- package/dist/motion/motion.d.ts.map +1 -0
- package/dist/motion/presence.d.ts +6 -0
- package/dist/motion/presence.d.ts.map +1 -0
- package/dist/motion/press.d.ts +15 -0
- package/dist/motion/press.d.ts.map +1 -0
- package/dist/mute-H5SJFXVI.wav +0 -0
- package/dist/primitives/BottomSheet.d.ts +36 -0
- package/dist/primitives/BottomSheet.d.ts.map +1 -0
- package/dist/primitives/ChalkText.d.ts +13 -0
- package/dist/primitives/ChalkText.d.ts.map +1 -0
- package/dist/primitives/CopyButton.d.ts +11 -0
- package/dist/primitives/CopyButton.d.ts.map +1 -0
- package/dist/primitives/IconButton.d.ts +26 -0
- package/dist/primitives/IconButton.d.ts.map +1 -0
- package/dist/primitives/Menu.d.ts +39 -0
- package/dist/primitives/Menu.d.ts.map +1 -0
- package/dist/primitives/NameTag.d.ts +19 -0
- package/dist/primitives/NameTag.d.ts.map +1 -0
- package/dist/primitives/PillButton.d.ts +19 -0
- package/dist/primitives/PillButton.d.ts.map +1 -0
- package/dist/primitives/SegmentedControl.d.ts +17 -0
- package/dist/primitives/SegmentedControl.d.ts.map +1 -0
- package/dist/primitives/SheetGroup.d.ts +24 -0
- package/dist/primitives/SheetGroup.d.ts.map +1 -0
- package/dist/primitives/Switch.d.ts +9 -0
- package/dist/primitives/Switch.d.ts.map +1 -0
- package/dist/primitives/TileShell.d.ts +18 -0
- package/dist/primitives/TileShell.d.ts.map +1 -0
- package/dist/primitives/announce.d.ts +3 -0
- package/dist/primitives/announce.d.ts.map +1 -0
- package/dist/primitives/menu-placement.d.ts +32 -0
- package/dist/primitives/menu-placement.d.ts.map +1 -0
- package/dist/primitives/toast/Toast.d.ts +8 -0
- package/dist/primitives/toast/Toast.d.ts.map +1 -0
- package/dist/primitives/toast/ToastStack.d.ts +18 -0
- package/dist/primitives/toast/ToastStack.d.ts.map +1 -0
- package/dist/primitives/toast/toast-store.d.ts +39 -0
- package/dist/primitives/toast/toast-store.d.ts.map +1 -0
- package/dist/recording-Q4GB77Y4.wav +0 -0
- package/dist/sheets/people-chat/ChatNotices.d.ts +14 -0
- package/dist/sheets/people-chat/ChatNotices.d.ts.map +1 -0
- package/dist/sheets/people-chat/ChatTab.d.ts +6 -0
- package/dist/sheets/people-chat/ChatTab.d.ts.map +1 -0
- package/dist/sheets/people-chat/Composer.d.ts +7 -0
- package/dist/sheets/people-chat/Composer.d.ts.map +1 -0
- package/dist/sheets/people-chat/PeopleChatSheet.d.ts +17 -0
- package/dist/sheets/people-chat/PeopleChatSheet.d.ts.map +1 -0
- package/dist/sheets/people-chat/PeopleTab.d.ts +3 -0
- package/dist/sheets/people-chat/PeopleTab.d.ts.map +1 -0
- package/dist/sheets/people-chat/PhotoViewer.d.ts +19 -0
- package/dist/sheets/people-chat/PhotoViewer.d.ts.map +1 -0
- package/dist/sheets/people-chat/TextField.d.ts +4 -0
- package/dist/sheets/people-chat/TextField.d.ts.map +1 -0
- package/dist/sheets/people-chat/attachments.d.ts +33 -0
- package/dist/sheets/people-chat/attachments.d.ts.map +1 -0
- package/dist/sheets/people-chat/chat-model.d.ts +64 -0
- package/dist/sheets/people-chat/chat-model.d.ts.map +1 -0
- package/dist/sheets/people-chat/people-model.d.ts +43 -0
- package/dist/sheets/people-chat/people-model.d.ts.map +1 -0
- package/dist/sounds/sounds.d.ts +33 -0
- package/dist/sounds/sounds.d.ts.map +1 -0
- package/dist/space-client/create-native-space-client.d.ts +0 -4
- package/dist/space-client/create-native-space-client.d.ts.map +1 -1
- package/dist/space-client/random-uuid.d.ts +7 -0
- package/dist/space-client/random-uuid.d.ts.map +1 -0
- package/dist/stage/ContentTile.d.ts +18 -0
- package/dist/stage/ContentTile.d.ts.map +1 -0
- package/dist/stage/ParticipantTile.d.ts +18 -0
- package/dist/stage/ParticipantTile.d.ts.map +1 -0
- package/dist/stage/Stage.d.ts +14 -0
- package/dist/stage/Stage.d.ts.map +1 -0
- package/dist/stage/TileSheet.d.ts +12 -0
- package/dist/stage/TileSheet.d.ts.map +1 -0
- package/dist/stage/camera.d.ts +7 -0
- package/dist/stage/camera.d.ts.map +1 -0
- package/dist/stage/layout.d.ts +57 -0
- package/dist/stage/layout.d.ts.map +1 -0
- package/dist/stage/space-selector.d.ts +10 -0
- package/dist/stage/space-selector.d.ts.map +1 -0
- package/dist/stage/stage-view.d.ts +23 -0
- package/dist/stage/stage-view.d.ts.map +1 -0
- package/dist/status/CallOverlays.d.ts +17 -0
- package/dist/status/CallOverlays.d.ts.map +1 -0
- package/dist/status/ChalkArrival.d.ts +24 -0
- package/dist/status/ChalkArrival.d.ts.map +1 -0
- package/dist/status/StatusPage.d.ts +14 -0
- package/dist/status/StatusPage.d.ts.map +1 -0
- package/dist/status/call-screen.d.ts +20 -0
- package/dist/status/call-screen.d.ts.map +1 -0
- package/dist/test-VD2KAM4D.wav +0 -0
- package/dist/theme/theme.d.ts +15 -0
- package/dist/theme/theme.d.ts.map +1 -0
- package/dist/theme/tokens.d.ts +248 -0
- package/dist/theme/tokens.d.ts.map +1 -0
- package/dist/toasts/CallEventToasts.d.ts +21 -0
- package/dist/toasts/CallEventToasts.d.ts.map +1 -0
- package/dist/toasts/notices.d.ts +34 -0
- package/dist/toasts/notices.d.ts.map +1 -0
- package/dist/ui/ChalkUI.d.ts +12 -0
- package/dist/ui/ChalkUI.d.ts.map +1 -0
- package/dist/ui.d.ts +25 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +77 -0
- package/dist/unmute-2XR2LICU.wav +0 -0
- package/embedded/chalk-whiteboard/asset-manifest.json +2 -2
- package/embedded/chalk-whiteboard/renderer.js +510 -510
- package/ios/ChalkAudioRouteModule.swift +93 -0
- package/ios/ChalkAudioRouteModuleBridge.m +9 -0
- package/package.json +46 -76
- package/src/android/call-service.ts +19 -0
- package/src/avatar/FaceAvatar.tsx +145 -0
- package/src/avatar/tile-avatar-size.ts +4 -0
- package/src/client.ts +1 -5
- package/src/components/EmbeddedWhiteboard.tsx +14 -71
- package/src/components/native-media-visibility.ts +1 -2
- package/src/components/useAndroidCallService.ts +41 -0
- package/src/dev.ts +9 -0
- package/src/dock/Dock.tsx +95 -0
- package/src/dock/LeaveMenu.tsx +21 -0
- package/src/dock/MoreSheet.tsx +233 -0
- package/src/dock/ReactionBurst.tsx +55 -0
- package/src/dock/RoutePicker.tsx +42 -0
- package/src/dock/audio-route.ts +69 -0
- package/src/frame/CallFrame.tsx +209 -0
- package/src/frame/Chalk.tsx +83 -0
- package/src/frame/back-handler.ts +19 -0
- package/src/frame/dev-scenario.ts +48 -0
- package/src/frame/device-layout.tsx +31 -0
- package/src/frame/measurement.ts +44 -0
- package/src/frame/portal.tsx +107 -0
- package/src/frame/preferences.tsx +48 -0
- package/src/frame/regions.test.ts +38 -0
- package/src/frame/regions.ts +60 -0
- package/src/haptics/haptics.ts +19 -0
- package/src/header/CallDetailsSheet.tsx +139 -0
- package/src/header/Header.tsx +107 -0
- package/src/header/QrCode.tsx +34 -0
- package/src/icons/Icon.tsx +35 -0
- package/src/icons/icon-element.ts +11 -0
- package/src/icons/icon-paths.ts +199 -0
- package/src/index.ts +6 -9
- package/src/limbo/Limbo.tsx +159 -0
- package/src/limbo/Veil.tsx +36 -0
- package/src/media/native-capture-error.test.ts +19 -0
- package/src/media/native-capture-error.ts +13 -0
- package/src/motion/MovingFrame.tsx +101 -0
- package/src/motion/motion.ts +33 -0
- package/src/motion/presence.ts +20 -0
- package/src/motion/press.ts +35 -0
- package/src/primitives/BottomSheet.tsx +211 -0
- package/src/primitives/ChalkText.tsx +23 -0
- package/src/primitives/CopyButton.tsx +78 -0
- package/src/primitives/IconButton.tsx +90 -0
- package/src/primitives/Menu.tsx +212 -0
- package/src/primitives/NameTag.tsx +68 -0
- package/src/primitives/PillButton.tsx +56 -0
- package/src/primitives/SegmentedControl.tsx +55 -0
- package/src/primitives/SheetGroup.tsx +72 -0
- package/src/primitives/Switch.tsx +39 -0
- package/src/primitives/TileShell.tsx +53 -0
- package/src/primitives/announce.ts +7 -0
- package/src/primitives/menu-placement.test.ts +50 -0
- package/src/primitives/menu-placement.ts +43 -0
- package/src/primitives/toast/Toast.tsx +50 -0
- package/src/primitives/toast/ToastStack.tsx +89 -0
- package/src/primitives/toast/toast-store.ts +86 -0
- package/src/sheets/people-chat/ChatNotices.tsx +69 -0
- package/src/sheets/people-chat/ChatTab.tsx +186 -0
- package/src/sheets/people-chat/Composer.tsx +182 -0
- package/src/sheets/people-chat/PeopleChatSheet.tsx +100 -0
- package/src/sheets/people-chat/PeopleTab.tsx +326 -0
- package/src/sheets/people-chat/PhotoViewer.tsx +132 -0
- package/src/sheets/people-chat/TextField.tsx +14 -0
- package/src/sheets/people-chat/attachments.tsx +183 -0
- package/src/sheets/people-chat/chat-model.ts +157 -0
- package/src/sheets/people-chat/people-chat-model.test.ts +98 -0
- package/src/sheets/people-chat/people-model.ts +68 -0
- package/src/sounds/assets/error.wav +0 -0
- package/src/sounds/assets/hand.wav +0 -0
- package/src/sounds/assets/join.wav +0 -0
- package/src/sounds/assets/knock.wav +0 -0
- package/src/sounds/assets/leave.wav +0 -0
- package/src/sounds/assets/message.wav +0 -0
- package/src/sounds/assets/mute.wav +0 -0
- package/src/sounds/assets/recording.wav +0 -0
- package/src/sounds/assets/test.wav +0 -0
- package/src/sounds/assets/unmute.wav +0 -0
- package/src/sounds/assets.d.ts +5 -0
- package/src/sounds/sounds.ts +50 -0
- package/src/space-client/create-native-space-client.ts +20 -181
- package/src/space-client/random-uuid.ts +17 -0
- package/src/space-client/whiteboard-watchdog.test.ts +23 -0
- package/src/stage/ContentTile.tsx +100 -0
- package/src/stage/ParticipantTile.tsx +140 -0
- package/src/stage/Stage.tsx +152 -0
- package/src/stage/TileSheet.tsx +116 -0
- package/src/stage/camera.ts +20 -0
- package/src/stage/layout.test.ts +77 -0
- package/src/stage/layout.ts +226 -0
- package/src/stage/space-selector.ts +31 -0
- package/src/stage/stage-view.tsx +68 -0
- package/src/status/CallOverlays.tsx +47 -0
- package/src/status/ChalkArrival.tsx +53 -0
- package/src/status/StatusPage.tsx +123 -0
- package/src/status/call-screen.test.ts +42 -0
- package/src/status/call-screen.ts +29 -0
- package/src/theme/theme.tsx +28 -0
- package/src/theme/tokens.ts +247 -0
- package/src/toasts/CallEventToasts.tsx +192 -0
- package/src/toasts/notices.test.ts +92 -0
- package/src/toasts/notices.ts +62 -0
- package/src/ui/ChalkUI.tsx +49 -0
- package/src/ui.ts +24 -0
- package/dist/android.d.ts +0 -2
- package/dist/android.d.ts.map +0 -1
- package/dist/android.js +0 -62
- package/dist/chunk-2OS7SSXE.js +0 -552
- package/dist/chunk-5DQ4KBRH.js +0 -690
- package/dist/chunk-ITLUHTVS.js +0 -5271
- package/dist/chunk-JB4LNWVP.js +0 -247
- package/dist/components/Chalk.d.ts +0 -79
- package/dist/components/Chalk.d.ts.map +0 -1
- package/dist/components/ChatMessageList.d.ts +0 -11
- package/dist/components/ChatMessageList.d.ts.map +0 -1
- package/dist/components/Entrance.d.ts +0 -22
- package/dist/components/Entrance.d.ts.map +0 -1
- package/dist/components/EntranceView.android.d.ts +0 -4
- package/dist/components/EntranceView.android.d.ts.map +0 -1
- package/dist/components/EntranceView.d.ts +0 -18
- package/dist/components/EntranceView.d.ts.map +0 -1
- package/dist/components/EntranceView.ios-pad.d.ts +0 -3
- package/dist/components/EntranceView.ios-pad.d.ts.map +0 -1
- package/dist/components/EntranceView.ios-phone.d.ts +0 -3
- package/dist/components/EntranceView.ios-phone.d.ts.map +0 -1
- package/dist/components/EntranceView.macos.d.ts +0 -4
- package/dist/components/EntranceView.macos.d.ts.map +0 -1
- package/dist/components/FaceAvatar.d.ts +0 -16
- package/dist/components/FaceAvatar.d.ts.map +0 -1
- package/dist/components/GradientSurface.d.ts +0 -14
- package/dist/components/GradientSurface.d.ts.map +0 -1
- package/dist/components/MediaView.d.ts +0 -14
- package/dist/components/MediaView.d.ts.map +0 -1
- package/dist/components/ReactionPicker.d.ts +0 -10
- package/dist/components/ReactionPicker.d.ts.map +0 -1
- package/dist/components/SpaceView.android.d.ts +0 -2
- package/dist/components/SpaceView.android.d.ts.map +0 -1
- package/dist/components/SpaceView.d.ts +0 -33
- package/dist/components/SpaceView.d.ts.map +0 -1
- package/dist/components/SpaceView.ios-pad.d.ts +0 -2
- package/dist/components/SpaceView.ios-pad.d.ts.map +0 -1
- package/dist/components/SpaceView.ios-phone.d.ts +0 -2
- package/dist/components/SpaceView.ios-phone.d.ts.map +0 -1
- package/dist/components/SpaceView.macos.d.ts +0 -2
- package/dist/components/SpaceView.macos.d.ts.map +0 -1
- package/dist/components/SpaceView.shared.d.ts +0 -3
- package/dist/components/SpaceView.shared.d.ts.map +0 -1
- package/dist/components/episode-diagnostic-render-observer.d.ts +0 -2
- package/dist/components/episode-diagnostic-render-observer.d.ts.map +0 -1
- package/dist/components/logo/Logo.d.ts +0 -11
- package/dist/components/logo/Logo.d.ts.map +0 -1
- package/dist/components/native-animation-controller.d.ts +0 -7
- package/dist/components/native-animation-controller.d.ts.map +0 -1
- package/dist/components/native-chat.d.ts +0 -8
- package/dist/components/native-chat.d.ts.map +0 -1
- package/dist/components/native-entrance/entrance-controller-store.d.ts +0 -34
- package/dist/components/native-entrance/entrance-controller-store.d.ts.map +0 -1
- package/dist/components/native-entrance/useEntranceController.d.ts +0 -19
- package/dist/components/native-entrance/useEntranceController.d.ts.map +0 -1
- package/dist/components/native-joining-loading-animation.d.ts +0 -9
- package/dist/components/native-joining-loading-animation.d.ts.map +0 -1
- package/dist/components/native-space-view/AppearanceSettings.d.ts +0 -2
- package/dist/components/native-space-view/AppearanceSettings.d.ts.map +0 -1
- package/dist/components/native-space-view/FeedbackSheet.d.ts +0 -12
- package/dist/components/native-space-view/FeedbackSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/ParticipantActionSheet.d.ts +0 -9
- package/dist/components/native-space-view/ParticipantActionSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/SettingsSheet.d.ts +0 -7
- package/dist/components/native-space-view/SettingsSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceActionMenu.d.ts +0 -10
- package/dist/components/native-space-view/SpaceActionMenu.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceBottomDock.android.d.ts +0 -4
- package/dist/components/native-space-view/SpaceBottomDock.android.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceChatAttachment.d.ts +0 -8
- package/dist/components/native-space-view/SpaceChatAttachment.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceChatSheet.d.ts +0 -5
- package/dist/components/native-space-view/SpaceChatSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceGrid.android.d.ts +0 -8
- package/dist/components/native-space-view/SpaceGrid.android.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceMoreSheet.d.ts +0 -7
- package/dist/components/native-space-view/SpaceMoreSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/SpacePanelSheet.d.ts +0 -5
- package/dist/components/native-space-view/SpacePanelSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/SpacePeopleSheet.d.ts +0 -5
- package/dist/components/native-space-view/SpacePeopleSheet.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceStage.android.d.ts +0 -29
- package/dist/components/native-space-view/SpaceStage.android.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceSurfacePrimitives.d.ts +0 -30
- package/dist/components/native-space-view/SpaceSurfacePrimitives.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceTopBar.android.d.ts +0 -11
- package/dist/components/native-space-view/SpaceTopBar.android.d.ts.map +0 -1
- package/dist/components/native-space-view/SpaceWhiteboardSurface.d.ts +0 -15
- package/dist/components/native-space-view/SpaceWhiteboardSurface.d.ts.map +0 -1
- package/dist/components/native-space-view/diagnostics.d.ts +0 -48
- package/dist/components/native-space-view/diagnostics.d.ts.map +0 -1
- package/dist/components/native-space-view/format-space-duration.d.ts +0 -2
- package/dist/components/native-space-view/format-space-duration.d.ts.map +0 -1
- package/dist/components/native-space-view/native-space-grid-keys.d.ts +0 -3
- package/dist/components/native-space-view/native-space-grid-keys.d.ts.map +0 -1
- package/dist/components/native-space-view/native-space-page-store.d.ts +0 -8
- package/dist/components/native-space-view/native-space-page-store.d.ts.map +0 -1
- package/dist/components/native-space-view/space-chat-attachments.d.ts +0 -15
- package/dist/components/native-space-view/space-chat-attachments.d.ts.map +0 -1
- package/dist/components/native-space-view/space-panel-layout.d.ts +0 -10
- package/dist/components/native-space-view/space-panel-layout.d.ts.map +0 -1
- package/dist/components/native-space-view/space-progressive-surface-helpers.d.ts +0 -16
- package/dist/components/native-space-view/space-progressive-surface-helpers.d.ts.map +0 -1
- package/dist/components/native-space-view/space-progressive-surface-types.d.ts +0 -3
- package/dist/components/native-space-view/space-progressive-surface-types.d.ts.map +0 -1
- package/dist/components/native-space-view/types.d.ts +0 -24
- package/dist/components/native-space-view/types.d.ts.map +0 -1
- package/dist/components/native-space-view/useSpaceViewController.contract.d.ts +0 -41
- package/dist/components/native-space-view/useSpaceViewController.contract.d.ts.map +0 -1
- package/dist/components/native-space-view/useSpaceViewController.d.ts +0 -156
- package/dist/components/native-space-view/useSpaceViewController.d.ts.map +0 -1
- package/dist/components/native-space-view/useSpaceViewDerived.d.ts +0 -12
- package/dist/components/native-space-view/useSpaceViewDerived.d.ts.map +0 -1
- package/dist/components/native-space-view/useSpaceViewPanels.d.ts +0 -70
- package/dist/components/native-space-view/useSpaceViewPanels.d.ts.map +0 -1
- package/dist/feedback/native-capture.d.ts +0 -9
- package/dist/feedback/native-capture.d.ts.map +0 -1
- package/dist/feedback/native-evidence.d.ts +0 -3
- package/dist/feedback/native-evidence.d.ts.map +0 -1
- package/dist/hooks/entrance-preview-store.d.ts +0 -16
- package/dist/hooks/entrance-preview-store.d.ts.map +0 -1
- package/dist/hooks/useEntrancePreview.d.ts +0 -7
- package/dist/hooks/useEntrancePreview.d.ts.map +0 -1
- package/dist/hooks/useLayout.d.ts +0 -16
- package/dist/hooks/useLayout.d.ts.map +0 -1
- package/dist/hooks/usePanels.d.ts +0 -12
- package/dist/hooks/usePanels.d.ts.map +0 -1
- package/dist/ios-pad.d.ts +0 -2
- package/dist/ios-pad.d.ts.map +0 -1
- package/dist/ios-pad.js +0 -62
- package/dist/ios-phone.d.ts +0 -2
- package/dist/ios-phone.d.ts.map +0 -1
- package/dist/ios-phone.js +0 -62
- package/dist/ios.d.ts +0 -2
- package/dist/ios.d.ts.map +0 -1
- package/dist/ios.js +0 -62
- package/dist/macos.d.ts +0 -2
- package/dist/macos.d.ts.map +0 -1
- package/dist/macos.js +0 -62
- package/dist/space-client/cloudflare-rtk-native.d.ts +0 -3
- package/dist/space-client/cloudflare-rtk-native.d.ts.map +0 -1
- package/dist/telemetry.d.ts +0 -63
- package/dist/telemetry.d.ts.map +0 -1
- package/dist/ui/appearance.d.ts +0 -246
- package/dist/ui/appearance.d.ts.map +0 -1
- package/dist/ui/appearance.js +0 -14
- package/dist/ui/native-appearance-context.d.ts +0 -15
- package/dist/ui/native-appearance-context.d.ts.map +0 -1
- package/dist/ui/native-theme.d.ts +0 -17
- package/dist/ui/native-theme.d.ts.map +0 -1
- package/dist/ui/native-types.d.ts +0 -38
- package/dist/ui/native-types.d.ts.map +0 -1
- package/dist/ui/participant-avatar.d.ts +0 -21
- package/dist/ui/participant-avatar.d.ts.map +0 -1
- package/dist/ui/theme-tokens.d.ts +0 -25
- package/dist/ui/theme-tokens.d.ts.map +0 -1
- package/dist/ui/theme.d.ts +0 -404
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js +0 -8
- package/dist/utils/native-space-layout.d.ts +0 -42
- package/dist/utils/native-space-layout.d.ts.map +0 -1
- package/src/android.ts +0 -1
- package/src/components/Chalk.tsx +0 -363
- package/src/components/ChatMessageList.tsx +0 -209
- package/src/components/Entrance.tsx +0 -42
- package/src/components/EntranceView.android.tsx +0 -274
- package/src/components/EntranceView.ios-pad.tsx +0 -326
- package/src/components/EntranceView.ios-phone.tsx +0 -271
- package/src/components/EntranceView.macos.tsx +0 -278
- package/src/components/EntranceView.tsx +0 -38
- package/src/components/FaceAvatar.tsx +0 -120
- package/src/components/GradientSurface.tsx +0 -23
- package/src/components/MediaView.tsx +0 -76
- package/src/components/ReactionPicker.tsx +0 -98
- package/src/components/SpaceView.android.tsx +0 -1
- package/src/components/SpaceView.ios-pad.tsx +0 -1
- package/src/components/SpaceView.ios-phone.tsx +0 -1
- package/src/components/SpaceView.macos.tsx +0 -1
- package/src/components/SpaceView.shared.tsx +0 -98
- package/src/components/SpaceView.tsx +0 -54
- package/src/components/episode-diagnostic-render-observer.ts +0 -12
- package/src/components/logo/Logo.tsx +0 -309
- package/src/components/native-animation-controller.ts +0 -36
- package/src/components/native-chat.ts +0 -23
- package/src/components/native-entrance/entrance-controller-store.ts +0 -112
- package/src/components/native-entrance/useEntranceController.ts +0 -60
- package/src/components/native-joining-loading-animation.ts +0 -26
- package/src/components/native-space-view/AppearanceSettings.tsx +0 -114
- package/src/components/native-space-view/FeedbackSheet.tsx +0 -296
- package/src/components/native-space-view/ParticipantActionSheet.tsx +0 -99
- package/src/components/native-space-view/SettingsSheet.tsx +0 -164
- package/src/components/native-space-view/SpaceActionMenu.tsx +0 -109
- package/src/components/native-space-view/SpaceBottomDock.android.tsx +0 -115
- package/src/components/native-space-view/SpaceChatAttachment.tsx +0 -105
- package/src/components/native-space-view/SpaceChatSheet.tsx +0 -184
- package/src/components/native-space-view/SpaceGrid.android.tsx +0 -323
- package/src/components/native-space-view/SpaceMoreSheet.tsx +0 -190
- package/src/components/native-space-view/SpacePanelSheet.tsx +0 -45
- package/src/components/native-space-view/SpacePeopleSheet.tsx +0 -116
- package/src/components/native-space-view/SpaceStage.android.tsx +0 -372
- package/src/components/native-space-view/SpaceSurfacePrimitives.tsx +0 -75
- package/src/components/native-space-view/SpaceTopBar.android.tsx +0 -113
- package/src/components/native-space-view/SpaceWhiteboardSurface.tsx +0 -69
- package/src/components/native-space-view/diagnostics.ts +0 -84
- package/src/components/native-space-view/format-space-duration.ts +0 -3
- package/src/components/native-space-view/native-space-grid-keys.ts +0 -6
- package/src/components/native-space-view/native-space-page-store.ts +0 -41
- package/src/components/native-space-view/space-chat-attachments.test.ts +0 -26
- package/src/components/native-space-view/space-chat-attachments.ts +0 -37
- package/src/components/native-space-view/space-panel-layout.ts +0 -10
- package/src/components/native-space-view/space-progressive-surface-helpers.ts +0 -33
- package/src/components/native-space-view/space-progressive-surface-types.ts +0 -3
- package/src/components/native-space-view/types.ts +0 -25
- package/src/components/native-space-view/useSpaceViewController.contract.ts +0 -34
- package/src/components/native-space-view/useSpaceViewController.ts +0 -416
- package/src/components/native-space-view/useSpaceViewDerived.ts +0 -29
- package/src/components/native-space-view/useSpaceViewPanels.ts +0 -170
- package/src/feedback/native-capture.ts +0 -62
- package/src/feedback/native-evidence.ts +0 -37
- package/src/hooks/entrance-preview-store.ts +0 -99
- package/src/hooks/useEntrancePreview.ts +0 -26
- package/src/hooks/useLayout.ts +0 -37
- package/src/hooks/usePanels.ts +0 -30
- package/src/ios-pad.ts +0 -1
- package/src/ios-phone.ts +0 -1
- package/src/ios.ts +0 -1
- package/src/macos.ts +0 -1
- package/src/space-client/cloudflare-rtk-native.ts +0 -104
- package/src/telemetry.ts +0 -234
- package/src/types/hugeicons-direct.d.ts +0 -4
- package/src/types/react-native-safe-area-context.d.ts +0 -8
- package/src/ui/appearance.ts +0 -808
- package/src/ui/native-appearance-context.tsx +0 -74
- package/src/ui/native-theme.tsx +0 -54
- package/src/ui/native-types.ts +0 -40
- package/src/ui/participant-avatar.ts +0 -47
- package/src/ui/theme-tokens.ts +0 -41
- package/src/ui/theme.ts +0 -214
- 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
|
+
});
|